chore: fix some key not work

This commit is contained in:
ZhaiSoul 2025-03-10 23:45:43 +08:00
parent ef646203a7
commit a8a4938c97
43 changed files with 348 additions and 217 deletions

View File

@ -38,6 +38,10 @@
} }
} }
}, },
"camera": {
"enable": "Enable Camera",
"disable": "Disable Camera"
},
"detect": { "detect": {
"enable": "Enable Detect", "enable": "Enable Detect",
"disable": "Disable Detect" "disable": "Disable Detect"

View File

@ -64,6 +64,7 @@
"info": "信息" "info": "信息"
}, },
"menu": { "menu": {
"system": "系统",
"systemMetrics": "系统信息", "systemMetrics": "系统信息",
"configuration": "配置", "configuration": "配置",
"systemLogs": "系统日志", "systemLogs": "系统日志",

View File

@ -38,6 +38,10 @@
} }
} }
}, },
"camera": {
"enable": "开启摄像头",
"disable": "关闭摄像头"
},
"detect": { "detect": {
"enable": "启用检测", "enable": "启用检测",
"disable": "关闭检测" "disable": "关闭检测"

View File

@ -69,7 +69,7 @@ export default function CalendarFilterButton({
updateSelectedDay(undefined); updateSelectedDay(undefined);
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</> </>

View File

@ -76,7 +76,7 @@ import { Switch } from "../ui/switch";
import { CameraStreamingDialog } from "../settings/CameraStreamingDialog"; import { CameraStreamingDialog } from "../settings/CameraStreamingDialog";
import { DialogTrigger } from "@radix-ui/react-dialog"; import { DialogTrigger } from "@radix-ui/react-dialog";
import { useStreamingSettings } from "@/context/streaming-settings-provider"; import { useStreamingSettings } from "@/context/streaming-settings-provider";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type CameraGroupSelectorProps = { type CameraGroupSelectorProps = {
className?: string; className?: string;
@ -165,7 +165,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
</TooltipTrigger> </TooltipTrigger>
<TooltipPortal> <TooltipPortal>
<TooltipContent className="" side="right"> <TooltipContent className="" side="right">
{t("menu.live.allCameras")} {t("menu.live.allCameras", { ns: "common" })}
</TooltipContent> </TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>
@ -536,15 +536,19 @@ export function CameraGroupRow({
<AlertDialogTitle>{t("group.delete.confirm")}</AlertDialogTitle> <AlertDialogTitle>{t("group.delete.confirm")}</AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogDescription> <AlertDialogDescription>
{t("group.delete.confirm.desc", { name: group[0] })} <Trans ns="components/camera" values={{ name: group[0] }}>
group.delete.confirm.desc
</Trans>
</AlertDialogDescription> </AlertDialogDescription>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t("button.cancel")}</AlertDialogCancel> <AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
className={buttonVariants({ variant: "destructive" })} className={buttonVariants({ variant: "destructive" })}
onClick={onDeleteGroup} onClick={onDeleteGroup}
> >
{t("button.delete")} {t("button.delete", { ns: "common" })}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
@ -562,13 +566,13 @@ export function CameraGroupRow({
aria-label="Edit group" aria-label="Edit group"
onClick={onEditGroup} onClick={onEditGroup}
> >
{t("button.edit")} {t("button.edit", { ns: "common" })}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
aria-label="Delete group" aria-label="Delete group"
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}
> >
{t("button.delete")} {t("button.delete", { ns: "common" })}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenuPortal> </DropdownMenuPortal>
@ -585,7 +589,9 @@ export function CameraGroupRow({
onClick={onEditGroup} onClick={onEditGroup}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{t("button.edit")}</TooltipContent> <TooltipContent>
{t("button.edit", { ns: "common" })}
</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -596,7 +602,9 @@ export function CameraGroupRow({
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{t("button.delete")}</TooltipContent> <TooltipContent>
{t("button.delete", { ns: "common" })}
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
)} )}
@ -925,7 +933,7 @@ export function CameraGroupEdit({
aria-label="Cancel" aria-label="Cancel"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -937,10 +945,10 @@ export function CameraGroupEdit({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -42,7 +42,7 @@ export function CamerasFilterButton({
} }
if (!selectedCameras || selectedCameras.length == 0) { if (!selectedCameras || selectedCameras.length == 0) {
return t("menu.live.allCameras"); return t("menu.live.allCameras", { ns: "common" });
} }
return `${selectedCameras.includes("birdseye") ? selectedCameras.length - 1 : selectedCameras.length} Camera${selectedCameras.length !== 1 ? "s" : ""}`; return `${selectedCameras.includes("birdseye") ? selectedCameras.length - 1 : selectedCameras.length} Camera${selectedCameras.length !== 1 ? "s" : ""}`;
@ -236,7 +236,7 @@ export function CamerasFilterContent({
setOpen(false); setOpen(false);
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common" })}
</Button> </Button>
<Button <Button
aria-label="Reset" aria-label="Reset"
@ -245,7 +245,7 @@ export function CamerasFilterContent({
updateCameraFilter(undefined); updateCameraFilter(undefined);
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</> </>

View File

@ -577,10 +577,10 @@ export function GeneralFilterContent({
onClose(); onClose();
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common" })}
</Button> </Button>
<Button aria-label="Reset" onClick={onReset}> <Button aria-label="Reset" onClick={onReset}>
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</> </>

View File

@ -391,7 +391,7 @@ export function GeneralFilterContent({
onClose(); onClose();
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common" })}
</Button> </Button>
<Button <Button
aria-label="Reset" aria-label="Reset"
@ -400,7 +400,7 @@ export function GeneralFilterContent({
updateLabelFilter(undefined); updateLabelFilter(undefined);
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</> </>
@ -567,7 +567,7 @@ export function SortTypeContent({
onClose(); onClose();
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common" })}
</Button> </Button>
<Button <Button
aria-label="Reset" aria-label="Reset"
@ -576,7 +576,7 @@ export function SortTypeContent({
updateSortType(undefined); updateSortType(undefined);
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</> </>

View File

@ -101,10 +101,10 @@ export function GeneralFilterContent({
> >
{t( {t(
"masksAndZones." + "masksAndZones." +
item.replace(/_([a-z])/g, (letter) => item
letter.toUpperCase(), .replace(/_([a-z])/g, (letter) => letter.toUpperCase())
) + .replace("_", "") +
"s", "s.label",
{ ns: "views/settings" }, { ns: "views/settings" },
)} )}
</Label> </Label>

View File

@ -80,7 +80,7 @@ export function SaveSearchDialog({
)} )}
<DialogFooter> <DialogFooter>
<Button aria-label="Cancel" onClick={onClose}> <Button aria-label="Cancel" onClick={onClose}>
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
onClick={handleSave} onClick={handleSave}
@ -88,7 +88,7 @@ export function SaveSearchDialog({
className="mb-2 md:mb-0" className="mb-2 md:mb-0"
aria-label="Save this search" aria-label="Save this search"
> >
{t("button.save")} {t("button.save", { ns: "common" })}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -187,7 +187,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
> >
<a className="flex" href={logoutUrl}> <a className="flex" href={logoutUrl}>
<LuLogOut className="mr-2 size-4" /> <LuLogOut className="mr-2 size-4" />
<span>Logout</span> <span>{t("menu.user.logout", { ns: "common" })}</span>
</a> </a>
</MenuItem> </MenuItem>
</div> </div>

View File

@ -40,7 +40,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import useSWR from "swr"; import useSWR from "swr";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type SearchResultActionsProps = { type SearchResultActionsProps = {
searchResult: SearchResult; searchResult: SearchResult;
@ -154,7 +154,7 @@ export default function SearchResultActions({
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}
> >
<LuTrash2 className="mr-2 size-4" /> <LuTrash2 className="mr-2 size-4" />
<span>{t("button.delete")}</span> <span>{t("button.delete", { ns: "common" })}</span>
</MenuItem> </MenuItem>
</> </>
); );
@ -170,15 +170,17 @@ export default function SearchResultActions({
<AlertDialogTitle>{t("dialog.confirmDelete")}</AlertDialogTitle> <AlertDialogTitle>{t("dialog.confirmDelete")}</AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogDescription> <AlertDialogDescription>
{t("dialog.confirmDelete.desc")} <Trans ns="views/explore">dialog.confirmDelete.desc</Trans>
</AlertDialogDescription> </AlertDialogDescription>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t("button.cancel")}</AlertDialogCancel> <AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
className={buttonVariants({ variant: "destructive" })} className={buttonVariants({ variant: "destructive" })}
onClick={handleDelete} onClick={handleDelete}
> >
{t("button.delete")} {t("button.delete", { ns: "common" })}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -61,7 +61,7 @@ export default function NavItem({
<TooltipTrigger>{content}</TooltipTrigger> <TooltipTrigger>{content}</TooltipTrigger>
<TooltipPortal> <TooltipPortal>
<TooltipContent side="right"> <TooltipContent side="right">
<p>{t("{item.title}")}</p> <p>{t(item.title)}</p>
</TooltipContent> </TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>

View File

@ -15,7 +15,7 @@ import { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { Toaster } from "../ui/sonner"; import { Toaster } from "../ui/sonner";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type CameraInfoDialogProps = { type CameraInfoDialogProps = {
camera: CameraConfig; camera: CameraConfig;
@ -80,7 +80,7 @@ export default function CameraInfoDialog({
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<DialogDescription> <DialogDescription>
{t("cameras.info.streamDataFromFFPROBE")} <Trans ns="views/system">cameras.info.streamDataFromFFPROBE</Trans>
</DialogDescription> </DialogDescription>
<div className="mb-2 p-4"> <div className="mb-2 p-4">
@ -184,7 +184,7 @@ export default function CameraInfoDialog({
aria-label="Copy" aria-label="Copy"
onClick={() => onCopyFfprobe()} onClick={() => onCopyFfprobe()}
> >
{t("button.copy")} {t("button.copy", { ns: "common" })}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -211,7 +211,7 @@ export default function CreateUserDialog({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="text-sm font-medium"> <FormLabel className="text-sm font-medium">
{t("role.title")} {t("role.title", { ns: "common" })}
</FormLabel> </FormLabel>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
@ -229,7 +229,7 @@ export default function CreateUserDialog({
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Shield className="h-4 w-4 text-primary" /> <Shield className="h-4 w-4 text-primary" />
<span>{t("role.admin")}</span> <span>{t("role.admin", { ns: "common" })}</span>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem <SelectItem
@ -238,13 +238,13 @@ export default function CreateUserDialog({
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<User className="h-4 w-4 text-muted-foreground" /> <User className="h-4 w-4 text-muted-foreground" />
<span>{t("role.viewer")}</span> <span>{t("role.viewer", { ns: "common" })}</span>
</div> </div>
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription className="text-xs text-muted-foreground"> <FormDescription className="text-xs text-muted-foreground">
{t("role.desc")} {t("role.desc", { ns: "common" })}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -261,7 +261,7 @@ export default function CreateUserDialog({
onClick={handleCancel} onClick={handleCancel}
type="button" type="button"
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -273,10 +273,10 @@ export default function CreateUserDialog({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -48,7 +48,7 @@ export default function DeleteUserDialog({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="destructive" variant="destructive"
@ -56,7 +56,7 @@ export default function DeleteUserDialog({
className="flex flex-1" className="flex flex-1"
onClick={onDelete} onClick={onDelete}
> >
{t("button.delete")} {t("button.delete", { ns: "common" })}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -321,7 +321,7 @@ export function ExportContent({
className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`} className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</div> </div>
<Button <Button
className={isDesktop ? "" : "w-full"} className={isDesktop ? "" : "w-full"}

View File

@ -247,7 +247,7 @@ export default function MobileReviewSettingsDrawer({
}); });
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { import {
Dialog, Dialog,
@ -52,7 +52,7 @@ export default function RoleChangeDialog({
<div className="py-6"> <div className="py-6">
<div className="mb-4 text-sm text-muted-foreground"> <div className="mb-4 text-sm text-muted-foreground">
{t("users.dialog.changeRole.roleInfo")} <Trans ns="views/settings">users.dialog.changeRole.roleInfo</Trans>
</div> </div>
<Select <Select
@ -90,7 +90,7 @@ export default function RoleChangeDialog({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -99,7 +99,7 @@ export default function RoleChangeDialog({
onClick={() => onSave(selectedRole)} onClick={() => onSave(selectedRole)}
disabled={selectedRole === currentRole} disabled={selectedRole === currentRole}
> >
{t("button.save")} {t("button.save", { ns: "common" })}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@ export default function SaveExportOverlay({
onClick={onCancel} onClick={onCancel}
> >
<LuX /> <LuX />
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"

View File

@ -205,7 +205,7 @@ export default function SetPasswordDialog({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -214,7 +214,7 @@ export default function SetPasswordDialog({
onClick={handleSave} onClick={handleSave}
disabled={!password || password !== confirmPassword} disabled={!password || password !== confirmPassword}
> >
{t("button.save")} {t("button.save", { ns: "common" })}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -719,7 +719,7 @@ function ObjectDetailsTab({
aria-label="Save" aria-label="Save"
onClick={updateDescription} onClick={updateDescription}
> >
{t("button.save")} {t("button.save", { ns: "common" })}
</Button> </Button>
)} )}
<TextEntryDialog <TextEntryDialog
@ -834,7 +834,9 @@ export function ObjectSnapshotTab({
</a> </a>
</TooltipTrigger> </TooltipTrigger>
<TooltipPortal> <TooltipPortal>
<TooltipContent>{t("button.download")}</TooltipContent> <TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -84,7 +84,9 @@ export default function RestartDialog({
<AlertDialogTitle>{t("restart.title")}</AlertDialogTitle> <AlertDialogTitle>{t("restart.title")}</AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t("button.cancel")}</AlertDialogCancel> <AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction onClick={handleRestart}> <AlertDialogAction onClick={handleRestart}>
{t("restart.button")} {t("restart.button")}
</AlertDialogAction> </AlertDialogAction>

View File

@ -33,7 +33,7 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type SearchFilterDialogProps = { type SearchFilterDialogProps = {
config?: FrigateConfig; config?: FrigateConfig;
@ -176,7 +176,7 @@ export default function SearchFilterDialog({
setOpen(false); setOpen(false);
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common" })}
</Button> </Button>
<Button <Button
aria-label="Reset filters to default values" aria-label="Reset filters to default values"
@ -196,7 +196,7 @@ export default function SearchFilterDialog({
})); }));
}} }}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
</div> </div>
</div> </div>
@ -691,14 +691,14 @@ export function SnapshotClipFilterContent({
aria-label="Yes" aria-label="Yes"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.yes")} {t("button.yes", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
value="no" value="no"
aria-label="No" aria-label="No"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.no")} {t("button.no", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
</div> </div>
@ -732,7 +732,9 @@ export function SnapshotClipFilterContent({
side="left" side="left"
sideOffset={5} sideOffset={5}
> >
{t("features.submittedToFrigatePlus.tips")} <Trans ns="components/filter">
features.submittedToFrigatePlus.tips
</Trans>
</TooltipContent> </TooltipContent>
)} )}
</Tooltip> </Tooltip>
@ -767,14 +769,14 @@ export function SnapshotClipFilterContent({
aria-label="Yes" aria-label="Yes"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.yes")} {t("button.yes", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
value="no" value="no"
aria-label="No" aria-label="No"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.no")} {t("button.no", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
</div> </div>
@ -822,14 +824,14 @@ export function SnapshotClipFilterContent({
aria-label="Yes" aria-label="Yes"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.yes")} {t("button.yes", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
value="no" value="no"
aria-label="No" aria-label="No"
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white" className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
> >
{t("button.no")} {t("button.no", { ns: "common" })}
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
</div> </div>

View File

@ -91,10 +91,10 @@ export default function TextEntryDialog({
/> />
<DialogFooter className="pt-4"> <DialogFooter className="pt-4">
<Button type="button" onClick={() => setOpen(false)}> <Button type="button" onClick={() => setOpen(false)}>
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button variant="select" type="submit"> <Button variant="select" type="submit">
{t("button.save")} {t("button.save", { ns: "common" })}
</Button> </Button>
</DialogFooter> </DialogFooter>
</form> </form>

View File

@ -31,7 +31,7 @@ import useSWR from "swr";
import { LuCheck, LuExternalLink, LuInfo, LuX } from "react-icons/lu"; import { LuCheck, LuExternalLink, LuInfo, LuX } from "react-icons/lu";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { LiveStreamMetadata } from "@/types/live"; import { LiveStreamMetadata } from "@/types/live";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type CameraStreamingDialogProps = { type CameraStreamingDialogProps = {
camera: string; camera: string;
@ -173,7 +173,9 @@ export function CameraStreamingDialog({
cameraName: camera.replaceAll("_", " "), cameraName: camera.replaceAll("_", " "),
})} })}
</DialogTitle> </DialogTitle>
<DialogDescription>{t("group.camera.setting.desc")}</DialogDescription> <DialogDescription>
<Trans ns="components/camera">group.camera.setting.desc</Trans>
</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-col space-y-8"> <div className="flex flex-col space-y-8">
{!isRestreamed && ( {!isRestreamed && (
@ -190,7 +192,9 @@ export function CameraStreamingDialog({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">{t("button.info")}</span> <span className="sr-only">
{t("button.info", { ns: "common" })}
</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
@ -247,7 +251,9 @@ export function CameraStreamingDialog({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">{t("button.info")}</span> <span className="sr-only">
{t("button.info", { ns: "common" })}
</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
@ -355,7 +361,7 @@ export function CameraStreamingDialog({
aria-label="Cancel" aria-label="Cancel"
onClick={handleCancel} onClick={handleCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -367,10 +373,10 @@ export function CameraStreamingDialog({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -22,7 +22,7 @@ import { Toaster } from "../ui/sonner";
import ActivityIndicator from "../indicators/activity-indicator"; import ActivityIndicator from "../indicators/activity-indicator";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu"; import { LuExternalLink } from "react-icons/lu";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type MotionMaskEditPaneProps = { type MotionMaskEditPaneProps = {
polygons?: Polygon[]; polygons?: Polygon[];
@ -232,7 +232,9 @@ export default function MotionMaskEditPane({
: t("masksAndZones.motionMasks.add")} : t("masksAndZones.motionMasks.add")}
</Heading> </Heading>
<div className="my-3 space-y-3 text-sm text-muted-foreground"> <div className="my-3 space-y-3 text-sm text-muted-foreground">
<p>{t("masksAndZones.motionMasks.context")}</p> <p>
<Trans ns="views/settings">masksAndZones.motionMasks.context</Trans>
</p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -324,7 +326,7 @@ export default function MotionMaskEditPane({
aria-label="Cancel" aria-label="Cancel"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -336,10 +338,10 @@ export default function MotionMaskEditPane({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -365,7 +365,7 @@ export default function ObjectMaskEditPane({
aria-label="Cancel" aria-label="Cancel"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -377,10 +377,10 @@ export default function ObjectMaskEditPane({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -319,7 +319,9 @@ export default function PolygonItem({
}} }}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{t("button.edit")}</TooltipContent> <TooltipContent>
{t("button.edit", { ns: "common" })}
</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -332,7 +334,9 @@ export default function PolygonItem({
onClick={() => handleCopyCoordinates(index)} onClick={() => handleCopyCoordinates(index)}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{t("button.copyCoordinates")}</TooltipContent> <TooltipContent>
{t("button.copyCoordinates", { ns: "common" })}
</TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -346,7 +350,9 @@ export default function PolygonItem({
onClick={() => !isLoading && setDeleteDialogOpen(true)} onClick={() => !isLoading && setDeleteDialogOpen(true)}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{t("button.delete")}</TooltipContent> <TooltipContent>
{t("button.delete", { ns: "common" })}
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
)} )}

View File

@ -29,7 +29,7 @@ import { toast } from "sonner";
import { flattenPoints, interpolatePoints } from "@/utils/canvasUtil"; import { flattenPoints, interpolatePoints } from "@/utils/canvasUtil";
import ActivityIndicator from "../indicators/activity-indicator"; import ActivityIndicator from "../indicators/activity-indicator";
import { getAttributeLabels } from "@/utils/iconUtil"; import { getAttributeLabels } from "@/utils/iconUtil";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type ZoneEditPaneProps = { type ZoneEditPaneProps = {
polygons?: Polygon[]; polygons?: Polygon[];
@ -558,7 +558,9 @@ export default function ZoneEditPane({
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t("masksAndZones.zones.inertia.desc")} <Trans ns="views/settings">
masksAndZones.zones.inertia.desc
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -579,7 +581,9 @@ export default function ZoneEditPane({
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t("masksAndZones.zones.loiteringTime.desc")} <Trans ns="views/settings">
masksAndZones.zones.loiteringTime.desc
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -808,7 +812,7 @@ export default function ZoneEditPane({
aria-label="Cancel" aria-label="Cancel"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -820,10 +824,10 @@ export default function ZoneEditPane({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -431,7 +431,7 @@ export function DateRangePicker({
} }
}} }}
> >
{t("button.apply")} {t("button.apply", { ns: "common"})}
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
@ -442,7 +442,7 @@ export function DateRangePicker({
variant="ghost" variant="ghost"
aria-label="Reset" aria-label="Reset"
> >
{t("button.reset")} {t("button.reset", { ns: "common"})}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -125,14 +125,16 @@ function Exports() {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t("button.cancel")}</AlertDialogCancel> <AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<Button <Button
className="text-white" className="text-white"
aria-label="Delete Export" aria-label="Delete Export"
variant="destructive" variant="destructive"
onClick={() => onHandleDelete()} onClick={() => onHandleDelete()}
> >
{t("button.delete")} {t("button.delete", { ns: "common" })}
</Button> </Button>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -96,7 +96,7 @@ export default function EventView({
pullLatestData, pullLatestData,
updateFilter, updateFilter,
}: EventViewProps) { }: EventViewProps) {
const { t } = useTranslation(["views/event"]); const { t } = useTranslation(["views/events"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
@ -472,7 +472,7 @@ function DetectionReview({
setSelectedReviews, setSelectedReviews,
pullLatestData, pullLatestData,
}: DetectionReviewProps) { }: DetectionReviewProps) {
const { t } = useTranslation(["views/event"]); const { t } = useTranslation(["views/events"]);
const reviewTimelineRef = useRef<HTMLDivElement>(null); const reviewTimelineRef = useRef<HTMLDivElement>(null);
@ -870,7 +870,7 @@ function MotionReview({
motionOnly = false, motionOnly = false,
onOpenRecording, onOpenRecording,
}: MotionReviewProps) { }: MotionReviewProps) {
const { t } = useTranslation(["views/event"]); const { t } = useTranslation(["views/events"]);
const segmentDuration = 30; const segmentDuration = 30;
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");

View File

@ -696,8 +696,8 @@ export default function DraggableGridLayout({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{fullscreen {fullscreen
? t("button.exitFullscreen") ? t("button.exitFullscreen", { ns: "common" })
: t("button.fullscreen")} : t("button.fullscreen", { ns: "common" })}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</> </>

View File

@ -436,7 +436,9 @@ export default function LiveCameraView({
> >
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && ( {isDesktop && (
<div className="text-primary">{t("button.back")}</div> <div className="text-primary">
{t("button.back", { ns: "common" })}
</div>
)} )}
</Button> </Button>
<Button <Button
@ -458,7 +460,9 @@ export default function LiveCameraView({
> >
<LuHistory className="size-5 text-secondary-foreground" /> <LuHistory className="size-5 text-secondary-foreground" />
{isDesktop && ( {isDesktop && (
<div className="text-primary">{t("button.history")}</div> <div className="text-primary">
{t("button.history", { ns: "common" })}
</div>
)} )}
</Button> </Button>
</div> </div>
@ -479,7 +483,7 @@ export default function LiveCameraView({
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && ( {isDesktop && (
<div className="text-secondary-foreground"> <div className="text-secondary-foreground">
{t("button.back")} {t("button.back", { ns: "common" })}
</div> </div>
)} )}
</Button> </Button>
@ -491,7 +495,9 @@ export default function LiveCameraView({
Icon={fullscreen ? FaCompress : FaExpand} Icon={fullscreen ? FaCompress : FaExpand}
isActive={fullscreen} isActive={fullscreen}
title={ title={
fullscreen ? t("button.close") : t("button.fullscreen") fullscreen
? t("button.close", { ns: "common" })
: t("button.fullscreen", { ns: "common" })
} }
onClick={toggleFullscreen} onClick={toggleFullscreen}
/> />
@ -502,7 +508,11 @@ export default function LiveCameraView({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={LuPictureInPicture} Icon={LuPictureInPicture}
isActive={pip} isActive={pip}
title={pip ? t("button.close") : t("button.pictureInPicture")} title={
pip
? t("button.close", { ns: "common" })
: t("button.pictureInPicture", { ns: "common" })
}
onClick={() => { onClick={() => {
if (!pip) { if (!pip) {
setPip(true); setPip(true);
@ -1101,7 +1111,9 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={enabledState == "ON" ? LuPower : LuPowerOff} Icon={enabledState == "ON" ? LuPower : LuPowerOff}
isActive={enabledState == "ON"} isActive={enabledState == "ON"}
title={`${enabledState == "ON" ? "Disable" : "Enable"} Camera`} title={
enabledState == "ON" ? t("camera.disable") : t("camera.enable")
}
onClick={() => sendEnabled(enabledState == "ON" ? "OFF" : "ON")} onClick={() => sendEnabled(enabledState == "ON" ? "OFF" : "ON")}
disabled={false} disabled={false}
/> />
@ -1110,7 +1122,9 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff} Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
isActive={detectState == "ON"} isActive={detectState == "ON"}
title={`${detectState == "ON" ? "Disable" : "Enable"} Detect`} title={
detectState == "ON" ? t("detect.disable") : t("detect.enable")
}
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")} onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
disabled={!cameraEnabled} disabled={!cameraEnabled}
/> />
@ -1119,7 +1133,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={recordState == "ON" ? LuVideo : LuVideoOff} Icon={recordState == "ON" ? LuVideo : LuVideoOff}
isActive={recordState == "ON"} isActive={recordState == "ON"}
title={`${recordState == "ON" ? "Disable" : "Enable"} Recording`} title={
recordState == "ON"
? t("recording.disable")
: t("recording.enable")
}
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")} onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
disabled={!cameraEnabled} disabled={!cameraEnabled}
/> />
@ -1128,7 +1146,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography} Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
isActive={snapshotState == "ON"} isActive={snapshotState == "ON"}
title={`${snapshotState == "ON" ? "Disable" : "Enable"} Snapshots`} title={
snapshotState == "ON"
? t("snapshots.disable")
: t("snapshots.enable")
}
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")} onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
disabled={!cameraEnabled} disabled={!cameraEnabled}
/> />
@ -1138,7 +1160,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={audioState == "ON" ? LuEar : LuEarOff} Icon={audioState == "ON" ? LuEar : LuEarOff}
isActive={audioState == "ON"} isActive={audioState == "ON"}
title={`${audioState == "ON" ? "Disable" : "Enable"} Audio Detect`} title={
audioState == "ON"
? t("audioDetect.disable")
: t("audioDetect.enable")
}
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")} onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
disabled={!cameraEnabled} disabled={!cameraEnabled}
/> />
@ -1151,7 +1177,11 @@ function FrigateCameraFeatures({
autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff
} }
isActive={autotrackingState == "ON"} isActive={autotrackingState == "ON"}
title={`${autotrackingState == "ON" ? "Disable" : "Enable"} Autotracking`} title={
autotrackingState == "ON"
? t("autotracking.disable")
: t("autotracking.enable")
}
onClick={() => onClick={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
} }
@ -1189,11 +1219,13 @@ function FrigateCameraFeatures({
<div className="flex flex-col gap-5 p-4"> <div className="flex flex-col gap-5 p-4">
{!isRestreamed && ( {!isRestreamed && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label>{t("streaming", { ns: "components/dialog" })}</Label> <Label>
{t("streaming.label", { ns: "components/dialog" })}
</Label>
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground"> <div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
<LuX className="size-4 text-danger" /> <LuX className="size-4 text-danger" />
<div> <div>
{t("streaming.restreaming.disabled", { {t("streaming.restreaming.NotEnabled", {
ns: "components/dialog", ns: "components/dialog",
})} })}
</div> </div>
@ -1398,7 +1430,9 @@ function FrigateCameraFeatures({
className="mx-0 cursor-pointer text-primary" className="mx-0 cursor-pointer text-primary"
htmlFor="showstats" htmlFor="showstats"
> >
{t("streaming.showStats", { ns: "components/dialog" })} {t("streaming.showStats.label", {
ns: "components/dialog",
})}
</Label> </Label>
<Switch <Switch
className="ml-1" className="ml-1"

View File

@ -564,8 +564,8 @@ export default function LiveDashboardView({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{fullscreen {fullscreen
? t("button.exitFullscreen") ? t("button.exitFullscreen", { ns: "common" })
: t("button.fullscreen")} : t("button.fullscreen", { ns: "common" })}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -401,7 +401,9 @@ export function RecordingView({
> >
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && ( {isDesktop && (
<div className="text-primary">{t("button.back")}</div> <div className="text-primary">
{t("button.back", { ns: "common" })}
</div>
)} )}
</Button> </Button>
<Button <Button

View File

@ -256,7 +256,9 @@ export default function AuthenticationView() {
: "" : ""
} }
> >
{t("role." + user.role || "viewer")} {t("role." + (user.role || "viewer"), {
ns: "common",
})}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
@ -280,7 +282,7 @@ export default function AuthenticationView() {
> >
<LuUserCog className="size-3.5" /> <LuUserCog className="size-3.5" />
<span className="ml-1.5 hidden sm:inline-block"> <span className="ml-1.5 hidden sm:inline-block">
{t("role.title")} {t("role.title", { ns: "common" })}
</span> </span>
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
@ -326,7 +328,7 @@ export default function AuthenticationView() {
> >
<HiTrash className="size-3.5" /> <HiTrash className="size-3.5" />
<span className="ml-1.5 hidden sm:inline-block"> <span className="ml-1.5 hidden sm:inline-block">
{t("button.delete")} {t("button.delete", { ns: "common" })}
</span> </span>
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>

View File

@ -27,11 +27,11 @@ import { LuExternalLink } from "react-icons/lu";
import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { MdCircle } from "react-icons/md"; import { MdCircle } from "react-icons/md";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Trans } from "react-i18next";
import { t } from "i18next";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useAlertsState, useDetectionsState, useEnabledState } from "@/api/ws"; import { useAlertsState, useDetectionsState, useEnabledState } from "@/api/ws";
import { useTranslation } from "react-i18next";
type CameraSettingsViewProps = { type CameraSettingsViewProps = {
selectedCamera: string; selectedCamera: string;
@ -47,7 +47,6 @@ export default function CameraSettingsView({
selectedCamera, selectedCamera,
setUnsavedChanges, setUnsavedChanges,
}: CameraSettingsViewProps) { }: CameraSettingsViewProps) {
const { t } = useTranslation(["views/settings"]);
const { data: config, mutate: updateConfig } = const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config"); useSWR<FrigateConfig>("config");
@ -82,7 +81,7 @@ export default function CameraSettingsView({
.map((label) => t(label, { ns: "objects" })) .map((label) => t(label, { ns: "objects" }))
.join(", ") .join(", ")
: ""; : "";
}, [cameraConfig, t]); }, [cameraConfig]);
const detectionsLabels = useMemo(() => { const detectionsLabels = useMemo(() => {
return cameraConfig?.review.detections.labels return cameraConfig?.review.detections.labels
@ -90,7 +89,7 @@ export default function CameraSettingsView({
.map((label) => t(label, { ns: "objects" })) .map((label) => t(label, { ns: "objects" }))
.join(", ") .join(", ")
: ""; : "";
}, [cameraConfig, t]); }, [cameraConfig]);
// form // form
@ -191,7 +190,7 @@ export default function CameraSettingsView({
setIsLoading(false); setIsLoading(false);
}); });
}, },
[updateConfig, setIsLoading, selectedCamera, cameraConfig, t], [updateConfig, setIsLoading, selectedCamera, cameraConfig],
); );
const onCancel = useCallback(() => { const onCancel = useCallback(() => {
@ -260,13 +259,13 @@ export default function CameraSettingsView({
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0"> <div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{t("camera.title")} <Trans ns="views/settings">camera.title</Trans>
</Heading> </Heading>
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<Heading as="h4" className="my-2"> <Heading as="h4" className="my-2">
{t("camera.streams.title")} <Trans ns="views/settings">camera.streams.title</Trans>
</Heading> </Heading>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -279,16 +278,18 @@ export default function CameraSettingsView({
}} }}
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="camera-enabled">{t("button.enabled")}</Label> <Label htmlFor="camera-enabled">
<Trans>button.enabled</Trans>
</Label>
</div> </div>
</div> </div>
<div className="mt-3 text-sm text-muted-foreground"> <div className="mt-3 text-sm text-muted-foreground">
{t("camera.streams.desc")} <Trans ns="views/settings">camera.streams.desc</Trans>
</div> </div>
<Separator className="mb-2 mt-4 flex bg-secondary" /> <Separator className="mb-2 mt-4 flex bg-secondary" />
<Heading as="h4" className="my-2"> <Heading as="h4" className="my-2">
{t("camera.review.title")} <Trans ns="views/settings">camera.review.title</Trans>
</Heading> </Heading>
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 space-y-3 text-sm text-primary-variant"> <div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 space-y-3 text-sm text-primary-variant">
@ -303,7 +304,7 @@ export default function CameraSettingsView({
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="alerts-enabled"> <Label htmlFor="alerts-enabled">
{t("camera.review.alerts")} <Trans ns="views/settings">camera.review.alerts</Trans>
</Label> </Label>
</div> </div>
</div> </div>
@ -319,12 +320,12 @@ export default function CameraSettingsView({
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="detections-enabled"> <Label htmlFor="detections-enabled">
{t("camera.review.detections")} <Trans ns="views/settings">camera.review.detections</Trans>
</Label> </Label>
</div> </div>
</div> </div>
<div className="mt-3 text-sm text-muted-foreground"> <div className="mt-3 text-sm text-muted-foreground">
{t("camera.review.desc")} <Trans ns="views/settings">camera.review.desc</Trans>
</div> </div>
</div> </div>
</div> </div>
@ -332,12 +333,16 @@ export default function CameraSettingsView({
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<Heading as="h4" className="my-2"> <Heading as="h4" className="my-2">
{t("camera.reviewClassification.title")} <Trans ns="views/settings">camera.reviewClassification.title</Trans>
</Heading> </Heading>
<div className="max-w-6xl"> <div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant"> <div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
<p>{t("camera.reviewClassification.desc")}</p> <p>
<Trans ns="views/settings">
camera.reviewClassification.desc
</Trans>
</p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
to="https://docs.frigate.video/configuration/review" to="https://docs.frigate.video/configuration/review"
@ -345,7 +350,9 @@ export default function CameraSettingsView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
{t("camera.reviewClassification.readTheDocumentation")}{" "} <Trans ns="views/settings">
camera.reviewClassification.readTheDocumentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -374,13 +381,15 @@ export default function CameraSettingsView({
<> <>
<div className="mb-2"> <div className="mb-2">
<FormLabel className="flex flex-row items-center text-base"> <FormLabel className="flex flex-row items-center text-base">
{t("camera.review.alerts")} <Trans ns="views/settings">
camera.review.alerts
</Trans>
<MdCircle className="ml-3 size-2 text-severity_alert" /> <MdCircle className="ml-3 size-2 text-severity_alert" />
</FormLabel> </FormLabel>
<FormDescription> <FormDescription>
{t( <Trans ns="views/settings">
"camera.reviewClassification.selectAlertsZones", camera.reviewClassification.selectAlertsZones
)} </Trans>
</FormDescription> </FormDescription>
</div> </div>
<div className="max-w-md rounded-lg bg-secondary p-4 md:max-w-full"> <div className="max-w-md rounded-lg bg-secondary p-4 md:max-w-full">
@ -429,7 +438,9 @@ export default function CameraSettingsView({
</> </>
) : ( ) : (
<div className="font-normal text-destructive"> <div className="font-normal text-destructive">
{t("camera.reviewClassification.noDefinedZones")} <Trans ns="views/settings">
camera.reviewClassification.noDefinedZones
</Trans>
</div> </div>
)} )}
<FormMessage /> <FormMessage />
@ -450,6 +461,7 @@ export default function CameraSettingsView({
cameraName: capitalizeFirstLetter( cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "", cameraConfig?.name ?? "",
).replaceAll("_", " "), ).replaceAll("_", " "),
ns: "views/settings",
}, },
) )
: t("camera.reviewClassification.objectAlertsTips", { : t("camera.reviewClassification.objectAlertsTips", {
@ -457,6 +469,7 @@ export default function CameraSettingsView({
cameraName: capitalizeFirstLetter( cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "", cameraConfig?.name ?? "",
).replaceAll("_", " "), ).replaceAll("_", " "),
ns: "views/settings",
})} })}
</div> </div>
</FormItem> </FormItem>
@ -472,14 +485,16 @@ export default function CameraSettingsView({
<> <>
<div className="mb-2"> <div className="mb-2">
<FormLabel className="flex flex-row items-center text-base"> <FormLabel className="flex flex-row items-center text-base">
{t("camera.review.detections")} <Trans ns="views/settings">
camera.review.detections
</Trans>
<MdCircle className="ml-3 size-2 text-severity_detection" /> <MdCircle className="ml-3 size-2 text-severity_detection" />
</FormLabel> </FormLabel>
{selectDetections && ( {selectDetections && (
<FormDescription> <FormDescription>
{t( <Trans ns="views/settings">
"camera.reviewClassification.selectDetectionsZones", camera.reviewClassification.selectDetectionsZones
)} </Trans>
</FormDescription> </FormDescription>
)} )}
</div> </div>
@ -542,9 +557,9 @@ export default function CameraSettingsView({
htmlFor="select-detections" htmlFor="select-detections"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
{t( <Trans ns="views/settings">
"camera.reviewClassification.limitDetections", camera.reviewClassification.limitDetections
)} </Trans>
</label> </label>
</div> </div>
</div> </div>
@ -553,11 +568,11 @@ export default function CameraSettingsView({
<div className="text-sm"> <div className="text-sm">
{watchedDetectionsZones && {watchedDetectionsZones &&
watchedDetectionsZones.length > 0 watchedDetectionsZones.length > 0 ? (
? !selectDetections !selectDetections ? (
? t( <Trans
"camera.reviewClassification.zoneObjectDetectionsTips", i18nKey="camera.reviewClassification.zoneObjectDetectionsTips"
{ values={{
detectionsLabels, detectionsLabels,
zone: watchedDetectionsZones zone: watchedDetectionsZones
.map((zone) => .map((zone) =>
@ -570,11 +585,13 @@ export default function CameraSettingsView({
cameraName: capitalizeFirstLetter( cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "", cameraConfig?.name ?? "",
).replaceAll("_", " "), ).replaceAll("_", " "),
}, }}
) ns="views/settings"
: t( ></Trans>
"camera.reviewClassification.zoneObjectDetectionsTips.notSelectDetections", ) : (
{ <Trans
i18nKey="camera.reviewClassification.zoneObjectDetectionsTips.notSelectDetections"
values={{
detectionsLabels, detectionsLabels,
zone: watchedDetectionsZones zone: watchedDetectionsZones
.map((zone) => .map((zone) =>
@ -587,16 +604,21 @@ export default function CameraSettingsView({
cameraName: capitalizeFirstLetter( cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "", cameraConfig?.name ?? "",
).replaceAll("_", " "), ).replaceAll("_", " "),
}, }}
ns="views/settings"
/>
) )
: t( ) : (
"camera.reviewClassification.objectDetectionsTips", <Trans
{ i18nKey="camera.reviewClassification.objectDetectionsTips"
values={{
detectionsLabels, detectionsLabels,
cameraName: capitalizeFirstLetter( cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "", cameraConfig?.name ?? "",
).replaceAll("_", " "), ).replaceAll("_", " "),
}, }}
ns="views/settings"
/>
)} )}
</div> </div>
</FormItem> </FormItem>
@ -612,7 +634,7 @@ export default function CameraSettingsView({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
{t("button.cancel")} <Trans>button.cancel</Trans>
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -624,10 +646,12 @@ export default function CameraSettingsView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>
<Trans>button.saving</Trans>
</span>
</div> </div>
) : ( ) : (
t("button.save") <Trans>button.save</Trans>
)} )}
</Button> </Button>
</div> </div>

View File

@ -21,7 +21,7 @@ import { Separator } from "@/components/ui/separator";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu"; import { LuExternalLink } from "react-icons/lu";
import { StatusBarMessagesContext } from "@/context/statusbar-provider"; import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type MotionTunerViewProps = { type MotionTunerViewProps = {
selectedCamera: string; selectedCamera: string;
@ -236,7 +236,11 @@ export default function MotionTunerView({
{t("motionDetectionTuner.contourArea")} {t("motionDetectionTuner.contourArea")}
</Label> </Label>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p>{t("motionDetectionTuner.contourArea.desc")}</p> <p>
<Trans ns="views/settings">
motionDetectionTuner.contourArea.desc
</Trans>
</p>
</div> </div>
</div> </div>
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
@ -264,7 +268,9 @@ export default function MotionTunerView({
{t("motionDetectionTuner.improveContrast")} {t("motionDetectionTuner.improveContrast")}
</Label> </Label>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{t("motionDetectionTuner.improveContrast.desc")} <Trans ns="views/settings">
motionDetectionTuner.improveContrast.desc
</Trans>
</div> </div>
</div> </div>
<Switch <Switch
@ -285,7 +291,7 @@ export default function MotionTunerView({
aria-label="Reset" aria-label="Reset"
onClick={onCancel} onClick={onCancel}
> >
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -297,10 +303,10 @@ export default function MotionTunerView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -478,7 +478,7 @@ export default function NotificationView({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
{t("button.cancel")} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -490,10 +490,10 @@ export default function NotificationView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>

View File

@ -250,7 +250,7 @@ export default function ObjectSettingsView({
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only"> <span className="sr-only">
{t("button.info")} {t("button.info", { ns: "common" })}
</span> </span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>

View File

@ -20,7 +20,7 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
type ExploreSettingsViewProps = { type ExploreSettingsViewProps = {
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>; setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
@ -172,7 +172,9 @@ export default function ExploreSettingsView({
</Heading> </Heading>
<div className="max-w-6xl"> <div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant"> <div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
<p>{t("explore.semanticSearch.desc")}</p> <p>
<Trans ns="views/settings">explore.semanticSearch.desc</Trans>
</p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -200,7 +202,9 @@ export default function ExploreSettingsView({
}} }}
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="enabled">{t("button.enabled")}</Label> <Label htmlFor="enabled">
{t("button.enabled", { ns: "common" })}
</Label>
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@ -221,7 +225,9 @@ export default function ExploreSettingsView({
</div> </div>
</div> </div>
<div className="mt-3 text-sm text-muted-foreground"> <div className="mt-3 text-sm text-muted-foreground">
{t("explore.semanticSearch.reindexOnStartup.desc")} <Trans ns="views/settings">
explore.semanticSearch.reindexOnStartup.desc
</Trans>
</div> </div>
</div> </div>
<div className="mt-2 flex flex-col space-y-6"> <div className="mt-2 flex flex-col space-y-6">
@ -230,10 +236,22 @@ export default function ExploreSettingsView({
{t("explore.semanticSearch.modelSize.label")} {t("explore.semanticSearch.modelSize.label")}
</div> </div>
<div className="space-y-1 text-sm text-muted-foreground"> <div className="space-y-1 text-sm text-muted-foreground">
<p>{t("explore.semanticSearch.modelSize.desc")}</p> <p>
<Trans ns="views/settings">
explore.semanticSearch.modelSize.desc
</Trans>
</p>
<ul className="list-disc pl-5 text-sm"> <ul className="list-disc pl-5 text-sm">
<li>{t("explore.semanticSearch.modelSize.small.desc")}</li> <li>
<li>{t("explore.semanticSearch.modelSize.large.desc")}</li> <Trans ns="views/settings">
explore.semanticSearch.modelSize.small.desc
</Trans>
</li>
<li>
<Trans ns="views/settings">
explore.semanticSearch.modelSize.large.desc
</Trans>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -271,7 +289,7 @@ export default function ExploreSettingsView({
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]"> <div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
<Button className="flex flex-1" aria-label="Reset" onClick={onCancel}> <Button className="flex flex-1" aria-label="Reset" onClick={onCancel}>
{t("button.reset")} {t("button.reset", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -283,10 +301,10 @@ export default function ExploreSettingsView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>{t("button.saving")}</span> <span>{t("button.saving", { ns: "common" })}</span>
</div> </div>
) : ( ) : (
t("button.save") t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </div>