This commit is contained in:
ZhaiSoul 2024-12-27 01:27:21 +08:00
parent a5eb3d355d
commit ecb213bf9a
34 changed files with 633 additions and 233 deletions

View File

@ -127,7 +127,9 @@ export function CameraLineGraph({
className="size-2" className="size-2"
style={{ color: GRAPH_COLORS[labelIdx] }} style={{ color: GRAPH_COLORS[labelIdx] }}
/> />
<div className="text-xs text-muted-foreground">{t("ui.system.cameras.label." + label)}</div> <div className="text-xs text-muted-foreground">
{t("ui.system.cameras.label." + label)}
</div>
<div className="text-xs text-primary"> <div className="text-xs text-primary">
{lastValues[labelIdx]} {lastValues[labelIdx]}
{unit} {unit}

View File

@ -178,10 +178,20 @@ export function CombinedStorageGraph({
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead><Trans>ui.system.storage.cameraStorage.camera</Trans></TableHead> <TableHead>
<TableHead><Trans>ui.system.storage.cameraStorage.storageUsed</Trans></TableHead> <Trans>ui.system.storage.cameraStorage.camera</Trans>
<TableHead><Trans>ui.system.storage.cameraStorage.percentageOfTotalUsed</Trans></TableHead> </TableHead>
<TableHead><Trans>ui.system.storage.cameraStorage.bandwidth</Trans></TableHead> <TableHead>
<Trans>ui.system.storage.cameraStorage.storageUsed</Trans>
</TableHead>
<TableHead>
<Trans>
ui.system.storage.cameraStorage.percentageOfTotalUsed
</Trans>
</TableHead>
<TableHead>
<Trans>ui.system.storage.cameraStorage.bandwidth</Trans>
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -193,7 +203,9 @@ export function CombinedStorageGraph({
className="size-3 rounded-md" className="size-3 rounded-md"
style={{ backgroundColor: item.color }} style={{ backgroundColor: item.color }}
></div> ></div>
{item.name === "Unused" ? t("ui.system.storage.cameraStorage.unused"): item.name.replaceAll("_", " ")} {item.name === "Unused"
? t("ui.system.storage.cameraStorage.unused")
: item.name.replaceAll("_", " ")}
{item.name === "Unused" && ( {item.name === "Unused" && (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -209,7 +221,9 @@ export function CombinedStorageGraph({
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80"> <PopoverContent className="w-80">
<div className="space-y-2"> <div className="space-y-2">
<Trans>ui.system.storage.cameraStorage.unused.tips</Trans> <Trans>
ui.system.storage.cameraStorage.unused.tips
</Trans>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@ -67,7 +67,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
> >
<div className="scrollbar-container w-full flex-col overflow-y-auto overflow-x-hidden"> <div className="scrollbar-container w-full flex-col overflow-y-auto overflow-x-hidden">
<DropdownMenuLabel> <DropdownMenuLabel>
{t("ui.menu.user.current", {user: profile?.username || t("ui.menu.user.anonymous")})} {t("ui.menu.user.current", {
user: profile?.username || t("ui.menu.user.anonymous"),
})}
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} /> <DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
<MenuItem <MenuItem
@ -78,7 +80,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
> >
<a className="flex" href={logoutUrl}> <a className="flex" href={logoutUrl}>
<LuLogOut className="mr-2 size-4" /> <LuLogOut className="mr-2 size-4" />
<span><Trans>ui.menu.user.logout</Trans></span> <span>
<Trans>ui.menu.user.logout</Trans>
</span>
</a> </a>
</MenuItem> </MenuItem>
</div> </div>

View File

@ -104,7 +104,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
</TooltipTrigger> </TooltipTrigger>
<TooltipPortal> <TooltipPortal>
<TooltipContent side="right"> <TooltipContent side="right">
<p><Trans>ui.settings</Trans></p> <p>
<Trans>ui.settings</Trans>
</p>
</TooltipContent> </TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>
@ -148,7 +150,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
</MenuItem> </MenuItem>
</> </>
)} )}
<DropdownMenuLabel><Trans>ui.system</Trans></DropdownMenuLabel> <DropdownMenuLabel>
<Trans>ui.system</Trans>
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}> <DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}>
<Link to="/system#general"> <Link to="/system#general">
@ -161,7 +165,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
aria-label="System metrics" aria-label="System metrics"
> >
<LuActivity className="mr-2 size-4" /> <LuActivity className="mr-2 size-4" />
<span><Trans>ui.systemMetrics</Trans></span> <span>
<Trans>ui.systemMetrics</Trans>
</span>
</MenuItem> </MenuItem>
</Link> </Link>
<Link to="/logs"> <Link to="/logs">
@ -174,7 +180,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
aria-label="System logs" aria-label="System logs"
> >
<LuList className="mr-2 size-4" /> <LuList className="mr-2 size-4" />
<span><Trans>ui.systemLogs</Trans></span> <span>
<Trans>ui.systemLogs</Trans>
</span>
</MenuItem> </MenuItem>
</Link> </Link>
</DropdownMenuGroup> </DropdownMenuGroup>
@ -193,7 +201,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
aria-label="Settings" aria-label="Settings"
> >
<LuSettings className="mr-2 size-4" /> <LuSettings className="mr-2 size-4" />
<span><Trans>ui.settings</Trans></span> <span>
<Trans>ui.settings</Trans>
</span>
</MenuItem> </MenuItem>
</Link> </Link>
<Link to="/config"> <Link to="/config">
@ -206,7 +216,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
aria-label="Configuration editor" aria-label="Configuration editor"
> >
<LuPenSquare className="mr-2 size-4" /> <LuPenSquare className="mr-2 size-4" />
<span><Trans>ui.configurationEditor</Trans></span> <span>
<Trans>ui.configurationEditor</Trans>
</span>
</MenuItem> </MenuItem>
</Link> </Link>
<SubItem> <SubItem>
@ -218,7 +230,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
} }
> >
<LuLanguages className="mr-2 size-4" /> <LuLanguages className="mr-2 size-4" />
<span><Trans>ui.languages</Trans></span> <span>
<Trans>ui.languages</Trans>
</span>
</SubItemTrigger> </SubItemTrigger>
<Portal> <Portal>
<SubItemContent <SubItemContent
@ -242,7 +256,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.language.en</Trans> <Trans>ui.language.en</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.language.en</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.language.en</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -260,7 +276,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.language.zhCN</Trans> <Trans>ui.language.zhCN</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.language.zhCN</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.language.zhCN</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -278,7 +296,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.withSystem</Trans> <Trans>ui.withSystem</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.withSystem</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.withSystem</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
</SubItemContent> </SubItemContent>
@ -297,7 +317,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
} }
> >
<LuSunMoon className="mr-2 size-4" /> <LuSunMoon className="mr-2 size-4" />
<span><Trans>ui.darkMode</Trans></span> <span>
<Trans>ui.darkMode</Trans>
</span>
</SubItemTrigger> </SubItemTrigger>
<Portal> <Portal>
<SubItemContent <SubItemContent
@ -321,7 +343,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.darkMode.light</Trans> <Trans>ui.darkMode.light</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.darkMode.light</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.darkMode.light</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -339,7 +363,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.darkMode.dark</Trans> <Trans>ui.darkMode.dark</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.darkMode.dark</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.darkMode.dark</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -357,7 +383,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Trans>ui.withSystem</Trans> <Trans>ui.withSystem</Trans>
</> </>
) : ( ) : (
<span className="ml-6 mr-2"><Trans>ui.withSystem</Trans></span> <span className="ml-6 mr-2">
<Trans>ui.withSystem</Trans>
</span>
)} )}
</MenuItem> </MenuItem>
</SubItemContent> </SubItemContent>
@ -372,7 +400,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
} }
> >
<LuSunMoon className="mr-2 size-4" /> <LuSunMoon className="mr-2 size-4" />
<span><Trans>ui.theme</Trans></span> <span>
<Trans>ui.theme</Trans>
</span>
</SubItemTrigger> </SubItemTrigger>
<Portal> <Portal>
<SubItemContent <SubItemContent
@ -420,7 +450,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
aria-label={t("ui.documentation.label")} aria-label={t("ui.documentation.label")}
> >
<LuLifeBuoy className="mr-2 size-4" /> <LuLifeBuoy className="mr-2 size-4" />
<span><Trans>ui.documentation</Trans></span> <span>
<Trans>ui.documentation</Trans>
</span>
</MenuItem> </MenuItem>
</a> </a>
<a <a
@ -446,7 +478,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
onClick={() => setRestartDialogOpen(true)} onClick={() => setRestartDialogOpen(true)}
> >
<LuRotateCw className="mr-2 size-4" /> <LuRotateCw className="mr-2 size-4" />
<span><Trans>ui.restart</Trans></span> <span>
<Trans>ui.restart</Trans>
</span>
</MenuItem> </MenuItem>
</div> </div>
</Content> </Content>

View File

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

View File

@ -74,7 +74,9 @@ export default function CameraInfoDialog({
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="capitalize"> <DialogTitle className="capitalize">
{t("ui.system.cameras.info.cameraProbeInfo", {camera: camera.name.replaceAll("_", " ")})} {t("ui.system.cameras.info.cameraProbeInfo", {
camera: camera.name.replaceAll("_", " "),
})}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<DialogDescription> <DialogDescription>
@ -87,7 +89,7 @@ export default function CameraInfoDialog({
{ffprobeInfo.map((stream, idx) => ( {ffprobeInfo.map((stream, idx) => (
<div key={idx} className="mb-5"> <div key={idx} className="mb-5">
<div className="mb-1 rounded-md bg-secondary p-2 text-lg text-primary"> <div className="mb-1 rounded-md bg-secondary p-2 text-lg text-primary">
{t("ui.system.cameras.info.stream", {idx: idx + 1})} {t("ui.system.cameras.info.stream", { idx: idx + 1 })}
</div> </div>
{stream.return_code == 0 ? ( {stream.return_code == 0 ? (
<div> <div>
@ -95,7 +97,9 @@ export default function CameraInfoDialog({
<div className="" key={idx}> <div className="" key={idx}>
{codec.width ? ( {codec.width ? (
<div className="text-muted-foreground"> <div className="text-muted-foreground">
<div className="ml-2"><Trans>ui.system.cameras.info.video</Trans></div> <div className="ml-2">
<Trans>ui.system.cameras.info.video</Trans>
</div>
<div className="ml-5"> <div className="ml-5">
<div> <div>
<Trans>ui.system.cameras.info.codec</Trans> <Trans>ui.system.cameras.info.codec</Trans>
@ -107,7 +111,9 @@ export default function CameraInfoDialog({
<div> <div>
{codec.width && codec.height ? ( {codec.width && codec.height ? (
<> <>
<Trans>ui.system.cameras.info.resolution</Trans>{" "} <Trans>
ui.system.cameras.info.resolution
</Trans>{" "}
<span className="text-primary"> <span className="text-primary">
{" "} {" "}
{codec.width}x{codec.height} ( {codec.width}x{codec.height} (
@ -121,7 +127,9 @@ export default function CameraInfoDialog({
</> </>
) : ( ) : (
<span> <span>
<Trans>ui.system.cameras.info.resolution</Trans>{" "} <Trans>
ui.system.cameras.info.resolution
</Trans>{" "}
<span className="text-primary"> <span className="text-primary">
Unknown Unknown
</span> </span>
@ -154,7 +162,11 @@ export default function CameraInfoDialog({
</div> </div>
) : ( ) : (
<div className="px-2"> <div className="px-2">
<div>{t("ui.system.cameras.info.error", {error: stream.stderr})}</div> <div>
{t("ui.system.cameras.info.error", {
error: stream.stderr,
})}
</div>
</div> </div>
)} )}
</div> </div>
@ -163,7 +175,9 @@ export default function CameraInfoDialog({
) : ( ) : (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<ActivityIndicator /> <ActivityIndicator />
<div className="mt-2"><Trans>ui.system.cameras.info.fetching</Trans></div> <div className="mt-2">
<Trans>ui.system.cameras.info.fetching</Trans>
</div>
</div> </div>
)} )}
</div> </div>

View File

@ -64,7 +64,9 @@ export default function CreateUserDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.createUser</Trans></DialogTitle> <DialogTitle>
<Trans>ui.settingView.users.dialog.createUser</Trans>
</DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}> <form onSubmit={form.handleSubmit(onSubmit)}>
@ -72,7 +74,9 @@ export default function CreateUserDialog({
name="user" name="user"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.users.dialog.createUser.user</Trans></FormLabel> <FormLabel>
<Trans>ui.settingView.users.dialog.createUser.user</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -87,7 +91,11 @@ export default function CreateUserDialog({
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.users.dialog.createUser.password</Trans></FormLabel> <FormLabel>
<Trans>
ui.settingView.users.dialog.createUser.password
</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"

View File

@ -22,9 +22,13 @@ export default function DeleteUserDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.deleteUser</Trans></DialogTitle> <DialogTitle>
<Trans>ui.settingView.users.dialog.deleteUser</Trans>
</DialogTitle>
</DialogHeader> </DialogHeader>
<div><Trans>ui.settingView.users.dialog.deleteUser.warn</Trans></div> <div>
<Trans>ui.settingView.users.dialog.deleteUser.warn</Trans>
</div>
<DialogFooter> <DialogFooter>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"

View File

@ -26,7 +26,9 @@ export default function SetPasswordDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}> <DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader> <DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.setPassword</Trans></DialogTitle> <DialogTitle>
<Trans>ui.settingView.users.dialog.setPassword</Trans>
</DialogTitle>
</DialogHeader> </DialogHeader>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"

View File

@ -85,7 +85,9 @@ export default function RestartDialog({
</AlertDialogTitle> </AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel><Trans>ui.cancel</Trans></AlertDialogCancel> <AlertDialogCancel>
<Trans>ui.cancel</Trans>
</AlertDialogCancel>
<AlertDialogAction onClick={handleRestart}> <AlertDialogAction onClick={handleRestart}>
<Trans>ui.dialog.restart.button</Trans> <Trans>ui.dialog.restart.button</Trans>
</AlertDialogAction> </AlertDialogAction>
@ -105,7 +107,9 @@ export default function RestartDialog({
<Trans>ui.dialog.restart.restarting.title</Trans> <Trans>ui.dialog.restart.restarting.title</Trans>
</SheetTitle> </SheetTitle>
<SheetDescription className="text-center"> <SheetDescription className="text-center">
<div>{t("ui.dialog.restart.restarting.content", {countdown})}</div> <div>
{t("ui.dialog.restart.restarting.content", { countdown })}
</div>
</SheetDescription> </SheetDescription>
</SheetHeader> </SheetHeader>
<Button <Button

View File

@ -215,11 +215,15 @@ export default function MotionMaskEditPane({
<> <>
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? t("ui.settingView.masksAndZonesSettings.motionMasks.edit") : t("ui.settingView.masksAndZonesSettings.motionMasks.add")} {polygon.name.length
? t("ui.settingView.masksAndZonesSettings.motionMasks.edit")
: t("ui.settingView.masksAndZonesSettings.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> <p>
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.context</Trans> <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.context
</Trans>
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
@ -229,7 +233,9 @@ export default function MotionMaskEditPane({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.context.documentation</Trans>{" "} <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.context.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -239,7 +245,7 @@ export default function MotionMaskEditPane({
<div className="my-2 flex w-full flex-row justify-between text-sm"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{t("ui.settingView.masksAndZonesSettings.motionMasks.point", { {t("ui.settingView.masksAndZonesSettings.motionMasks.point", {
count: polygons[activePolygonIndex].points.length count: polygons[activePolygonIndex].points.length,
})} })}
{polygons[activePolygonIndex].isFinished && ( {polygons[activePolygonIndex].isFinished && (
<FaCheckCircle className="ml-2 size-5" /> <FaCheckCircle className="ml-2 size-5" />
@ -253,7 +259,10 @@ export default function MotionMaskEditPane({
</div> </div>
)} )}
<div className="mb-3 text-sm text-muted-foreground"> <div className="mb-3 text-sm text-muted-foreground">
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.clickDrawPolygon</Trans>Click to draw a polygon on the image. <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.clickDrawPolygon
</Trans>
Click to draw a polygon on the image.
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
@ -261,19 +270,26 @@ export default function MotionMaskEditPane({
{polygonArea && polygonArea >= 0.35 && ( {polygonArea && polygonArea >= 0.35 && (
<> <>
<div className="mb-3 text-sm text-danger"> <div className="mb-3 text-sm text-danger">
{t("ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge", { {t(
polygonArea: Math.round(polygonArea * 100) "ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge",
})} {
polygonArea: Math.round(polygonArea * 100),
},
)}
</div> </div>
<div className="mb-3 text-sm text-primary"> <div className="mb-3 text-sm text-primary">
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.tips</Trans> <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.tips
</Trans>
<Link <Link
to="https://github.com/blakeblackshear/frigate/discussions/13040" to="https://github.com/blakeblackshear/frigate/discussions/13040"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="my-3 block" className="my-3 block"
> >
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.documentation</Trans>{" "} <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -322,7 +338,9 @@ 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><Trans>ui.saving</Trans></span> <span>
<Trans>ui.saving</Trans>
</span>
</div> </div>
) : ( ) : (
<Trans>ui.save</Trans> <Trans>ui.save</Trans>

View File

@ -249,11 +249,15 @@ export default function ObjectMaskEditPane({
<> <>
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? t("ui.settingView.masksAndZonesSettings.objectMasks.edit") : t("ui.settingView.masksAndZonesSettings.objectMasks.add")} {polygon.name.length
? t("ui.settingView.masksAndZonesSettings.objectMasks.edit")
: t("ui.settingView.masksAndZonesSettings.objectMasks.add")}
</Heading> </Heading>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.context</Trans> <Trans>
ui.settingView.masksAndZonesSettings.objectMasks.context
</Trans>
</p> </p>
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
@ -261,7 +265,7 @@ export default function ObjectMaskEditPane({
<div className="my-2 flex w-full flex-row justify-between text-sm"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{t("ui.settingView.masksAndZonesSettings.objectMasks.point", { {t("ui.settingView.masksAndZonesSettings.objectMasks.point", {
count: polygons[activePolygonIndex].points.length count: polygons[activePolygonIndex].points.length,
})} })}
{polygons[activePolygonIndex].isFinished && ( {polygons[activePolygonIndex].isFinished && (
<FaCheckCircle className="ml-2 size-5" /> <FaCheckCircle className="ml-2 size-5" />
@ -275,7 +279,9 @@ export default function ObjectMaskEditPane({
</div> </div>
)} )}
<div className="mb-3 text-sm text-muted-foreground"> <div className="mb-3 text-sm text-muted-foreground">
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.clickDrawPolygon</Trans> <Trans>
ui.settingView.masksAndZonesSettings.objectMasks.clickDrawPolygon
</Trans>
Click to draw a polygon on the image. Click to draw a polygon on the image.
</div> </div>
@ -301,7 +307,11 @@ export default function ObjectMaskEditPane({
name="objects" name="objects"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.objectMasks.objects</Trans></FormLabel> <FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.objects
</Trans>
</FormLabel>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={field.value}
@ -317,7 +327,9 @@ export default function ObjectMaskEditPane({
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription> <FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.objects.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.objectMasks.objects.desc
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -414,7 +426,11 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) {
return ( return (
<> <>
<SelectGroup> <SelectGroup>
<SelectItem value="all_labels"><Trans>ui.settingView.masksAndZonesSettings.objectMasks.objects.allObjectTypes</Trans></SelectItem> <SelectItem value="all_labels">
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.objects.allObjectTypes
</Trans>
</SelectItem>
<SelectSeparator className="bg-secondary" /> <SelectSeparator className="bg-secondary" />
{allLabels.map((item) => ( {allLabels.map((item) => (
<SelectItem key={item} value={item}> <SelectItem key={item} value={item}>

View File

@ -332,7 +332,9 @@ export default function ZoneEditPane({
<> <>
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? t("ui.settingView.masksAndZonesSettings.zone.edit") : t("ui.settingView.masksAndZonesSettings.zone.add")} {polygon.name.length
? t("ui.settingView.masksAndZonesSettings.zone.edit")
: t("ui.settingView.masksAndZonesSettings.zone.add")}
</Heading> </Heading>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
@ -343,8 +345,10 @@ export default function ZoneEditPane({
{polygons && activePolygonIndex !== undefined && ( {polygons && activePolygonIndex !== undefined && (
<div className="my-2 flex w-full flex-row justify-between text-sm"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{t("ui.settingView.masksAndZonesSettings.zone.point", { count: polygons[activePolygonIndex].points.length })} {t("ui.settingView.masksAndZonesSettings.zone.point", {
count: polygons[activePolygonIndex].points.length,
})}
{polygons[activePolygonIndex].isFinished && ( {polygons[activePolygonIndex].isFinished && (
<FaCheckCircle className="ml-2 size-5" /> <FaCheckCircle className="ml-2 size-5" />
)} )}
@ -357,7 +361,9 @@ export default function ZoneEditPane({
</div> </div>
)} )}
<div className="mb-3 text-sm text-muted-foreground"> <div className="mb-3 text-sm text-muted-foreground">
<Trans>ui.settingView.masksAndZonesSettings.zone.clickDrawPolygon</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.clickDrawPolygon
</Trans>
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
@ -369,16 +375,22 @@ export default function ZoneEditPane({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.name</Trans></FormLabel> <FormLabel>
<Trans>ui.settingView.masksAndZonesSettings.zone.name</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
placeholder={t("ui.settingView.masksAndZonesSettings.zone.name.inputPlaceHolder")} placeholder={t(
"ui.settingView.masksAndZonesSettings.zone.name.inputPlaceHolder",
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.name.tips</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.name.tips
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -390,7 +402,11 @@ export default function ZoneEditPane({
name="inertia" name="inertia"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.inertia</Trans></FormLabel> <FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.zone.inertia
</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -399,7 +415,9 @@ export default function ZoneEditPane({
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.inertia.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.inertia.desc
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -411,7 +429,11 @@ export default function ZoneEditPane({
name="loitering_time" name="loitering_time"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.loiteringTime</Trans></FormLabel> <FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.zone.loiteringTime
</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -420,7 +442,9 @@ export default function ZoneEditPane({
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.loiteringTime.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.loiteringTime.desc
</Trans>
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -428,9 +452,13 @@ export default function ZoneEditPane({
/> />
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.objects</Trans></FormLabel> <FormLabel>
<Trans>ui.settingView.masksAndZonesSettings.zone.objects</Trans>
</FormLabel>
<FormDescription> <FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.objects.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.objects.desc
</Trans>
</FormDescription> </FormDescription>
<ZoneObjectSelector <ZoneObjectSelector
camera={polygon.camera} camera={polygon.camera}

View File

@ -1,5 +1,5 @@
import { createContext, useContext, useState, useEffect, useMemo } from 'react'; import { createContext, useContext, useState, useEffect, useMemo } from "react";
import i18next from 'i18next'; import i18next from "i18next";
type LanguageProviderState = { type LanguageProviderState = {
language: string; language: string;
@ -8,17 +8,18 @@ type LanguageProviderState = {
}; };
const initialState: LanguageProviderState = { const initialState: LanguageProviderState = {
language: i18next.language || 'en', language: i18next.language || "en",
systemLanguage: 'en', systemLanguage: "en",
setLanguage: () => null, setLanguage: () => null,
}; };
const LanguageProviderContext = createContext<LanguageProviderState>(initialState); const LanguageProviderContext =
createContext<LanguageProviderState>(initialState);
export function LanguageProvider({ export function LanguageProvider({
children, children,
defaultLanguage = 'en', defaultLanguage = "en",
storageKey = 'frigate-ui-language', storageKey = "frigate-ui-language",
...props ...props
}: { }: {
children: React.ReactNode; children: React.ReactNode;
@ -27,27 +28,26 @@ export function LanguageProvider({
}) { }) {
const [language, setLanguage] = useState<string>(() => { const [language, setLanguage] = useState<string>(() => {
try { try {
const storedData = localStorage.getItem(storageKey); const storedData = localStorage.getItem(storageKey);
const newLanguage = storedData || defaultLanguage; const newLanguage = storedData || defaultLanguage;
i18next.changeLanguage(newLanguage); i18next.changeLanguage(newLanguage);
return newLanguage; return newLanguage;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('Error retrieving language data from storage:', error); console.error("Error retrieving language data from storage:", error);
return defaultLanguage; return defaultLanguage;
} }
}); });
const systemLanguage = useMemo<string | undefined>(() => { const systemLanguage = useMemo<string | undefined>(() => {
if (typeof window === 'undefined') return undefined; if (typeof window === "undefined") return undefined;
return window.navigator.language; return window.navigator.language;
}, []); }, []);
useEffect(() => { useEffect(() => {
if (language === systemLanguage) return; if (language === systemLanguage) return;
i18next.changeLanguage(language); i18next.changeLanguage(language);
}, [language]); }, [language, systemLanguage]);
const value = { const value = {
language, language,
@ -68,10 +68,10 @@ export function LanguageProvider({
// eslint-disable-next-line react-refresh/only-export-components // eslint-disable-next-line react-refresh/only-export-components
export const useLanguage = () => { export const useLanguage = () => {
const context = useContext(LanguageProviderContext); const context = useContext(LanguageProviderContext);
if (context === undefined) if (context === undefined)
throw new Error("useLanguage must be used within a LanguageProvider"); throw new Error("useLanguage must be used within a LanguageProvider");
return context; return context;
}; };

View File

@ -16,19 +16,16 @@ function providers({ children }: TProvidersProps) {
<RecoilRoot> <RecoilRoot>
<ApiProvider> <ApiProvider>
<ThemeProvider defaultTheme="system" storageKey="frigate-ui-theme"> <ThemeProvider defaultTheme="system" storageKey="frigate-ui-theme">
<TooltipProvider> <LanguageProvider>
<IconContext.Provider value={{ size: "20" }}> <TooltipProvider>
<StatusBarMessagesProvider>{children}</StatusBarMessagesProvider> <IconContext.Provider value={{ size: "20" }}>
</IconContext.Provider> <StatusBarMessagesProvider>
</TooltipProvider> {children}
</StatusBarMessagesProvider>
</IconContext.Provider>
</TooltipProvider>
</LanguageProvider>
</ThemeProvider> </ThemeProvider>
<LanguageProvider>
<TooltipProvider>
<IconContext.Provider value={{ size: "20" }}>
<StatusBarMessagesProvider>{children}</StatusBarMessagesProvider>
</IconContext.Provider>
</TooltipProvider>
</LanguageProvider>
</ApiProvider> </ApiProvider>
</RecoilRoot> </RecoilRoot>
); );

View File

@ -23,7 +23,7 @@ export const colorSchemes: ColorScheme[] = [
// eslint-disable-next-line react-refresh/only-export-components // eslint-disable-next-line react-refresh/only-export-components
export const friendlyColorSchemeName = (className: string): string => { export const friendlyColorSchemeName = (className: string): string => {
const words = className.split("-").slice(1); // Exclude the first word (e.g., 'theme') const words = className.split("-").slice(1); // Exclude the first word (e.g., 'theme')
return "ui.theme."+words.join("."); return "ui.theme." + words.join(".");
}; };
type ThemeProviderProps = { type ThemeProviderProps = {
@ -125,7 +125,7 @@ export function ThemeProvider({
return ( return (
<ThemeProviderContext.Provider {...props} value={value}> <ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider> </ThemeProviderContext.Provider>
); );
} }

View File

@ -72,6 +72,6 @@ export default function useNavigation(
enabled: isDesktop && config?.face_recognition.enabled, enabled: isDesktop && config?.face_recognition.enabled,
}, },
] as NavData[], ] as NavData[],
[config?.face_recognition.enabled, variant], [config?.face_recognition?.enabled, variant],
); );
} }

View File

@ -73,7 +73,10 @@ export default function useStats(stats: FrigateStats | undefined) {
if (!isNaN(ffmpegAvg) && ffmpegAvg >= CameraFfmpegThreshold.error) { if (!isNaN(ffmpegAvg) && ffmpegAvg >= CameraFfmpegThreshold.error) {
problems.push({ problems.push({
text: t("ui.stats.ffmpegHighCpuUsage", {camera: capitalizeFirstLetter(name.replaceAll("_", " ")), ffmpegAvg}),//`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`, text: t("ui.stats.ffmpegHighCpuUsage", {
camera: capitalizeFirstLetter(name.replaceAll("_", " ")),
ffmpegAvg,
}), //`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`,
color: "text-danger", color: "text-danger",
relevantLink: "/system#cameras", relevantLink: "/system#cameras",
}); });
@ -81,7 +84,10 @@ export default function useStats(stats: FrigateStats | undefined) {
if (!isNaN(detectAvg) && detectAvg >= CameraDetectThreshold.error) { if (!isNaN(detectAvg) && detectAvg >= CameraDetectThreshold.error) {
problems.push({ problems.push({
text: t("ui.stats.detectHighCpuUsage", {camera: capitalizeFirstLetter(name.replaceAll("_", " ")), detectAvg}),//`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`, text: t("ui.stats.detectHighCpuUsage", {
camera: capitalizeFirstLetter(name.replaceAll("_", " ")),
detectAvg,
}), //`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`,
color: "text-danger", color: "text-danger",
relevantLink: "/system#cameras", relevantLink: "/system#cameras",
}); });

View File

@ -200,7 +200,9 @@ function ConfigEditor() {
onClick={() => handleCopyConfig()} onClick={() => handleCopyConfig()}
> >
<LuCopy className="text-secondary-foreground" /> <LuCopy className="text-secondary-foreground" />
<span className="hidden md:block"><Trans>ui.configEditorView.copyConfig</Trans></span> <span className="hidden md:block">
<Trans>ui.configEditorView.copyConfig</Trans>
</span>
</Button> </Button>
<Button <Button
size="sm" size="sm"
@ -212,7 +214,9 @@ function ConfigEditor() {
<LuSave className="absolute left-0 top-0 size-3 text-secondary-foreground" /> <LuSave className="absolute left-0 top-0 size-3 text-secondary-foreground" />
<MdOutlineRestartAlt className="absolute size-4 translate-x-1 translate-y-1/2 text-secondary-foreground" /> <MdOutlineRestartAlt className="absolute size-4 translate-x-1 translate-y-1/2 text-secondary-foreground" />
</div> </div>
<span className="hidden md:block"><Trans>ui.configEditorView.saveAndRestart</Trans></span> <span className="hidden md:block">
<Trans>ui.configEditorView.saveAndRestart</Trans>
</span>
</Button> </Button>
<Button <Button
size="sm" size="sm"
@ -221,7 +225,9 @@ function ConfigEditor() {
onClick={() => onHandleSaveConfig("saveonly")} onClick={() => onHandleSaveConfig("saveonly")}
> >
<LuSave className="text-secondary-foreground" /> <LuSave className="text-secondary-foreground" />
<span className="hidden md:block"><Trans>ui.configEditorView.saveOnly</Trans></span> <span className="hidden md:block">
<Trans>ui.configEditorView.saveOnly</Trans>
</span>
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -148,7 +148,9 @@ export default function Settings() {
data-nav-item={item} data-nav-item={item}
aria-label={`Select ${item}`} aria-label={`Select ${item}`}
> >
<div className="capitalize">{t("ui.settingView.menu." + item)}</div> <div className="capitalize">
{t("ui.settingView.menu." + item)}
</div>
</ToggleGroupItem> </ToggleGroupItem>
))} ))}
</ToggleGroup> </ToggleGroup>

View File

@ -71,7 +71,9 @@ function System() {
{item == "general" && <LuActivity className="size-4" />} {item == "general" && <LuActivity className="size-4" />}
{item == "storage" && <LuHardDrive className="size-4" />} {item == "storage" && <LuHardDrive className="size-4" />}
{item == "cameras" && <FaVideo className="size-4" />} {item == "cameras" && <FaVideo className="size-4" />}
{isDesktop && <div className="capitalize">{t("ui.system." + item)}</div>} {isDesktop && (
<div className="capitalize">{t("ui.system." + item)}</div>
)}
</ToggleGroupItem> </ToggleGroupItem>
))} ))}
</ToggleGroup> </ToggleGroup>
@ -79,7 +81,8 @@ function System() {
<div className="flex h-full items-center"> <div className="flex h-full items-center">
{lastUpdated && ( {lastUpdated && (
<div className="h-full content-center text-sm text-muted-foreground"> <div className="h-full content-center text-sm text-muted-foreground">
<Trans>ui.system.lastRefreshed</Trans><TimeAgo time={lastUpdated * 1000} dense /> <Trans>ui.system.lastRefreshed</Trans>
<TimeAgo time={lastUpdated * 1000} dense />
</div> </div>
)} )}
</div> </div>

View File

@ -12,16 +12,24 @@ i18n
// if you're using a language detector, do not define the lng option // if you're using a language detector, do not define the lng option
backend: { backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json" loadPath: "/locales/{{lng}}/{{ns}}.json",
}, },
react: { react: {
transSupportBasicHtmlNodes: true, transSupportBasicHtmlNodes: true,
transKeepBasicHtmlNodesFor: ["br", "strong", "i", "em", "li", "p", "code"], transKeepBasicHtmlNodesFor: [
"br",
"strong",
"i",
"em",
"li",
"p",
"code",
],
}, },
interpolation: { interpolation: {
escapeValue: false // react already safes from xss escapeValue: false, // react already safes from xss
} },
}); });
export default i18n; export default i18n;

View File

@ -365,7 +365,11 @@ export default function LiveCameraView({
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
> >
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && <div className="text-primary"><Trans>ui.back</Trans></div>} {isDesktop && (
<div className="text-primary">
<Trans>ui.back</Trans>
</div>
)}
</Button> </Button>
<Button <Button
className="flex items-center gap-2.5 rounded-lg" className="flex items-center gap-2.5 rounded-lg"
@ -385,7 +389,11 @@ export default function LiveCameraView({
}} }}
> >
<LuHistory className="size-5 text-secondary-foreground" /> <LuHistory className="size-5 text-secondary-foreground" />
{isDesktop && <div className="text-primary"><Trans>ui.history</Trans></div>} {isDesktop && (
<div className="text-primary">
<Trans>ui.history</Trans>
</div>
)}
</Button> </Button>
</div> </div>
) : ( ) : (
@ -849,7 +857,11 @@ 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" ? t("ui.live.detect.disable") : t("ui.live.detect.enable")} title={
detectState == "ON"
? t("ui.live.detect.disable")
: t("ui.live.detect.enable")
}
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")} onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
/> />
<CameraFeatureToggle <CameraFeatureToggle
@ -857,7 +869,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" ? t("ui.live.recording.disable") : t("ui.live.recording.enable")} title={
recordState == "ON"
? t("ui.live.recording.disable")
: t("ui.live.recording.enable")
}
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")} onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
/> />
<CameraFeatureToggle <CameraFeatureToggle
@ -865,7 +881,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" ? t("ui.live.snapshots.disable") : t("ui.live.snapshots.enable")} title={
snapshotState == "ON"
? t("ui.live.snapshots.disable")
: t("ui.live.snapshots.enable")
}
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")} onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
/> />
{audioDetectEnabled && ( {audioDetectEnabled && (
@ -874,7 +894,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" ? t("ui.live.audioDetect.disable") : t("ui.live.audioDetect.enable")} title={
audioState == "ON"
? t("ui.live.audioDetect.disable")
: t("ui.live.audioDetect.enable")
}
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")} onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
/> />
)} )}
@ -884,7 +908,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff} Icon={autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff}
isActive={autotrackingState == "ON"} isActive={autotrackingState == "ON"}
title={autotrackingState == "ON" ? t("ui.live.autotracking.disable") : t("ui.live.autotracking.enable")} title={
autotrackingState == "ON"
? t("ui.live.autotracking.disable")
: t("ui.live.autotracking.enable")
}
onClick={() => onClick={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
} }

View File

@ -124,7 +124,9 @@ export default function AuthenticationView() {
}} }}
> >
<FaUserEdit /> <FaUserEdit />
<div className="hidden md:block"><Trans>ui.settingView.users.updatePassword</Trans></div> <div className="hidden md:block">
<Trans>ui.settingView.users.updatePassword</Trans>
</div>
</Button> </Button>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"
@ -136,7 +138,9 @@ export default function AuthenticationView() {
}} }}
> >
<HiTrash /> <HiTrash />
<div className="hidden md:block"><Trans>ui.delete</Trans></div> <div className="hidden md:block">
<Trans>ui.delete</Trans>
</div>
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -253,7 +253,9 @@ export default function CameraSettingsView({
<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> <p>
<Trans>ui.settingView.cameraSettings.reviewClassification.desc</Trans> <Trans>
ui.settingView.cameraSettings.reviewClassification.desc
</Trans>
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -262,7 +264,9 @@ export default function CameraSettingsView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.cameraSettings.reviewClassification.readTheDocumentation</Trans>{" "} <Trans>
ui.settingView.cameraSettings.reviewClassification.readTheDocumentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -295,7 +299,9 @@ export default function CameraSettingsView({
<MdCircle className="ml-3 size-2 text-severity_alert" /> <MdCircle className="ml-3 size-2 text-severity_alert" />
</FormLabel> </FormLabel>
<FormDescription> <FormDescription>
<Trans>ui.settingView.cameraSettings.reviewClassification.selectAlertsZones</Trans> <Trans>
ui.settingView.cameraSettings.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">
@ -344,17 +350,40 @@ export default function CameraSettingsView({
</> </>
) : ( ) : (
<div className="font-normal text-destructive"> <div className="font-normal text-destructive">
<Trans>ui.settingView.cameraSettings.reviewClassification.noDefinedZones</Trans> <Trans>
ui.settingView.cameraSettings.reviewClassification.noDefinedZones
</Trans>
</div> </div>
)} )}
<FormMessage /> <FormMessage />
<div className="text-sm"> <div className="text-sm">
{watchedAlertsZones && watchedAlertsZones.length > 0 {watchedAlertsZones && watchedAlertsZones.length > 0
? ? t(
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectAlertsTips", { alertsLabels, zone: watchedAlertsZones.map((zone) => capitalizeFirstLetter(zone).replaceAll("_", " ")).join(", "), cameraName: capitalizeFirstLetter(cameraConfig?.name ?? "",).replaceAll("_", " ") }) "ui.settingView.cameraSettings.reviewClassification.zoneObjectAlertsTips",
: {
t("ui.settingView.cameraSettings.reviewClassification.objectAlertsTips", { alertsLabels, cameraName: capitalizeFirstLetter(cameraConfig?.name ?? "",).replaceAll("_", " ") }) alertsLabels,
} zone: watchedAlertsZones
.map((zone) =>
capitalizeFirstLetter(zone).replaceAll(
"_",
" ",
),
)
.join(", "),
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
},
)
: t(
"ui.settingView.cameraSettings.reviewClassification.objectAlertsTips",
{
alertsLabels,
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
},
)}
</div> </div>
</FormItem> </FormItem>
)} )}
@ -445,37 +474,52 @@ export default function CameraSettingsView({
)} )}
<div className="text-sm"> <div className="text-sm">
{watchedDetectionsZones && {watchedDetectionsZones &&
watchedDetectionsZones.length > 0 watchedDetectionsZones.length > 0
? ? !selectDetections
!selectDetections ? ? t(
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips", { "ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips",
detectionsLabels, {
zone: watchedDetectionsZones.map((zone) => detectionsLabels,
capitalizeFirstLetter(zone).replaceAll("_", " "), zone: watchedDetectionsZones
).join(", "), .map((zone) =>
cameraName: capitalizeFirstLetter( capitalizeFirstLetter(zone).replaceAll(
cameraConfig?.name ?? "", "_",
).replaceAll("_", " "), " ",
}) ),
: )
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips.notSelectDetections",{ .join(", "),
detectionsLabels, cameraName: capitalizeFirstLetter(
zone: watchedDetectionsZones.map((zone) => cameraConfig?.name ?? "",
capitalizeFirstLetter(zone).replaceAll("_", " "), ).replaceAll("_", " "),
).join(", "), },
cameraName: capitalizeFirstLetter( )
cameraConfig?.name ?? "", : t(
).replaceAll("_", " "), "ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips.notSelectDetections",
}) {
: detectionsLabels,
t("ui.settingView.cameraSettings.reviewClassification.objectDetectionsTips", { zone: watchedDetectionsZones
detectionsLabels, .map((zone) =>
cameraName: capitalizeFirstLetter( capitalizeFirstLetter(zone).replaceAll(
cameraConfig?.name ?? "", "_",
).replaceAll("_", " "), " ",
}) ),
} )
.join(", "),
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
},
)
: t(
"ui.settingView.cameraSettings.reviewClassification.objectDetectionsTips",
{
detectionsLabels,
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
},
)}
</div> </div>
</FormItem> </FormItem>
)} )}
@ -502,7 +546,9 @@ 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><Trans>ui.saving</Trans></span> <span>
<Trans>ui.saving</Trans>
</span>
</div> </div>
) : ( ) : (
<Trans>ui.save</Trans> <Trans>ui.save</Trans>

View File

@ -432,12 +432,18 @@ export default function MasksAndZonesView({
<div className="my-3 flex flex-row items-center justify-between"> <div className="my-3 flex flex-row items-center justify-between">
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default"><Trans>ui.settingView.masksAndZonesSettings.zone</Trans></div> <div className="text-md cursor-default">
<Trans>
ui.settingView.masksAndZonesSettings.zone
</Trans>
</div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
<Trans>ui.settingView.masksAndZonesSettings.zone.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.zone.desc
</Trans>
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -446,7 +452,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.masksAndZonesSettings.zone.desc.documentation</Trans>{" "} <Trans>
ui.settingView.masksAndZonesSettings.zone.desc.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -467,7 +475,11 @@ export default function MasksAndZonesView({
<LuPlus /> <LuPlus />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.zone.add</Trans></TooltipContent> <TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.zone.add
</Trans>
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
{allPolygons {allPolygons
@ -497,13 +509,17 @@ export default function MasksAndZonesView({
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default"> <div className="text-md cursor-default">
<Trans>ui.settingView.masksAndZonesSettings.motionMasks</Trans> <Trans>
ui.settingView.masksAndZonesSettings.motionMasks
</Trans>
</div> </div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.desc
</Trans>
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -512,7 +528,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.desc.documentation</Trans>{" "} <Trans>
ui.settingView.masksAndZonesSettings.motionMasks.desc.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -533,7 +551,11 @@ export default function MasksAndZonesView({
<LuPlus /> <LuPlus />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.motionMasks.add</Trans></TooltipContent> <TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.motionMasks.add
</Trans>
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
{allPolygons {allPolygons
@ -565,13 +587,17 @@ export default function MasksAndZonesView({
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default"> <div className="text-md cursor-default">
<Trans>ui.settingView.masksAndZonesSettings.objectMasks</Trans> <Trans>
ui.settingView.masksAndZonesSettings.objectMasks
</Trans>
</div> </div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.desc</Trans> <Trans>
ui.settingView.masksAndZonesSettings.objectMasks.desc
</Trans>
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -580,7 +606,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.documentation</Trans>{" "} <Trans>
ui.settingView.masksAndZonesSettings.objectMasks.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -601,7 +629,11 @@ export default function MasksAndZonesView({
<LuPlus /> <LuPlus />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.objectMasks.add</Trans></TooltipContent> <TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.add
</Trans>
</TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
{allPolygons {allPolygons

View File

@ -194,7 +194,9 @@ export default function MotionTunerView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.motionDetectionTuner.desc.documentation</Trans>{" "} <Trans>
ui.settingView.motionDetectionTuner.desc.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -208,7 +210,9 @@ export default function MotionTunerView({
</Label> </Label>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.motionDetectionTuner.Threshold.desc</Trans> <Trans>
ui.settingView.motionDetectionTuner.Threshold.desc
</Trans>
</p> </p>
</div> </div>
</div> </div>
@ -237,7 +241,9 @@ export default function MotionTunerView({
</Label> </Label>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.motionDetectionTuner.contourArea.desc</Trans> <Trans>
ui.settingView.motionDetectionTuner.contourArea.desc
</Trans>
</p> </p>
</div> </div>
</div> </div>
@ -262,9 +268,15 @@ export default function MotionTunerView({
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="flex flex-row items-center justify-between"> <div className="flex flex-row items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="improve-contrast"><Trans>ui.settingView.motionDetectionTuner.improveContrast</Trans></Label> <Label htmlFor="improve-contrast">
<Trans>
ui.settingView.motionDetectionTuner.improveContrast
</Trans>
</Label>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
<Trans>ui.settingView.motionDetectionTuner.improveContrast.desc</Trans> <Trans>
ui.settingView.motionDetectionTuner.improveContrast.desc
</Trans>
</div> </div>
</div> </div>
<Switch <Switch
@ -297,7 +309,9 @@ 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><Trans>ui.saving</Trans></span> <span>
<Trans>ui.saving</Trans>
</span>
</div> </div>
) : ( ) : (
<Trans>ui.save</Trans> <Trans>ui.save</Trans>

View File

@ -252,11 +252,15 @@ export default function NotificationView({
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel><Trans>ui.settingView.notification.email</Trans></FormLabel> <FormLabel>
<Trans>ui.settingView.notification.email</Trans>
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72"
placeholder={t("ui.settingView.notification.email.placeholder")} placeholder={t(
"ui.settingView.notification.email.placeholder",
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -286,7 +290,9 @@ 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><Trans>ui.saving</Trans></span> <span>
<Trans>ui.saving</Trans>
</span>
</div> </div>
) : ( ) : (
<Trans>ui.save</Trans> <Trans>ui.save</Trans>
@ -336,7 +342,9 @@ export default function NotificationView({
} }
}} }}
> >
{registration != null ? t("ui.settingView.notification.unregisterDevice") : t("ui.settingView.notification.registerDevice")} {registration != null
? t("ui.settingView.notification.unregisterDevice")
: t("ui.settingView.notification.registerDevice")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -47,7 +47,9 @@ export default function ObjectSettingsView({
info: ( info: (
<> <>
<p className="mb-2"> <p className="mb-2">
<strong><Trans>ui.settingView.debug.boundingBoxes.colors</Trans></strong> <strong>
<Trans>ui.settingView.debug.boundingBoxes.colors</Trans>
</strong>
</p> </p>
<ul className="list-disc space-y-1 pl-5"> <ul className="list-disc space-y-1 pl-5">
<Trans>ui.settingView.debug.boundingBoxes.colors.info</Trans> <Trans>ui.settingView.debug.boundingBoxes.colors.info</Trans>
@ -148,9 +150,11 @@ export default function ObjectSettingsView({
<div className="mb-5 space-y-3 text-sm text-muted-foreground"> <div className="mb-5 space-y-3 text-sm text-muted-foreground">
<p> <p>
{t("ui.settingView.debug.detectorDesc", { {t("ui.settingView.debug.detectorDesc", {
detectors: config ? Object.keys(config?.detectors) detectors: config
.map((detector) => capitalizeFirstLetter(detector)) ? Object.keys(config?.detectors)
.join(",") : "" .map((detector) => capitalizeFirstLetter(detector))
.join(",")
: "",
})} })}
</p> </p>
<p> <p>
@ -175,8 +179,12 @@ export default function ObjectSettingsView({
<Tabs defaultValue="debug" className="w-full"> <Tabs defaultValue="debug" className="w-full">
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="debug"><Trans>ui.settingView.debug.debugging</Trans></TabsTrigger> <TabsTrigger value="debug">
<TabsTrigger value="objectlist"><Trans>ui.settingView.debug.objectList</Trans></TabsTrigger> <Trans>ui.settingView.debug.debugging</Trans>
</TabsTrigger>
<TabsTrigger value="objectlist">
<Trans>ui.settingView.debug.objectList</Trans>
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="debug"> <TabsContent value="debug">
<div className="flex w-full flex-col space-y-6"> <div className="flex w-full flex-col space-y-6">
@ -329,7 +337,9 @@ function ObjectList(objects?: ObjectType[]) {
); );
}) })
) : ( ) : (
<div className="p-3 text-center"><Trans>ui.settingView.debug.noObjects</Trans></div> <div className="p-3 text-center">
<Trans>ui.settingView.debug.noObjects</Trans>
</div>
)} )}
</div> </div>
); );

View File

@ -173,7 +173,9 @@ export default function SearchSettingsView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
<Trans>ui.settingView.searchSettings.semanticSearch.readTheDocumentation</Trans> <Trans>
ui.settingView.searchSettings.semanticSearch.readTheDocumentation
</Trans>
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -192,7 +194,9 @@ export default function SearchSettingsView({
}} }}
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="enabled"><Trans>ui.enabled</Trans></Label> <Label htmlFor="enabled">
<Trans>ui.enabled</Trans>
</Label>
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@ -207,26 +211,42 @@ export default function SearchSettingsView({
}} }}
/> />
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="reindex"><Trans>ui.settingView.searchSettings.semanticSearch.reindexOnStartup</Trans></Label> <Label htmlFor="reindex">
<Trans>
ui.settingView.searchSettings.semanticSearch.reindexOnStartup
</Trans>
</Label>
</div> </div>
</div> </div>
<div className="mt-3 text-sm text-muted-foreground"> <div className="mt-3 text-sm text-muted-foreground">
<Trans>ui.settingView.searchSettings.semanticSearch.reindexOnStartup.desc</Trans> <Trans>
ui.settingView.searchSettings.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">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md"><Trans>ui.settingView.searchSettings.semanticSearch.modelSize</Trans></div> <div className="text-md">
<Trans>
ui.settingView.searchSettings.semanticSearch.modelSize
</Trans>
</div>
<div className="space-y-1 text-sm text-muted-foreground"> <div className="space-y-1 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.searchSettings.semanticSearch.modelSize.desc</Trans> <Trans>
ui.settingView.searchSettings.semanticSearch.modelSize.desc
</Trans>
</p> </p>
<ul className="list-disc pl-5 text-sm"> <ul className="list-disc pl-5 text-sm">
<li> <li>
<Trans>ui.settingView.searchSettings.semanticSearch.modelSize.small.desc</Trans> <Trans>
ui.settingView.searchSettings.semanticSearch.modelSize.small.desc
</Trans>
</li> </li>
<li> <li>
<Trans>ui.settingView.searchSettings.semanticSearch.modelSize.large.desc</Trans> <Trans>
ui.settingView.searchSettings.semanticSearch.modelSize.large.desc
</Trans>
</li> </li>
</ul> </ul>
</div> </div>
@ -240,7 +260,10 @@ export default function SearchSettingsView({
} }
> >
<SelectTrigger className="w-20"> <SelectTrigger className="w-20">
{t("ui.settingView.searchSettings.semanticSearch.modelSize." + searchSettings.model_size)} {t(
"ui.settingView.searchSettings.semanticSearch.modelSize." +
searchSettings.model_size,
)}
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
@ -250,7 +273,10 @@ export default function SearchSettingsView({
className="cursor-pointer" className="cursor-pointer"
value={size} value={size}
> >
{t("ui.settingView.searchSettings.semanticSearch.modelSize." + size)} {t(
"ui.settingView.searchSettings.semanticSearch.modelSize." +
size,
)}
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@ -274,7 +300,9 @@ export default function SearchSettingsView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span><Trans>ui.saving</Trans></span> <span>
<Trans>ui.saving</Trans>
</span>
</div> </div>
) : ( ) : (
t("ui.save") t("ui.save")

View File

@ -83,12 +83,16 @@ export default function UiSettingsView() {
onCheckedChange={setAutoLive} onCheckedChange={setAutoLive}
/> />
<Label className="cursor-pointer" htmlFor="auto-live"> <Label className="cursor-pointer" htmlFor="auto-live">
<Trans>ui.settingView.generalSettings.automaticLiveView</Trans> <Trans>
ui.settingView.generalSettings.automaticLiveView
</Trans>
</Label> </Label>
</div> </div>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.generalSettings.automaticLiveView.desc</Trans> <Trans>
ui.settingView.generalSettings.automaticLiveView.desc
</Trans>
</p> </p>
</div> </div>
</div> </div>
@ -105,7 +109,9 @@ export default function UiSettingsView() {
</div> </div>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.generalSettings.playAlertVideos.desc</Trans> <Trans>
ui.settingView.generalSettings.playAlertVideos.desc
</Trans>
</p> </p>
</div> </div>
</div> </div>
@ -114,10 +120,14 @@ export default function UiSettingsView() {
<div className="my-3 flex w-full flex-col space-y-6"> <div className="my-3 flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md"><Trans>ui.settingView.generalSettings.storedLayouts</Trans></div> <div className="text-md">
<Trans>ui.settingView.generalSettings.storedLayouts</Trans>
</div>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
<Trans>ui.settingView.generalSettings.storedLayouts.desc</Trans> <Trans>
ui.settingView.generalSettings.storedLayouts.desc
</Trans>
</p> </p>
</div> </div>
</div> </div>
@ -125,7 +135,9 @@ export default function UiSettingsView() {
aria-label="Clear all saved layouts" aria-label="Clear all saved layouts"
onClick={clearStoredLayouts} onClick={clearStoredLayouts}
> >
<Trans>ui.settingView.generalSettings.storedLayouts.clearAll</Trans> <Trans>
ui.settingView.generalSettings.storedLayouts.clearAll
</Trans>
</Button> </Button>
</div> </div>
@ -137,9 +149,17 @@ export default function UiSettingsView() {
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md"><Trans>ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate</Trans></div> <div className="text-md">
<Trans>
ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate
</Trans>
</div>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p><Trans>ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate.desc</Trans></p> <p>
<Trans>
ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate.desc
</Trans>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -172,9 +192,17 @@ export default function UiSettingsView() {
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md"><Trans>ui.settingView.generalSettings.calendar.firstWeekday</Trans></div> <div className="text-md">
<Trans>
ui.settingView.generalSettings.calendar.firstWeekday
</Trans>
</div>
<div className="my-2 text-sm text-muted-foreground"> <div className="my-2 text-sm text-muted-foreground">
<p><Trans>ui.settingView.generalSettings.calendar.firstWeekday.desc</Trans></p> <p>
<Trans>
ui.settingView.generalSettings.calendar.firstWeekday.desc
</Trans>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -183,7 +211,10 @@ export default function UiSettingsView() {
onValueChange={(value) => setWeekStartsOn(parseInt(value))} onValueChange={(value) => setWeekStartsOn(parseInt(value))}
> >
<SelectTrigger className="w-32"> <SelectTrigger className="w-32">
{t("ui.settingView.generalSettings.calendar.firstWeekday." + WEEK_STARTS_ON[weekStartsOn ?? 0].toLowerCase())} {t(
"ui.settingView.generalSettings.calendar.firstWeekday." +
WEEK_STARTS_ON[weekStartsOn ?? 0].toLowerCase(),
)}
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
@ -193,7 +224,10 @@ export default function UiSettingsView() {
className="cursor-pointer" className="cursor-pointer"
value={index.toString()} value={index.toString()}
> >
{t("ui.settingView.generalSettings.calendar.firstWeekday."+day.toLowerCase())} {t(
"ui.settingView.generalSettings.calendar.firstWeekday." +
day.toLowerCase(),
)}
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>

View File

@ -224,11 +224,15 @@ export default function CameraMetrics({
return ( return (
<div className="scrollbar-container mt-4 flex size-full flex-col gap-3 overflow-y-auto"> <div className="scrollbar-container mt-4 flex size-full flex-col gap-3 overflow-y-auto">
<div className="text-sm font-medium text-muted-foreground"><Trans>ui.system.cameras.overview</Trans></div> <div className="text-sm font-medium text-muted-foreground">
<Trans>ui.system.cameras.overview</Trans>
</div>
<div className="grid grid-cols-1 md:grid-cols-3"> <div className="grid grid-cols-1 md:grid-cols-3">
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.cameras.framesAndDetections</Trans></div> <div className="mb-5">
<Trans>ui.system.cameras.framesAndDetections</Trans>
</div>
<CameraLineGraph <CameraLineGraph
graphId="overall-stats" graphId="overall-stats"
unit="" unit=""
@ -295,7 +299,9 @@ export default function CameraMetrics({
)} )}
{Object.keys(cameraFpsSeries).includes(camera.name) ? ( {Object.keys(cameraFpsSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.cameras.framesAndDetections</Trans></div> <div className="mb-5">
<Trans>ui.system.cameras.framesAndDetections</Trans>
</div>
<CameraLineGraph <CameraLineGraph
graphId={`${camera.name}-dps`} graphId={`${camera.name}-dps`}
unit="" unit=""

View File

@ -459,7 +459,9 @@ export default function GeneralMetrics({
> >
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.detectorInferenceSpeed</Trans></div> <div className="mb-5">
<Trans>ui.system.general.detectorInferenceSpeed</Trans>
</div>
{detInferenceTimeSeries.map((series) => ( {detInferenceTimeSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -497,7 +499,9 @@ export default function GeneralMetrics({
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.detectorCpuUsage</Trans></div> <div className="mb-5">
<Trans>ui.system.general.detectorCpuUsage</Trans>
</div>
{detCpuSeries.map((series) => ( {detCpuSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -515,7 +519,9 @@ export default function GeneralMetrics({
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.detectorMemoryUsage</Trans></div> <div className="mb-5">
<Trans>ui.system.general.detectorMemoryUsage</Trans>
</div>
{detMemSeries.map((series) => ( {detMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -558,7 +564,9 @@ export default function GeneralMetrics({
> >
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.gpuUsage</Trans></div> <div className="mb-5">
<Trans>ui.system.general.gpuUsage</Trans>
</div>
{gpuSeries.map((series) => ( {gpuSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -578,7 +586,9 @@ export default function GeneralMetrics({
<> <>
{gpuMemSeries && ( {gpuMemSeries && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.gpuMemroy</Trans></div> <div className="mb-5">
<Trans>ui.system.general.gpuMemroy</Trans>
</div>
{gpuMemSeries.map((series) => ( {gpuMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -600,7 +610,9 @@ export default function GeneralMetrics({
<> <>
{gpuEncSeries && gpuEncSeries?.length != 0 && ( {gpuEncSeries && gpuEncSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.gpuEncoder</Trans></div> <div className="mb-5">
<Trans>ui.system.general.gpuEncoder</Trans>
</div>
{gpuEncSeries.map((series) => ( {gpuEncSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -622,7 +634,9 @@ export default function GeneralMetrics({
<> <>
{gpuDecSeries && gpuDecSeries?.length != 0 && ( {gpuDecSeries && gpuDecSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.gpuDecoder</Trans></div> <div className="mb-5">
<Trans>ui.system.general.gpuDecoder</Trans>
</div>
{gpuDecSeries.map((series) => ( {gpuDecSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
@ -670,7 +684,9 @@ export default function GeneralMetrics({
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"><Trans>ui.system.general.processMemoryUsage</Trans></div> <div className="mb-5">
<Trans>ui.system.general.processMemoryUsage</Trans>
</div>
{otherProcessMemSeries.map((series) => ( {otherProcessMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}

View File

@ -51,7 +51,9 @@ export default function StorageMetrics({
return ( return (
<div className="scrollbar-container mt-4 flex size-full flex-col overflow-y-auto"> <div className="scrollbar-container mt-4 flex size-full flex-col overflow-y-auto">
<div className="text-sm font-medium text-muted-foreground"><Trans>ui.system.storage.overview</Trans></div> <div className="text-sm font-medium text-muted-foreground">
<Trans>ui.system.storage.overview</Trans>
</div>
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3"> <div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
<div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5 flex flex-row items-center justify-between"> <div className="mb-5 flex flex-row items-center justify-between">