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"
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">
{lastValues[labelIdx]}
{unit}

View File

@ -178,10 +178,20 @@ export function CombinedStorageGraph({
<Table>
<TableHeader>
<TableRow>
<TableHead><Trans>ui.system.storage.cameraStorage.camera</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>
<TableHead>
<Trans>ui.system.storage.cameraStorage.camera</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>
</TableHeader>
<TableBody>
@ -193,7 +203,9 @@ export function CombinedStorageGraph({
className="size-3 rounded-md"
style={{ backgroundColor: item.color }}
></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" && (
<Popover>
<PopoverTrigger asChild>
@ -209,7 +221,9 @@ export function CombinedStorageGraph({
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="space-y-2">
<Trans>ui.system.storage.cameraStorage.unused.tips</Trans>
<Trans>
ui.system.storage.cameraStorage.unused.tips
</Trans>
</div>
</PopoverContent>
</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">
<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>
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
<MenuItem
@ -78,7 +80,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
>
<a className="flex" href={logoutUrl}>
<LuLogOut className="mr-2 size-4" />
<span><Trans>ui.menu.user.logout</Trans></span>
<span>
<Trans>ui.menu.user.logout</Trans>
</span>
</a>
</MenuItem>
</div>

View File

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

View File

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

View File

@ -74,7 +74,9 @@ export default function CameraInfoDialog({
<DialogContent>
<DialogHeader>
<DialogTitle className="capitalize">
{t("ui.system.cameras.info.cameraProbeInfo", {camera: camera.name.replaceAll("_", " ")})}
{t("ui.system.cameras.info.cameraProbeInfo", {
camera: camera.name.replaceAll("_", " "),
})}
</DialogTitle>
</DialogHeader>
<DialogDescription>
@ -87,7 +89,7 @@ export default function CameraInfoDialog({
{ffprobeInfo.map((stream, idx) => (
<div key={idx} className="mb-5">
<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>
{stream.return_code == 0 ? (
<div>
@ -95,7 +97,9 @@ export default function CameraInfoDialog({
<div className="" key={idx}>
{codec.width ? (
<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>
<Trans>ui.system.cameras.info.codec</Trans>
@ -107,7 +111,9 @@ export default function CameraInfoDialog({
<div>
{codec.width && codec.height ? (
<>
<Trans>ui.system.cameras.info.resolution</Trans>{" "}
<Trans>
ui.system.cameras.info.resolution
</Trans>{" "}
<span className="text-primary">
{" "}
{codec.width}x{codec.height} (
@ -121,7 +127,9 @@ export default function CameraInfoDialog({
</>
) : (
<span>
<Trans>ui.system.cameras.info.resolution</Trans>{" "}
<Trans>
ui.system.cameras.info.resolution
</Trans>{" "}
<span className="text-primary">
Unknown
</span>
@ -154,7 +162,11 @@ export default function CameraInfoDialog({
</div>
) : (
<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>
@ -163,7 +175,9 @@ export default function CameraInfoDialog({
) : (
<div className="flex flex-col items-center">
<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>

View File

@ -64,7 +64,9 @@ export default function CreateUserDialog({
<Dialog open={show} onOpenChange={onCancel}>
<DialogContent>
<DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.createUser</Trans></DialogTitle>
<DialogTitle>
<Trans>ui.settingView.users.dialog.createUser</Trans>
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
@ -72,7 +74,9 @@ export default function CreateUserDialog({
name="user"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.users.dialog.createUser.user</Trans></FormLabel>
<FormLabel>
<Trans>ui.settingView.users.dialog.createUser.user</Trans>
</FormLabel>
<FormControl>
<Input
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"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.users.dialog.createUser.password</Trans></FormLabel>
<FormLabel>
<Trans>
ui.settingView.users.dialog.createUser.password
</Trans>
</FormLabel>
<FormControl>
<Input
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}>
<DialogContent>
<DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.deleteUser</Trans></DialogTitle>
<DialogTitle>
<Trans>ui.settingView.users.dialog.deleteUser</Trans>
</DialogTitle>
</DialogHeader>
<div><Trans>ui.settingView.users.dialog.deleteUser.warn</Trans></div>
<div>
<Trans>ui.settingView.users.dialog.deleteUser.warn</Trans>
</div>
<DialogFooter>
<Button
className="flex items-center gap-1"

View File

@ -26,7 +26,9 @@ export default function SetPasswordDialog({
<Dialog open={show} onOpenChange={onCancel}>
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader>
<DialogTitle><Trans>ui.settingView.users.dialog.setPassword</Trans></DialogTitle>
<DialogTitle>
<Trans>ui.settingView.users.dialog.setPassword</Trans>
</DialogTitle>
</DialogHeader>
<Input
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>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel><Trans>ui.cancel</Trans></AlertDialogCancel>
<AlertDialogCancel>
<Trans>ui.cancel</Trans>
</AlertDialogCancel>
<AlertDialogAction onClick={handleRestart}>
<Trans>ui.dialog.restart.button</Trans>
</AlertDialogAction>
@ -105,7 +107,9 @@ export default function RestartDialog({
<Trans>ui.dialog.restart.restarting.title</Trans>
</SheetTitle>
<SheetDescription className="text-center">
<div>{t("ui.dialog.restart.restarting.content", {countdown})}</div>
<div>
{t("ui.dialog.restart.restarting.content", { countdown })}
</div>
</SheetDescription>
</SheetHeader>
<Button

View File

@ -215,11 +215,15 @@ export default function MotionMaskEditPane({
<>
<Toaster position="top-center" closeButton={true} />
<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>
<div className="my-3 space-y-3 text-sm text-muted-foreground">
<p>
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.context</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.motionMasks.context
</Trans>
</p>
<div className="flex items-center text-primary">
@ -229,7 +233,9 @@ export default function MotionMaskEditPane({
rel="noopener noreferrer"
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" />
</Link>
</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-1 inline-flex">
{t("ui.settingView.masksAndZonesSettings.motionMasks.point", {
count: polygons[activePolygonIndex].points.length
count: polygons[activePolygonIndex].points.length,
})}
{polygons[activePolygonIndex].isFinished && (
<FaCheckCircle className="ml-2 size-5" />
@ -253,7 +259,10 @@ export default function MotionMaskEditPane({
</div>
)}
<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>
<Separator className="my-3 bg-secondary" />
@ -261,19 +270,26 @@ export default function MotionMaskEditPane({
{polygonArea && polygonArea >= 0.35 && (
<>
<div className="mb-3 text-sm text-danger">
{t("ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge", {
polygonArea: Math.round(polygonArea * 100)
})}
{t(
"ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge",
{
polygonArea: Math.round(polygonArea * 100),
},
)}
</div>
<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
to="https://github.com/blakeblackshear/frigate/discussions/13040"
target="_blank"
rel="noopener noreferrer"
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" />
</Link>
</div>
@ -322,7 +338,9 @@ export default function MotionMaskEditPane({
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span><Trans>ui.saving</Trans></span>
<span>
<Trans>ui.saving</Trans>
</span>
</div>
) : (
<Trans>ui.save</Trans>

View File

@ -249,11 +249,15 @@ export default function ObjectMaskEditPane({
<>
<Toaster position="top-center" closeButton={true} />
<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>
<div className="my-2 text-sm text-muted-foreground">
<p>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.context</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.context
</Trans>
</p>
</div>
<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-1 inline-flex">
{t("ui.settingView.masksAndZonesSettings.objectMasks.point", {
count: polygons[activePolygonIndex].points.length
count: polygons[activePolygonIndex].points.length,
})}
{polygons[activePolygonIndex].isFinished && (
<FaCheckCircle className="ml-2 size-5" />
@ -275,7 +279,9 @@ export default function ObjectMaskEditPane({
</div>
)}
<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.
</div>
@ -301,7 +307,11 @@ export default function ObjectMaskEditPane({
name="objects"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.objectMasks.objects</Trans></FormLabel>
<FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.objects
</Trans>
</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
@ -317,7 +327,9 @@ export default function ObjectMaskEditPane({
</SelectContent>
</Select>
<FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.objects.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.objects.desc
</Trans>
</FormDescription>
<FormMessage />
</FormItem>
@ -414,7 +426,11 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) {
return (
<>
<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" />
{allLabels.map((item) => (
<SelectItem key={item} value={item}>

View File

@ -332,7 +332,9 @@ export default function ZoneEditPane({
<>
<Toaster position="top-center" closeButton={true} />
<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>
<div className="my-2 text-sm text-muted-foreground">
<p>
@ -343,7 +345,9 @@ export default function ZoneEditPane({
{polygons && activePolygonIndex !== undefined && (
<div className="my-2 flex w-full flex-row justify-between text-sm">
<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 && (
<FaCheckCircle className="ml-2 size-5" />
@ -357,7 +361,9 @@ export default function ZoneEditPane({
</div>
)}
<div className="mb-3 text-sm text-muted-foreground">
<Trans>ui.settingView.masksAndZonesSettings.zone.clickDrawPolygon</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.clickDrawPolygon
</Trans>
</div>
<Separator className="my-3 bg-secondary" />
@ -369,16 +375,22 @@ export default function ZoneEditPane({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.name</Trans></FormLabel>
<FormLabel>
<Trans>ui.settingView.masksAndZonesSettings.zone.name</Trans>
</FormLabel>
<FormControl>
<Input
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}
/>
</FormControl>
<FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.name.tips</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.name.tips
</Trans>
</FormDescription>
<FormMessage />
</FormItem>
@ -390,7 +402,11 @@ export default function ZoneEditPane({
name="inertia"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.inertia</Trans></FormLabel>
<FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.zone.inertia
</Trans>
</FormLabel>
<FormControl>
<Input
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>
<FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.inertia.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.inertia.desc
</Trans>
</FormDescription>
<FormMessage />
</FormItem>
@ -411,7 +429,11 @@ export default function ZoneEditPane({
name="loitering_time"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.loiteringTime</Trans></FormLabel>
<FormLabel>
<Trans>
ui.settingView.masksAndZonesSettings.zone.loiteringTime
</Trans>
</FormLabel>
<FormControl>
<Input
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>
<FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.loiteringTime.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.loiteringTime.desc
</Trans>
</FormDescription>
<FormMessage />
</FormItem>
@ -428,9 +452,13 @@ export default function ZoneEditPane({
/>
<Separator className="my-2 flex bg-secondary" />
<FormItem>
<FormLabel><Trans>ui.settingView.masksAndZonesSettings.zone.objects</Trans></FormLabel>
<FormLabel>
<Trans>ui.settingView.masksAndZonesSettings.zone.objects</Trans>
</FormLabel>
<FormDescription>
<Trans>ui.settingView.masksAndZonesSettings.zone.objects.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.objects.desc
</Trans>
</FormDescription>
<ZoneObjectSelector
camera={polygon.camera}

View File

@ -1,5 +1,5 @@
import { createContext, useContext, useState, useEffect, useMemo } from 'react';
import i18next from 'i18next';
import { createContext, useContext, useState, useEffect, useMemo } from "react";
import i18next from "i18next";
type LanguageProviderState = {
language: string;
@ -8,17 +8,18 @@ type LanguageProviderState = {
};
const initialState: LanguageProviderState = {
language: i18next.language || 'en',
systemLanguage: 'en',
language: i18next.language || "en",
systemLanguage: "en",
setLanguage: () => null,
};
const LanguageProviderContext = createContext<LanguageProviderState>(initialState);
const LanguageProviderContext =
createContext<LanguageProviderState>(initialState);
export function LanguageProvider({
children,
defaultLanguage = 'en',
storageKey = 'frigate-ui-language',
defaultLanguage = "en",
storageKey = "frigate-ui-language",
...props
}: {
children: React.ReactNode;
@ -27,27 +28,26 @@ export function LanguageProvider({
}) {
const [language, setLanguage] = useState<string>(() => {
try {
const storedData = localStorage.getItem(storageKey);
const newLanguage = storedData || defaultLanguage;
i18next.changeLanguage(newLanguage);
return newLanguage;
} catch (error) {
// 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;
}
});
const systemLanguage = useMemo<string | undefined>(() => {
if (typeof window === 'undefined') return undefined;
if (typeof window === "undefined") return undefined;
return window.navigator.language;
}, []);
useEffect(() => {
if (language === systemLanguage) return;
i18next.changeLanguage(language);
}, [language]);
}, [language, systemLanguage]);
const value = {
language,

View File

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

View File

@ -23,7 +23,7 @@ export const colorSchemes: ColorScheme[] = [
// eslint-disable-next-line react-refresh/only-export-components
export const friendlyColorSchemeName = (className: string): string => {
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 = {
@ -125,7 +125,7 @@ export function ThemeProvider({
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}

View File

@ -72,6 +72,6 @@ export default function useNavigation(
enabled: isDesktop && config?.face_recognition.enabled,
},
] 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) {
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",
relevantLink: "/system#cameras",
});
@ -81,7 +84,10 @@ export default function useStats(stats: FrigateStats | undefined) {
if (!isNaN(detectAvg) && detectAvg >= CameraDetectThreshold.error) {
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",
relevantLink: "/system#cameras",
});

View File

@ -200,7 +200,9 @@ function ConfigEditor() {
onClick={() => handleCopyConfig()}
>
<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
size="sm"
@ -212,7 +214,9 @@ function ConfigEditor() {
<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" />
</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
size="sm"
@ -221,7 +225,9 @@ function ConfigEditor() {
onClick={() => onHandleSaveConfig("saveonly")}
>
<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>
</div>
</div>

View File

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

View File

@ -71,7 +71,9 @@ function System() {
{item == "general" && <LuActivity className="size-4" />}
{item == "storage" && <LuHardDrive 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>
))}
</ToggleGroup>
@ -79,7 +81,8 @@ function System() {
<div className="flex h-full items-center">
{lastUpdated && (
<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>

View File

@ -12,16 +12,24 @@ i18n
// if you're using a language detector, do not define the lng option
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json"
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
react: {
transSupportBasicHtmlNodes: true,
transKeepBasicHtmlNodesFor: ["br", "strong", "i", "em", "li", "p", "code"],
transKeepBasicHtmlNodesFor: [
"br",
"strong",
"i",
"em",
"li",
"p",
"code",
],
},
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)}
>
<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
className="flex items-center gap-2.5 rounded-lg"
@ -385,7 +389,11 @@ export default function LiveCameraView({
}}
>
<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>
</div>
) : (
@ -849,7 +857,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"}
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
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")}
/>
<CameraFeatureToggle
@ -857,7 +869,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"}
Icon={recordState == "ON" ? LuVideo : LuVideoOff}
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")}
/>
<CameraFeatureToggle
@ -865,7 +881,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"}
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
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")}
/>
{audioDetectEnabled && (
@ -874,7 +894,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"}
Icon={audioState == "ON" ? LuEar : LuEarOff}
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")}
/>
)}
@ -884,7 +908,11 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"}
Icon={autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff}
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={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
}

View File

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

View File

@ -253,7 +253,9 @@ export default function CameraSettingsView({
<div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
<p>
<Trans>ui.settingView.cameraSettings.reviewClassification.desc</Trans>
<Trans>
ui.settingView.cameraSettings.reviewClassification.desc
</Trans>
</p>
<div className="flex items-center text-primary">
<Link
@ -262,7 +264,9 @@ export default function CameraSettingsView({
rel="noopener noreferrer"
className="inline"
>
<Trans>ui.settingView.cameraSettings.reviewClassification.readTheDocumentation</Trans>{" "}
<Trans>
ui.settingView.cameraSettings.reviewClassification.readTheDocumentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
@ -295,7 +299,9 @@ export default function CameraSettingsView({
<MdCircle className="ml-3 size-2 text-severity_alert" />
</FormLabel>
<FormDescription>
<Trans>ui.settingView.cameraSettings.reviewClassification.selectAlertsZones</Trans>
<Trans>
ui.settingView.cameraSettings.reviewClassification.selectAlertsZones
</Trans>
</FormDescription>
</div>
<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">
<Trans>ui.settingView.cameraSettings.reviewClassification.noDefinedZones</Trans>
<Trans>
ui.settingView.cameraSettings.reviewClassification.noDefinedZones
</Trans>
</div>
)}
<FormMessage />
<div className="text-sm">
{watchedAlertsZones && watchedAlertsZones.length > 0
?
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectAlertsTips", { 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("_", " ") })
}
? t(
"ui.settingView.cameraSettings.reviewClassification.zoneObjectAlertsTips",
{
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>
</FormItem>
)}
@ -447,35 +476,50 @@ export default function CameraSettingsView({
<div className="text-sm">
{watchedDetectionsZones &&
watchedDetectionsZones.length > 0
?
!selectDetections ?
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips", {
? !selectDetections
? t(
"ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips",
{
detectionsLabels,
zone: watchedDetectionsZones.map((zone) =>
capitalizeFirstLetter(zone).replaceAll("_", " "),
).join(", "),
zone: watchedDetectionsZones
.map((zone) =>
capitalizeFirstLetter(zone).replaceAll(
"_",
" ",
),
)
.join(", "),
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
})
:
t("ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips.notSelectDetections",{
},
)
: t(
"ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips.notSelectDetections",
{
detectionsLabels,
zone: watchedDetectionsZones.map((zone) =>
capitalizeFirstLetter(zone).replaceAll("_", " "),
).join(", "),
zone: watchedDetectionsZones
.map((zone) =>
capitalizeFirstLetter(zone).replaceAll(
"_",
" ",
),
)
.join(", "),
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
})
:
t("ui.settingView.cameraSettings.reviewClassification.objectDetectionsTips", {
},
)
: t(
"ui.settingView.cameraSettings.reviewClassification.objectDetectionsTips",
{
detectionsLabels,
cameraName: capitalizeFirstLetter(
cameraConfig?.name ?? "",
).replaceAll("_", " "),
})
}
},
)}
</div>
</FormItem>
)}
@ -502,7 +546,9 @@ export default function CameraSettingsView({
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span><Trans>ui.saving</Trans></span>
<span>
<Trans>ui.saving</Trans>
</span>
</div>
) : (
<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">
<HoverCard>
<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>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
<Trans>ui.settingView.masksAndZonesSettings.zone.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.zone.desc
</Trans>
</p>
<div className="flex items-center text-primary">
<Link
@ -446,7 +452,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
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" />
</Link>
</div>
@ -467,7 +475,11 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.zone.add</Trans></TooltipContent>
<TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.zone.add
</Trans>
</TooltipContent>
</Tooltip>
</div>
{allPolygons
@ -497,13 +509,17 @@ export default function MasksAndZonesView({
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
<Trans>ui.settingView.masksAndZonesSettings.motionMasks</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.motionMasks
</Trans>
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
<Trans>ui.settingView.masksAndZonesSettings.motionMasks.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.motionMasks.desc
</Trans>
</p>
<div className="flex items-center text-primary">
<Link
@ -512,7 +528,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
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" />
</Link>
</div>
@ -533,7 +551,11 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.motionMasks.add</Trans></TooltipContent>
<TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.motionMasks.add
</Trans>
</TooltipContent>
</Tooltip>
</div>
{allPolygons
@ -565,13 +587,17 @@ export default function MasksAndZonesView({
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
<Trans>ui.settingView.masksAndZonesSettings.objectMasks</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks
</Trans>
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.desc</Trans>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.desc
</Trans>
</p>
<div className="flex items-center text-primary">
<Link
@ -580,7 +606,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
className="inline"
>
<Trans>ui.settingView.masksAndZonesSettings.objectMasks.documentation</Trans>{" "}
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.documentation
</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
@ -601,7 +629,11 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent><Trans>ui.settingView.masksAndZonesSettings.objectMasks.add</Trans></TooltipContent>
<TooltipContent>
<Trans>
ui.settingView.masksAndZonesSettings.objectMasks.add
</Trans>
</TooltipContent>
</Tooltip>
</div>
{allPolygons

View File

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

View File

@ -252,11 +252,15 @@ export default function NotificationView({
name="email"
render={({ field }) => (
<FormItem>
<FormLabel><Trans>ui.settingView.notification.email</Trans></FormLabel>
<FormLabel>
<Trans>ui.settingView.notification.email</Trans>
</FormLabel>
<FormControl>
<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"
placeholder={t("ui.settingView.notification.email.placeholder")}
placeholder={t(
"ui.settingView.notification.email.placeholder",
)}
{...field}
/>
</FormControl>
@ -286,7 +290,9 @@ export default function NotificationView({
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span><Trans>ui.saving</Trans></span>
<span>
<Trans>ui.saving</Trans>
</span>
</div>
) : (
<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>
</div>
</div>

View File

@ -47,7 +47,9 @@ export default function ObjectSettingsView({
info: (
<>
<p className="mb-2">
<strong><Trans>ui.settingView.debug.boundingBoxes.colors</Trans></strong>
<strong>
<Trans>ui.settingView.debug.boundingBoxes.colors</Trans>
</strong>
</p>
<ul className="list-disc space-y-1 pl-5">
<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">
<p>
{t("ui.settingView.debug.detectorDesc", {
detectors: config ? Object.keys(config?.detectors)
detectors: config
? Object.keys(config?.detectors)
.map((detector) => capitalizeFirstLetter(detector))
.join(",") : ""
.join(",")
: "",
})}
</p>
<p>
@ -175,8 +179,12 @@ export default function ObjectSettingsView({
<Tabs defaultValue="debug" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="debug"><Trans>ui.settingView.debug.debugging</Trans></TabsTrigger>
<TabsTrigger value="objectlist"><Trans>ui.settingView.debug.objectList</Trans></TabsTrigger>
<TabsTrigger value="debug">
<Trans>ui.settingView.debug.debugging</Trans>
</TabsTrigger>
<TabsTrigger value="objectlist">
<Trans>ui.settingView.debug.objectList</Trans>
</TabsTrigger>
</TabsList>
<TabsContent value="debug">
<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>
);

View File

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

View File

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

View File

@ -224,11 +224,15 @@ export default function CameraMetrics({
return (
<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">
{statsHistory.length != 0 ? (
<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
graphId="overall-stats"
unit=""
@ -295,7 +299,9 @@ export default function CameraMetrics({
)}
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
<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
graphId={`${camera.name}-dps`}
unit=""

View File

@ -459,7 +459,9 @@ export default function GeneralMetrics({
>
{statsHistory.length != 0 ? (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -497,7 +499,9 @@ export default function GeneralMetrics({
)}
{statsHistory.length != 0 ? (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -515,7 +519,9 @@ export default function GeneralMetrics({
)}
{statsHistory.length != 0 ? (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -558,7 +564,9 @@ export default function GeneralMetrics({
>
{statsHistory.length != 0 ? (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -578,7 +586,9 @@ export default function GeneralMetrics({
<>
{gpuMemSeries && (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -600,7 +610,9 @@ export default function GeneralMetrics({
<>
{gpuEncSeries && gpuEncSeries?.length != 0 && (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -622,7 +634,9 @@ export default function GeneralMetrics({
<>
{gpuDecSeries && gpuDecSeries?.length != 0 && (
<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) => (
<ThresholdBarGraph
key={series.name}
@ -670,7 +684,9 @@ export default function GeneralMetrics({
)}
{statsHistory.length != 0 ? (
<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) => (
<ThresholdBarGraph
key={series.name}

View File

@ -51,7 +51,9 @@ export default function StorageMetrics({
return (
<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="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5 flex flex-row items-center justify-between">