fix missing i18n keys (#18309)

This commit is contained in:
Josh Hawkins 2025-05-19 16:45:02 -05:00 committed by GitHub
parent 8a143b4284
commit afe513336c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 103 additions and 46 deletions

View File

@ -78,6 +78,10 @@
"speed": { "speed": {
"mph": "mph", "mph": "mph",
"kph": "kph" "kph": "kph"
},
"length": {
"feet": "feet",
"meters": "meters"
} }
}, },
"label": { "label": {

View File

@ -39,8 +39,11 @@
"document": "Read the documentation " "document": "Read the documentation "
} }
}, },
"stream": "Stream",
"placeholder": "Choose a stream",
"streamMethod": { "streamMethod": {
"label": "Streaming Method", "label": "Streaming Method",
"placeholder": "Choose a streaming method",
"method": { "method": {
"noStreaming": { "noStreaming": {
"label": "No Streaming", "label": "No Streaming",

View File

@ -4,6 +4,7 @@
"copyConfig": "Copy Config", "copyConfig": "Copy Config",
"saveAndRestart": "Save & Restart", "saveAndRestart": "Save & Restart",
"saveOnly": "Save Only", "saveOnly": "Save Only",
"confirm": "Exit without saving?",
"toast": { "toast": {
"success": { "success": {
"copyToClipboard": "Config copied to clipboard." "copyToClipboard": "Config copied to clipboard."

View File

@ -75,7 +75,10 @@
"desc": "This data comes from your camera's detect feed but is overlayed on images from the the record feed. It is unlikely that the two streams are perfectly in sync. As a result, the bounding box and the footage will not line up perfectly. However, the <code>annotation_offset</code> field can be used to adjust this.", "desc": "This data comes from your camera's detect feed but is overlayed on images from the the record feed. It is unlikely that the two streams are perfectly in sync. As a result, the bounding box and the footage will not line up perfectly. However, the <code>annotation_offset</code> field can be used to adjust this.",
"documentation": "Read the documentation ", "documentation": "Read the documentation ",
"millisecondsToOffset": "Milliseconds to offset detect annotations by. <em>Default: 0</em>", "millisecondsToOffset": "Milliseconds to offset detect annotations by. <em>Default: 0</em>",
"tips": "TIP: Imagine there is an event clip with a person walking from left to right. If the event timeline bounding box is consistently to the left of the person then the value should be decreased. Similarly, if a person is walking from left to right and the bounding box is consistently ahead of the person then the value should be increased." "tips": "TIP: Imagine there is an event clip with a person walking from left to right. If the event timeline bounding box is consistently to the left of the person then the value should be decreased. Similarly, if a person is walking from left to right and the bounding box is consistently ahead of the person then the value should be increased.",
"toast": {
"success": "Annotation offset for {{camera}} has been saved to the config file. Restart Frigate to apply your changes."
}
} }
}, },
"carousel": { "carousel": {

View File

@ -219,6 +219,11 @@
"mustBeGreaterOrEqualZero": "Loitering time must be greater than or equal to 0." "mustBeGreaterOrEqualZero": "Loitering time must be greater than or equal to 0."
} }
}, },
"speed": {
"error": {
"mustBeGreaterOrEqualTo": "Speed threshold must greater than or equal to 0.1."
}
},
"polygonDrawing": { "polygonDrawing": {
"removeLastPoint": "Remove last point", "removeLastPoint": "Remove last point",
"reset": { "reset": {
@ -271,7 +276,11 @@
"speedEstimation": { "speedEstimation": {
"title": "Speed Estimation", "title": "Speed Estimation",
"desc": "Enable speed estimation for objects in this zone. The zone must have exactly 4 points.", "desc": "Enable speed estimation for objects in this zone. The zone must have exactly 4 points.",
"docs": "Read the documentation" "docs": "Read the documentation",
"lineADistance": "Line A distance ({{unit}})",
"lineBDistance": "Line B distance ({{unit}})",
"lineCDistance": "Line C distance ({{unit}})",
"lineDDistance": "Line D distance ({{unit}})"
}, },
"speedThreshold": { "speedThreshold": {
"title": "Speed Threshold ({{unit}})", "title": "Speed Threshold ({{unit}})",
@ -473,12 +482,14 @@
"placeholder": "Re-enter new password" "placeholder": "Re-enter new password"
} }
}, },
"usernameIsRequired": "Username is required" "usernameIsRequired": "Username is required",
"passwordIsRequired": "Password is required"
}, },
"createUser": { "createUser": {
"title": "Create New User", "title": "Create New User",
"desc": "Add a new user account and specify an role for access to areas of the Frigate UI.", "desc": "Add a new user account and specify an role for access to areas of the Frigate UI.",
"usernameOnlyInclude": "Username may only include letters, numbers, . or _" "usernameOnlyInclude": "Username may only include letters, numbers, . or _",
"confirmPassword": "Please confirm your password"
}, },
"deleteUser": { "deleteUser": {
"title": "Delete User", "title": "Delete User",
@ -486,12 +497,15 @@
"warn": "Are you sure you want to delete <strong>{{username}}</strong>?" "warn": "Are you sure you want to delete <strong>{{username}}</strong>?"
}, },
"passwordSetting": { "passwordSetting": {
"cannotBeEmpty": "Password cannot be empty",
"doNotMatch": "Passwords do not match",
"updatePassword": "Update Password for {{username}}", "updatePassword": "Update Password for {{username}}",
"setPassword": "Set Password", "setPassword": "Set Password",
"desc": "Create a strong password to secure this account." "desc": "Create a strong password to secure this account."
}, },
"changeRole": { "changeRole": {
"title": "Change User Role", "title": "Change User Role",
"select": "Select a role",
"desc": "Update permissions for <strong>{{username}}</strong>", "desc": "Update permissions for <strong>{{username}}</strong>",
"roleInfo": { "roleInfo": {
"intro": "Select the appropriate role for this user:", "intro": "Select the appropriate role for this user:",

View File

@ -108,6 +108,7 @@
"title": "Cameras", "title": "Cameras",
"overview": "Overview", "overview": "Overview",
"info": { "info": {
"aspectRatio": "aspect ratio",
"cameraProbeInfo": "{{camera}} Camera Probe Info", "cameraProbeInfo": "{{camera}} Camera Probe Info",
"streamDataFromFFPROBE": "Stream data is obtained with <code>ffprobe</code>.", "streamDataFromFFPROBE": "Stream data is obtained with <code>ffprobe</code>.",
"fetching": "Fetching Camera Data", "fetching": "Fetching Camera Data",

View File

@ -132,14 +132,14 @@ export default function CameraInfoDialog({
/ /
{codec.height / {codec.height /
gcd(codec.width, codec.height)}{" "} gcd(codec.width, codec.height)}{" "}
aspect ratio) {t("cameras.info.aspectRatio")})
</span> </span>
</> </>
) : ( ) : (
<span> <span>
{t("cameras.info.resolution")}{" "} {t("cameras.info.resolution")}{" "}
<span className="text-primary"> <span className="text-primary">
Unknown t("cameras.info.unknown")
</span> </span>
</span> </span>
)} )}

View File

@ -56,8 +56,10 @@ export default function CreateUserDialog({
.regex(/^[A-Za-z0-9._]+$/, { .regex(/^[A-Za-z0-9._]+$/, {
message: t("users.dialog.createUser.usernameOnlyInclude"), message: t("users.dialog.createUser.usernameOnlyInclude"),
}), }),
password: z.string().min(1, "Password is required"), password: z.string().min(1, t("users.dialog.form.passwordIsRequired")),
confirmPassword: z.string().min(1, "Please confirm your password"), confirmPassword: z
.string()
.min(1, t("users.dialog.createUser.confirmPassword")),
role: z.enum(["admin", "viewer"]), role: z.enum(["admin", "viewer"]),
}) })
.refine((data) => data.password === data.confirmPassword, { .refine((data) => data.password === data.confirmPassword, {

View File

@ -79,8 +79,8 @@ export default function FaceSelectionDialog({
> >
{isMobile && ( {isMobile && (
<DrawerHeader className="sr-only"> <DrawerHeader className="sr-only">
<DrawerTitle>Log Details</DrawerTitle> <DrawerTitle>Details</DrawerTitle>
<DrawerDescription>Log details</DrawerDescription> <DrawerDescription>Details</DrawerDescription>
</DrawerHeader> </DrawerHeader>
)} )}
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel> <DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>

View File

@ -4,6 +4,7 @@ import { Button } from "../ui/button";
import { FaFlag } from "react-icons/fa"; import { FaFlag } from "react-icons/fa";
import { TimelineType } from "@/types/timeline"; import { TimelineType } from "@/types/timeline";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next";
type MobileTimelineDrawerProps = { type MobileTimelineDrawerProps = {
selected: TimelineType; selected: TimelineType;
@ -13,6 +14,7 @@ export default function MobileTimelineDrawer({
selected, selected,
onSelect, onSelect,
}: MobileTimelineDrawerProps) { }: MobileTimelineDrawerProps) {
const { t } = useTranslation(["views/events"]);
const [drawer, setDrawer] = useState(false); const [drawer, setDrawer] = useState(false);
if (!isMobile) { if (!isMobile) {
@ -38,7 +40,7 @@ export default function MobileTimelineDrawer({
setDrawer(false); setDrawer(false);
}} }}
> >
Timeline {t("timeline")}
</div> </div>
<div <div
className={`mx-4 w-full py-2 text-center smart-capitalize ${selected == "events" ? "rounded-lg bg-secondary" : ""}`} className={`mx-4 w-full py-2 text-center smart-capitalize ${selected == "events" ? "rounded-lg bg-secondary" : ""}`}
@ -47,7 +49,7 @@ export default function MobileTimelineDrawer({
setDrawer(false); setDrawer(false);
}} }}
> >
Events {t("events.label")}
</div> </div>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>

View File

@ -83,7 +83,7 @@ export default function RoleChangeDialog({
} }
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select a role" /> <SelectValue placeholder={t("users.dialog.changeRole.select")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="admin" className="flex items-center gap-2"> <SelectItem value="admin" className="flex items-center gap-2">

View File

@ -66,12 +66,12 @@ export default function SetPasswordDialog({
const handleSave = () => { const handleSave = () => {
if (!password) { if (!password) {
setError("Password cannot be empty"); setError(t("users.dialog.passwordSetting.cannotBeEmpty"));
return; return;
} }
if (password !== confirmPassword) { if (password !== confirmPassword) {
setError("Passwords do not match"); setError(t("users.dialog.passwordSetting.doNotMatch"));
return; return;
} }

View File

@ -77,7 +77,9 @@ export function AnnotationSettingsPane({
.then((res) => { .then((res) => {
if (res.status === 200) { if (res.status === 200) {
toast.success( toast.success(
`Annotation offset for ${event?.camera} has been saved to the config file. Restart Frigate to apply your changes.`, t("objectLifecycle.annotationSettings.offset.toast.success", {
camera: event?.camera,
}),
{ {
position: "top-center", position: "top-center",
}, },

View File

@ -909,7 +909,9 @@ function ObjectDetailsTab({
search.label, search.label,
)) ? ( )) ? (
<> <>
<div className="text-sm text-primary/40">Description</div> <div className="text-sm text-primary/40">
{t("details.description.label")}
</div>
<div className="flex h-64 flex-col items-center justify-center gap-3 border p-4 text-sm text-primary/40"> <div className="flex h-64 flex-col items-center justify-center gap-3 border p-4 text-sm text-primary/40">
<div className="flex"> <div className="flex">
<ActivityIndicator /> <ActivityIndicator />

View File

@ -240,11 +240,13 @@ export function CameraStreamingDialog({
Object.entries(config?.cameras[camera].live.streams).length > 0 && ( Object.entries(config?.cameras[camera].live.streams).length > 0 && (
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<Label htmlFor="stream" className="text-right"> <Label htmlFor="stream" className="text-right">
Stream {t("group.camera.setting.stream")}
</Label> </Label>
<Select value={streamName} onValueChange={setStreamName}> <Select value={streamName} onValueChange={setStreamName}>
<SelectTrigger className=""> <SelectTrigger className="">
<SelectValue placeholder="Choose a stream" /> <SelectValue
placeholder={t("group.camera.setting.placeholder")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{camera !== "birdseye" && {camera !== "birdseye" &&
@ -305,7 +307,9 @@ export function CameraStreamingDialog({
onValueChange={(value) => setStreamType(value as StreamType)} onValueChange={(value) => setStreamType(value as StreamType)}
> >
<SelectTrigger className=""> <SelectTrigger className="">
<SelectValue placeholder="Choose a streaming option" /> <SelectValue
placeholder={t("group.camera.setting.streamMethod.placeholder")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="no-streaming"> <SelectItem value="no-streaming">

View File

@ -99,8 +99,11 @@ export default function ObjectMaskEditPane({
objectType = objects; objectType = objects;
} }
return `Object Mask ${count + 1} (${objectType})`; return t("masksAndZones.objectMaskLabel", {
}, [polygons, polygon]); number: count + 1,
label: t(objectType, { ns: "objects" }),
});
}, [polygons, polygon, t]);
const formSchema = z const formSchema = z
.object({ .object({

View File

@ -200,7 +200,7 @@ export default function ZoneEditPane({
speed_threshold: z.coerce speed_threshold: z.coerce
.number() .number()
.min(0.1, { .min(0.1, {
message: "Speed threshold must be greater than or equal to 0.1", message: t("masksAndZones.form.speed.error.mustBeGreaterOrEqualTo"),
}) })
.optional() .optional()
.or(z.literal("")), .or(z.literal("")),
@ -699,11 +699,15 @@ export default function ZoneEditPane({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Line A distance ( {t(
{config?.ui.unit_system == "imperial" "masksAndZones.zones.speedEstimation.lineADistance",
? "feet" {
: "meters"} unit:
) config?.ui.unit_system == "imperial"
? t("feet", { ns: "common" })
: t("meters", { ns: "common" }),
},
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -722,11 +726,15 @@ export default function ZoneEditPane({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Line B distance ( {t(
{config?.ui.unit_system == "imperial" "masksAndZones.zones.speedEstimation.lineBDistance",
? "feet" {
: "meters"} unit:
) config?.ui.unit_system == "imperial"
? t("feet", { ns: "common" })
: t("meters", { ns: "common" }),
},
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -745,11 +753,15 @@ export default function ZoneEditPane({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Line C distance ( {t(
{config?.ui.unit_system == "imperial" "masksAndZones.zones.speedEstimation.lineCDistance",
? "feet" {
: "meters"} unit:
) config?.ui.unit_system == "imperial"
? t("feet", { ns: "common" })
: t("meters", { ns: "common" }),
},
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
@ -768,11 +780,15 @@ export default function ZoneEditPane({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Line D distance ( {t(
{config?.ui.unit_system == "imperial" "masksAndZones.zones.speedEstimation.lineDDistance",
? "feet" {
: "meters"} unit:
) config?.ui.unit_system == "imperial"
? t("feet", { ns: "common" })
: t("meters", { ns: "common" }),
},
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input

View File

@ -197,7 +197,7 @@ function ConfigEditor() {
listener = (e) => { listener = (e) => {
e.preventDefault(); e.preventDefault();
e.returnValue = true; e.returnValue = true;
return "Exit without saving?"; return t("confirm");
}; };
window.addEventListener("beforeunload", listener); window.addEventListener("beforeunload", listener);
} }
@ -207,7 +207,7 @@ function ConfigEditor() {
window.removeEventListener("beforeunload", listener); window.removeEventListener("beforeunload", listener);
} }
}; };
}, [hasChanges]); }, [hasChanges, t]);
if (!config) { if (!config) {
return <ActivityIndicator />; return <ActivityIndicator />;

View File

@ -1044,7 +1044,7 @@ function FaceGrid({
return ( return (
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center"> <div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
<LuFolderCheck className="size-16" /> <LuFolderCheck className="size-16" />
No faces available (t("nofaces"))
</div> </div>
); );
} }