From 10fa4ec2ca15847b28756f8e6392b570369ab2e1 Mon Sep 17 00:00:00 2001 From: ZhaiSoul <842607283@qq.com> Date: Sun, 9 Mar 2025 22:30:22 +0800 Subject: [PATCH] chore: add more translation key --- web/package-lock.json | 20 +-- web/public/locales/en/common.json | 7 + web/public/locales/en/views/explore.json | 5 +- web/public/locales/en/views/settings.json | 104 +++++++++++++-- web/public/locales/zh-CN/common.json | 6 + web/public/locales/zh-CN/views/explore.json | 2 +- web/public/locales/zh-CN/views/settings.json | 67 ++++++++-- web/src/components/card/ReviewCard.tsx | 9 +- web/src/components/dynamic/TimeAgo.tsx | 9 +- .../filter/CalendarFilterButton.tsx | 6 +- .../components/filter/CameraGroupSelector.tsx | 27 ++-- .../components/filter/CamerasFilterButton.tsx | 2 +- .../components/filter/SearchFilterGroup.tsx | 31 +++-- web/src/components/graph/CameraGraph.tsx | 2 +- .../components/graph/CombinedStorageGraph.tsx | 8 +- web/src/components/icons/IconPicker.tsx | 4 +- web/src/components/input/SaveSearchDialog.tsx | 6 +- web/src/components/menu/GeneralSettings.tsx | 122 +++++++++++++++-- web/src/components/menu/LiveContextMenu.tsx | 9 +- .../components/menu/SearchResultActions.tsx | 26 +++- .../components/overlay/CameraInfoDialog.tsx | 31 +++-- .../components/overlay/CreateUserDialog.tsx | 66 +++++++--- .../components/overlay/DeleteUserDialog.tsx | 5 +- web/src/components/overlay/ExportDialog.tsx | 62 ++++++--- .../overlay/MobileReviewSettingsDrawer.tsx | 9 +- .../components/overlay/RoleChangeDialog.tsx | 26 ++-- .../components/overlay/SaveExportOverlay.tsx | 4 +- .../components/overlay/SetPasswordDialog.tsx | 47 +++++-- .../overlay/detail/SearchDetailDialog.tsx | 38 ++++-- .../overlay/dialog/RestartDialog.tsx | 5 +- .../overlay/dialog/SearchFilterDialog.tsx | 4 +- web/src/components/player/PreviewPlayer.tsx | 10 +- .../settings/CameraStreamingDialog.tsx | 25 +++- .../settings/MotionMaskEditPane.tsx | 54 +++----- .../settings/ObjectMaskEditPane.tsx | 62 ++++----- .../components/settings/SearchSettings.tsx | 32 +++-- web/src/components/settings/ZoneEditPane.tsx | 124 +++++++++--------- web/src/hooks/use-stats.ts | 4 +- web/src/pages/Events.tsx | 4 +- web/src/pages/Exports.tsx | 5 +- web/src/pages/Live.tsx | 6 +- web/src/pages/System.tsx | 8 +- web/src/utils/i18n.ts | 61 +++++---- web/src/views/events/EventView.tsx | 21 ++- web/src/views/live/DraggableGridLayout.tsx | 4 +- web/src/views/live/LiveCameraView.tsx | 48 ++++--- web/src/views/live/LiveDashboardView.tsx | 4 +- web/src/views/settings/AuthenticationView.tsx | 90 +++++++++---- web/src/views/settings/CameraSettingsView.tsx | 51 ++++--- web/src/views/settings/MotionTunerView.tsx | 19 +-- .../settings/NotificationsSettingsView.tsx | 40 ++++-- web/src/views/settings/ObjectSettingsView.tsx | 6 +- web/src/views/settings/SearchSettingsView.tsx | 32 +++-- web/src/views/settings/UiSettingsView.tsx | 14 +- web/src/views/system/CameraMetrics.tsx | 4 +- web/src/views/system/GeneralMetrics.tsx | 36 +++-- 56 files changed, 1026 insertions(+), 507 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index a171f6c89..986677695 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -5477,15 +5477,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/i18next": { "version": "24.2.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.0.tgz", @@ -8590,7 +8581,7 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9016,6 +9007,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 48ce0edbe..973f3f1d6 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -64,6 +64,7 @@ "info": "Info" }, "menu": { + "system": "System", "systemMetrics": "System metrics", "configuration": "Configuration", "systemLogs": "System logs", @@ -112,5 +113,11 @@ "error": "Failed to save config changes: {{errorMessage}}", "error.noMessage": "Failed to save config changes" } + }, + "role": { + "title": "Role", + "admin": "Admin", + "viewer": "Viewer", + "desc": "Admins have full access to all features in the Frigate UI. Viewers are limited to viewing cameras, review items, and historical footage in the UI." } } diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index 41a325118..96d7d2002 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -29,7 +29,7 @@ "regenerateFromThumbnails": "Regenerate from Thumbnails", "tips": { "descriptionSaved": "Successfully saved description", - "saveDescriptionFailed": "Failed to update the description" + "saveDescriptionFailed": "Failed to update the description: {{errorMessage}}" } }, "itemMenu": { @@ -59,5 +59,6 @@ "title": "Confirm Delete", "desc": "Deleting this tracked object removes the snapshot, any saved embeddings, and any associated object lifecycle entries. Recorded footage of this tracked object in History view will NOT be deleted.

Are you sure you want to proceed?" } - } + }, + "noTrackedObjects": "No Tracked Objects Found" } diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index dacde1ffa..64fd734db 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -91,6 +91,10 @@ "noDefinedZones": "No zones are defined for this camera.", "objectAlertsTips": "All {{alertsLabels}} objects on {{cameraName}} will be shown as Alerts.", "zoneObjectAlertsTips": "All {{alertsLabels}} objects detected in {{zone}} on {{cameraName}} will be shown as Alerts.", + "objectDetectionsTips": "All {{detectionsLabels}} objects not categorized on {{cameraName}} will be shown as Detections regardless of which zone they are in.", + "zoneObjectDetectionsTips": "All {{detectionsLabels}} objects not categorized in {{zone}} on {{cameraName}} will be shown as Detections.", + "zoneObjectDetectionsTips.notSelectDetections": "All {{detectionsLabels}} objects detected in {{zone}} on {{cameraName}} not categorized as Alerts will be shown as Detections regardless of which zone they are in.", + "zoneObjectDetectionsTips.regardlessOfZoneObjectDetectionsTips": "All {{detectionsLabels}} objects not categorized on {{cameraName}} will be shown as Detections regardless of which zone they are in.", "selectAlertsZones": "Select zones for Alerts", "selectDetectionsZones": "Select zones for Detections", "limitDetections": "Limit detections to specific zones", @@ -103,9 +107,24 @@ "filter": { "all": "All Masks and Zones" }, - "polygonDrawing": { - "error": { - "mustBeFinished": "多边形绘制必须完成闭合后才能保存。" + "form": { + "zoneName": { + "error": { + "mustBeAtLeastTwoCharacters": "Zone name must be at least 2 characters.", + "mustNotBeSameWithCamera": "Zone name must not be the same as camera name.", + "alreadyExists": "A zone with this name already exists for this camera.", + "mustNotContainPeriod": "Zone name must not contain periods.", + "hasIllegalCharacter": "Zone name contains illegal characters." + } + }, + "distance.error": "Distance must be greater than or equal to 0.1.", + "distance.error.mustBeFilled": "All distance fields must be filled to use speed estimation.", + "inertia.error.mustBeAboveZero": "Inertia must be above 0.", + "loiteringTime.error.mustBeGreaterOrEqualZero": "Loitering time must be greater than or equal to 0.", + "polygonDrawing": { + "error": { + "mustBeFinished": "Polygon drawing must be finished before saving." + } } }, "zones": { @@ -182,7 +201,9 @@ "contourArea.desc": "The contour area value is used to decide which groups of changed pixels qualify as motion. Default: 10", "improveContrast": "Improve Contrast", "improveContrast.desc": "Improve contrast for darker scenes. Default: ON", - "toast.success": "Motion settings have been saved.", + "toast": { + "success": "Motion settings have been saved." + } }, "debug": { "title": "Debug", @@ -230,29 +251,86 @@ }, "users": { "title": "Users", + "management": "Users Management", + "management.desc": "Manage this Frigate instance's user accounts.", "addUser": "Add User", "updatePassword": "Update Password", "toast": { + "success": { + "createUser": "User {{user}} created successfully", + "deleteUser": "User {{user}} deleted successfully" + }, "error": { - "setPasswordFailed": "Error setting password", - "createUserFailed": "Error creating user. Check server logs.", - "deleteUserFailed": "Error deleting user. Check server logs." + "setPasswordFailed": "Failed to save password: {{errorMessage}}", + "createUserFailed": "Failed to create user: {{errorMessage}}", + "deleteUserFailed": "Failed to delete user: {{errorMessage}}" } }, + "table": { + "username": "Username", + "actions": "Actions", + "role": "Role", + "noUsers": "No users found.", + "changeRole": "Change user role", + "password": "Password", + "deleteUser": "Delete user" + }, "dialog": { + "form": { + "user": "Username", + "user.desc": "Only letters, numbers, periods and underscores allowed.", + "user.placeholder": "Enter username", + "password": "Password", + "password.placeholder": "Enter password", + "password.confirm": "Confirm Password", + "password.confirm.placeholder": "Confirm Password", + "password.strength": "password strength: ", + "password.strength.weak": "Weak", + "password.strength.medium": "Medium", + "password.strength.strong": "Strong", + "password.strength.veryStrong": "Very strong", + "password.match": "Passwords match", + "password.notMatch": "Passwords don't match", + "newPassword": "New Password", + "newPassword.placeholder": "Enter new password", + "newPassword.confirm.placeholder": "Re-enter new password", + "usernameIsRequired": "Username is required" + }, "createUser": { - "title": "Create User", - "user": "user", - "password": "password", + "title": "Create New User", + "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 _" }, "deleteUser": { "title": "Delete User", - "warn": "Are you sure?" + "desc": "This action cannot be undone. This will permanently delete the user account and remove all associated data.", + "warn": "Are you sure you want to delete {{username}}?" }, - "setPassword": { - "title": "Set Password" + "passwordSetting": { + "updatePassword": "Update Password for {{username}}", + "setPassword": "Set Password", + "desc": "Create a strong password to secure this account." + }, + "changeRole": { + "title": "Change User Role", + "desc": "Update permissions for {{username}}", + "roleInfo": "

Select the appropriate role for this user:

" } } + }, + "notification": { + "title": "Notifications", + "notificationSettings": "Notification Settings", + "desc": "Frigate can send native push notifications to your device when running in the browser or installed as a PWA.", + "documentation": "Read the Documentation", + "email": "Email", + "email.placeholder": "e.g. example@email.com", + "email.desc": "A valid email is required and will be used to notify you if there are any issues with the push service.", + "cameras": "Cameras", + "cameras.noCameras": "No cameras available", + "cameras.desc": "Select which cameras to enable notifications for.", + "deviceSpecific": "Device Specific Settings", + "registerDevice": "Register This Device", + "unregisterDevice": "Unregister This Device" } } diff --git a/web/public/locales/zh-CN/common.json b/web/public/locales/zh-CN/common.json index 2f8b5f602..a143765e5 100644 --- a/web/public/locales/zh-CN/common.json +++ b/web/public/locales/zh-CN/common.json @@ -112,5 +112,11 @@ "error": "保存配置信息失败: {{errorMessage}}", "error.noMessage": "保存配置信息失败" } + }, + "role": { + "title": "权限组", + "admin": "管理员", + "viewer": "查看者", + "desc": "管理员可以完全访问 Frigate UI 的所有功能。查看者则仅限于在 UI 中查看摄像头、审核项和历史录像。" } } diff --git a/web/public/locales/zh-CN/views/explore.json b/web/public/locales/zh-CN/views/explore.json index ea3493bd8..09370a3cf 100644 --- a/web/public/locales/zh-CN/views/explore.json +++ b/web/public/locales/zh-CN/views/explore.json @@ -29,7 +29,7 @@ "regenerateFromThumbnails": "从缩略图重新生成", "tips": { "descriptionSaved": "已保存描述", - "saveDescriptionFailed": "更新描述失败" + "saveDescriptionFailed": "更新描述失败:{{errorMessage}}" } }, "itemMenu": { diff --git a/web/public/locales/zh-CN/views/settings.json b/web/public/locales/zh-CN/views/settings.json index 448119851..746768b15 100644 --- a/web/public/locales/zh-CN/views/settings.json +++ b/web/public/locales/zh-CN/views/settings.json @@ -158,7 +158,7 @@ "motionMasks": { "label": "运动遮罩", "documentTitle": "编辑运动遮罩 - Frigate", - "desc": "该功能用于防止触发不必要的运动类型。过度的设置遮罩将使对象更加难以被追踪", + "desc": "运动遮罩用于防止触发不必要的运动类型。过度的设置遮罩将使对象更加难以被追踪", "desc.documentation": "文档(英文)", "add": "添加运动遮罩", "edit": "编辑运动遮罩", @@ -200,7 +200,10 @@ "contourArea": "轮廓面积", "contourArea.desc": "轮廓面积决定哪些变化的像素组符合运动条件。默认值:10", "improveContrast": "提高对比度", - "improveContrast.desc": "提高较暗场景的对比度。默认值:开启" + "improveContrast.desc": "提高较暗场景的对比度。默认值:开启", + "toast": { + "success": "运动设置已保存。" + } }, "debug": { "title": "调试", @@ -248,29 +251,73 @@ }, "users": { "title": "用户", + "management": "用户管理", + "management.desc": "管理此 Frigate 实例的用户账户。", "addUser": "添加用户", "updatePassword": "修改密码", "toast": { + "success": { + "createUser": "用户 {{user}} 创建成功", + "deleteUser": "用户 {{user}} 删除成功" + }, "error": { - "setPasswordFailed": "保存密码出现错误", - "createUserFailed": "创建用户失败!请检查后台日志。", - "deleteUserFailed": "删除用户失败!请检查后台日志。" + "setPasswordFailed": "保存密码出现错误:{{errorMessage}}", + "createUserFailed": "创建用户失败:{{errorMessage}}", + "deleteUserFailed": "删除用户失败:{{errorMessage}}" } }, + "table": { + "username": "用户名", + "actions": "操作", + "role": "权限组", + "noUsers": "未找到用户。", + "changeRole": "更改用户角色", + "password": "密码", + "deleteUser": "删除用户" + }, "dialog": { + "form": { + "user": "用户名", + "user.desc": "仅允许使用字母、数字、句点和下划线。", + "user.placeholder": "请输入用户名", + "password": "密码", + "password.placeholder": "请输入密码", + "password.confirm": "确认密码", + "password.confirm.placeholder": "请再次输入密码", + "password.strength": "密码强度:", + "password.strength.weak": "弱", + "password.strength.medium": "中等", + "password.strength.strong": "强", + "password.strength.veryStrong": "非常强", + "password.match": "密码匹配", + "password.notMatch": "密码不匹配", + "newPassword": "新密码", + "newPassword.placeholder": "请输入新密码", + "newPassword.confirm.placeholder": "请再次输入新密码", + "usernameIsRequired": "用户名为必填项" + }, "createUser": { - "title": "创建用户", + "title": "创建新用户", + "desc": "创建一个新用户账户,并指定一个角色以控制访问 Frigate UI 的权限。", "user": "用户", "password": "密码", "usernameOnlyInclude": "用户名只能包含字母、数字和 _" }, "deleteUser": { "title": "删除该用户", - "warn": "你确定要删除该用户吗?" + "desc": "此操作无法撤销。这将永久删除用户账户并移除所有相关数据。", + "warn": "你确定要删除 {{username}} 吗?" }, - "setPassword": { - "title": "修改密码" - } + "passwordSetting": { + "updatePassword": "更新 {{username}} 的密码", + "setPassword": "设置密码", + "desc": "创建一个强密码来保护此账户。" + }, + "changeRole": { + "title": "更改用户权限组", + "desc": "更新 {{username}} 的权限", + "roleInfo": "

请选择此用户的适当角色:

" + } } }, "notification": { diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index b42d83af7..6975f7d9e 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -83,9 +83,12 @@ export default function ReviewCard({ ) .then((response) => { if (response.status == 200) { - toast.success(t("export.toast.success", { ns: "components/dialog"}), { - position: "top-center", - }); + toast.success( + t("export.toast.success", { ns: "components/dialog" }), + { + position: "top-center", + }, + ); } }) .catch((error) => { diff --git a/web/src/components/dynamic/TimeAgo.tsx b/web/src/components/dynamic/TimeAgo.tsx index 4836dd35a..594187988 100644 --- a/web/src/components/dynamic/TimeAgo.tsx +++ b/web/src/components/dynamic/TimeAgo.tsx @@ -66,12 +66,9 @@ const timeAgo = ({ if (monthDiff > 0) { const unitAmount = monthDiff; return t("time.ago", { - timeAgo: t( - `time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, - { - time: unitAmount, - }, - ), + timeAgo: t(`time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, { + time: unitAmount, + }), }); } } else if (elapsed >= timeUnits[i].value) { diff --git a/web/src/components/filter/CalendarFilterButton.tsx b/web/src/components/filter/CalendarFilterButton.tsx index c463aebb5..2975b110e 100644 --- a/web/src/components/filter/CalendarFilterButton.tsx +++ b/web/src/components/filter/CalendarFilterButton.tsx @@ -32,7 +32,7 @@ export default function CalendarFilterButton({ const [open, setOpen] = useState(false); const selectedDate = useFormattedTimestamp( day == undefined ? 0 : day?.getTime() / 1000 + 1, - "%b %-d", + t("time.formattedTimestampOnlyMonthAndDay"), ); const trigger = ( @@ -48,7 +48,9 @@ export default function CalendarFilterButton({
- {day == undefined ? t("calendarFilter.last24Hours", {ns: "views/events"}) : selectedDate} + {day == undefined + ? t("calendarFilter.last24Hours", { ns: "views/events" }) + : selectedDate}
); diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index a1f9e58e4..43d54a53b 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -656,7 +656,9 @@ export function CameraGroupEdit({ name: z .string() .min(2, { - message: t("group.name.errorMessage.mustLeastCharacters", { ns: "components/camera" }), + message: t("group.name.errorMessage.mustLeastCharacters", { + ns: "components/camera", + }), }) .transform((val: string) => val.trim().replace(/\s+/g, "_")) .refine( @@ -667,7 +669,9 @@ export function CameraGroupEdit({ ); }, { - message: t("group.name.errorMessage.exists", { ns: "components/camera" }), + message: t("group.name.errorMessage.exists", { + ns: "components/camera", + }), }, ) .refine( @@ -679,7 +683,9 @@ export function CameraGroupEdit({ }, ) .refine((value: string) => value.toLowerCase() !== "default", { - message: t("group.name.errorMessage.invalid", { ns: "components/camera" }), + message: t("group.name.errorMessage.invalid", { + ns: "components/camera", + }), }), cameras: z.array(z.string()), @@ -735,7 +741,10 @@ export function CameraGroupEdit({ .then(async (res) => { if (res.status === 200) { toast.success( - t("group.toast.success", { name: values.name, ns: "components/camera"}), + t("group.toast.success", { + name: values.name, + ns: "components/camera", + }), { position: "top-center", }, @@ -756,9 +765,9 @@ export function CameraGroupEdit({ }) .catch((error) => { const errorMessage = - error.response?.data?.message || - error.response?.data?.detail || - "Unknown error"; + error.response?.data?.message || + error.response?.data?.detail || + "Unknown error"; toast.error( t("toast.save.error", { errorMessage, @@ -809,7 +818,9 @@ export function CameraGroupEdit({ diff --git a/web/src/components/filter/CamerasFilterButton.tsx b/web/src/components/filter/CamerasFilterButton.tsx index 9c346978f..48a736388 100644 --- a/web/src/components/filter/CamerasFilterButton.tsx +++ b/web/src/components/filter/CamerasFilterButton.tsx @@ -153,7 +153,7 @@ export function CamerasFilterContent({
{ if (isChecked) { setCurrentCameras(undefined); diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 293be24f1..344ca7245 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -198,7 +198,9 @@ export default function SearchFilterGroup({ } } defaultText={ - isMobile ? t("dates.all.short", {ns: "components/filter"}) : t("dates.all", {ns: "components/filter"}) + isMobile + ? t("dates.all.short", { ns: "components/filter" }) + : t("dates.all", { ns: "components/filter" }) } updateSelectedRange={onUpdateSelectedRange} /> @@ -240,18 +242,21 @@ function GeneralFilterButton({ const buttonText = useMemo(() => { if (isMobile) { - return t("labels.all.short", {ns: "components/filter"}); + return t("labels.all.short", { ns: "components/filter" }); } if (!selectedLabels || selectedLabels.length == 0) { - return t("labels.all", {ns: "components/filter"}); + return t("labels.all", { ns: "components/filter" }); } if (selectedLabels.length == 1) { - return t(selectedLabels[0], { ns: "objects"}); + return t(selectedLabels[0], { ns: "objects" }); } - return t("labels.count", { count: selectedLabels.length, ns: "components/filter" }); + return t("labels.count", { + count: selectedLabels.length, + ns: "components/filter", + }); }, [selectedLabels]); // ui @@ -352,7 +357,7 @@ export function GeneralFilterContent({ {allLabels.map((item) => ( { if (isChecked) { @@ -501,13 +506,13 @@ export function SortTypeContent({ onClose, }: SortTypeContentProps) { const sortLabels = { - date_asc: t("sort.dateAsc", {ns: "components/filter"}), - date_desc: t("sort.dateDesc", {ns: "components/filter"}), - score_asc: t("sort.scoreAsc", {ns: "components/filter"}), - score_desc: t("sort.scoreDesc", {ns: "components/filter"}), - speed_asc: t("sort.speedAsc", {ns: "components/filter"}), - speed_desc: t("sort.speedDesc", {ns: "components/filter"}), - relevance: t("sort.relevance", {ns: "components/filter"}), + date_asc: t("sort.dateAsc", { ns: "components/filter" }), + date_desc: t("sort.dateDesc", { ns: "components/filter" }), + score_asc: t("sort.scoreAsc", { ns: "components/filter" }), + score_desc: t("sort.scoreDesc", { ns: "components/filter" }), + speed_asc: t("sort.speedAsc", { ns: "components/filter" }), + speed_desc: t("sort.speedDesc", { ns: "components/filter" }), + relevance: t("sort.relevance", { ns: "components/filter" }), }; return ( <> diff --git a/web/src/components/graph/CameraGraph.tsx b/web/src/components/graph/CameraGraph.tsx index 2d887fa3b..1ed970354 100644 --- a/web/src/components/graph/CameraGraph.tsx +++ b/web/src/components/graph/CameraGraph.tsx @@ -128,7 +128,7 @@ export function CameraLineGraph({ style={{ color: GRAPH_COLORS[labelIdx] }} />
- {t("cameras.label." + label, {ns: "views/settings"})} + {t("cameras.label." + label, { ns: "views/settings" })}
{lastValues[labelIdx]} diff --git a/web/src/components/graph/CombinedStorageGraph.tsx b/web/src/components/graph/CombinedStorageGraph.tsx index 4685d19b4..ba4795962 100644 --- a/web/src/components/graph/CombinedStorageGraph.tsx +++ b/web/src/components/graph/CombinedStorageGraph.tsx @@ -182,7 +182,9 @@ export function CombinedStorageGraph({ storage.cameraStorage.camera - storage.cameraStorage.storageUsed + + storage.cameraStorage.storageUsed + @@ -205,8 +207,8 @@ export function CombinedStorageGraph({ >
{item.name === "Unused" ? t("storage.cameraStorage.unused", { - ns: "views/system", - }) + ns: "views/system", + }) : item.name.replaceAll("_", " ")} {item.name === "Unused" && ( diff --git a/web/src/components/icons/IconPicker.tsx b/web/src/components/icons/IconPicker.tsx index db750e262..a01461b32 100644 --- a/web/src/components/icons/IconPicker.tsx +++ b/web/src/components/icons/IconPicker.tsx @@ -117,7 +117,9 @@ export default function IconPicker({
setSearchTerm(e.target.value)} diff --git a/web/src/components/input/SaveSearchDialog.tsx b/web/src/components/input/SaveSearchDialog.tsx index 45e2e5b65..efeaeccd1 100644 --- a/web/src/components/input/SaveSearchDialog.tsx +++ b/web/src/components/input/SaveSearchDialog.tsx @@ -37,7 +37,7 @@ export function SaveSearchDialog({ toast.success( t("search.saveSearch.success", { searchName: searchName.trim(), - ns: "components/dialog" + ns: "components/dialog", }), { position: "top-center", @@ -73,7 +73,9 @@ export function SaveSearchDialog({ value={searchName} className="text-md" onChange={(e) => setSearchName(e.target.value)} - placeholder={t("search.saveSearch.placeholder", {ns: "components/dialog"})} + placeholder={t("search.saveSearch.placeholder", { + ns: "components/dialog", + })} /> {overwrite && (
diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index 033094836..c6905b31d 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -11,6 +11,7 @@ import { LuSettings, LuSun, LuSunMoon, + LuEarth, } from "react-icons/lu"; import { DropdownMenu, @@ -195,7 +196,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { )} {isAdmin && ( <> - menu.system + + menu.system + @@ -208,7 +211,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { aria-label="System metrics" > - menu.systemMetrics + + menu.systemMetrics + @@ -221,7 +226,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { aria-label="System logs" > - menu.systemLogs + + menu.systemLogs + @@ -261,7 +268,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { aria-label="Configuration editor" > - menu.configurationEditor + + menu.configurationEditor + @@ -271,6 +280,87 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { menu.appearance + + + + + menu.languages + + + + + + setLanguage("en")} + > + {language.trim() === "en" ? ( + <> + + menu.language.en + + ) : ( + + menu.language.en + + )} + + setLanguage("zh-CN")} + > + {language === "zh-CN" ? ( + <> + + menu.language.zhCN + + ) : ( + + menu.language.zhCN + + )} + + setLanguage(systemLanguage)} + > + {language === systemLanguage ? ( + <> + + menu.withSystem + + ) : ( + + menu.withSystem + + )} + + + + - menu.darkMode.label + + menu.darkMode.label + menu.darkMode.light ) : ( - menu.darkMode.light + + menu.darkMode.light + )} menu.darkMode.dark ) : ( - menu.darkMode.dark + + menu.darkMode.dark + )} menu.withSystem ) : ( - menu.withSystem + + menu.withSystem + )} @@ -351,7 +449,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { } > - menu.theme.label + + menu.theme.label + setRestartDialogOpen(true)} > - menu.restart + + menu.restart + )} diff --git a/web/src/components/menu/LiveContextMenu.tsx b/web/src/components/menu/LiveContextMenu.tsx index 1571841a6..ab590a357 100644 --- a/web/src/components/menu/LiveContextMenu.tsx +++ b/web/src/components/menu/LiveContextMenu.tsx @@ -242,7 +242,10 @@ export default function LiveContextMenu({ time_style: "medium", date_style: "medium", timezone: config?.ui.timezone, - strftime_fmt: config?.ui.time_format == "24hour" ? t("time.formattedTimestampExcludeSeconds.24hour"): t("time.formattedTimestampExcludeSeconds"), + strftime_fmt: + config?.ui.time_format == "24hour" + ? t("time.formattedTimestampExcludeSeconds.24hour") + : t("time.formattedTimestampExcludeSeconds"), }); }; @@ -359,7 +362,9 @@ export default function LiveContextMenu({ className="flex w-full cursor-pointer items-center justify-start gap-2" onClick={isEnabled ? resetPreferredLiveMode : undefined} > -
button
+
+ button +
diff --git a/web/src/components/menu/SearchResultActions.tsx b/web/src/components/menu/SearchResultActions.tsx index 9df9215dc..5859cf92c 100644 --- a/web/src/components/menu/SearchResultActions.tsx +++ b/web/src/components/menu/SearchResultActions.tsx @@ -92,7 +92,9 @@ export default function SearchResultActions({ const menuItems = ( <> {searchResult.has_clip && ( - + @@ -134,7 +140,7 @@ export default function SearchResultActions({ )} {config?.semantic_search?.enabled && isContextMenu && ( @@ -150,7 +156,9 @@ export default function SearchResultActions({ searchResult.data.type == "object" && !searchResult.plus_id && ( @@ -215,7 +223,9 @@ export default function SearchResultActions({ onClick={findSimilar} /> - itemMenu.findSimilar.label + + itemMenu.findSimilar.label + )} @@ -232,7 +242,9 @@ export default function SearchResultActions({ onClick={showSnapshot} /> - itemMenu.submitToPlus.label + + itemMenu.submitToPlus.label + )} diff --git a/web/src/components/overlay/CameraInfoDialog.tsx b/web/src/components/overlay/CameraInfoDialog.tsx index 54cbab787..ed0bb9f9a 100644 --- a/web/src/components/overlay/CameraInfoDialog.tsx +++ b/web/src/components/overlay/CameraInfoDialog.tsx @@ -74,9 +74,9 @@ export default function CameraInfoDialog({ - {t("system.cameras.info.cameraProbeInfo", { + {t("cameras.info.cameraProbeInfo", { camera: camera.name.replaceAll("_", " "), - ns: "views/system" + ns: "views/system", })} @@ -90,7 +90,10 @@ export default function CameraInfoDialog({ {ffprobeInfo.map((stream, idx) => (
- {t("cameras.info.stream", { idx: idx + 1, ns: "views/system" })} + {t("cameras.info.stream", { + idx: idx + 1, + ns: "views/system", + })}
{stream.return_code == 0 ? (
@@ -99,11 +102,15 @@ export default function CameraInfoDialog({ {codec.width ? (
- cameras.info.video + + cameras.info.video +
- cameras.info.codec + + cameras.info.codec + {" "} {codec.codec_long_name} @@ -138,10 +145,14 @@ export default function CameraInfoDialog({ )}
- cameras.info.fps{" "} + + cameras.info.fps + {" "} {codec.avg_frame_rate == "0/0" - ? t("cameras.info.unknown", { ns: "views/system" }) + ? t("cameras.info.unknown", { + ns: "views/system", + }) : codec.avg_frame_rate}
@@ -151,7 +162,9 @@ export default function CameraInfoDialog({
Audio:
- cameras.info.codec{" "} + + cameras.info.codec + {" "} {codec.codec_long_name} @@ -166,7 +179,7 @@ export default function CameraInfoDialog({
{t("cameras.info.error", { error: stream.stderr, - ns: "views/system" + ns: "views/system", })}
diff --git a/web/src/components/overlay/CreateUserDialog.tsx b/web/src/components/overlay/CreateUserDialog.tsx index 7d724dfb1..129859a2e 100644 --- a/web/src/components/overlay/CreateUserDialog.tsx +++ b/web/src/components/overlay/CreateUserDialog.tsx @@ -51,16 +51,22 @@ export default function CreateUserDialog({ .object({ user: z .string() - .min(1, "Username is required") + .min(1, t("users.dialog.form.usernameIsRequired", { + ns: "views/settings", + })) .regex(/^[A-Za-z0-9._]+$/, { - message: t("users.dialog.createUser.usernameOnlyInclude", {ns: "views/settings"}), + message: t("users.dialog.createUser.usernameOnlyInclude", { + ns: "views/settings", + }), }), password: z.string().min(1, "Password is required"), confirmPassword: z.string().min(1, "Please confirm your password"), role: z.enum(["admin", "viewer"]), }) .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", + message: t("users.dialog.form.password.notMatch", { + ns: "views/settings", + }), path: ["confirmPassword"], }); @@ -111,9 +117,11 @@ export default function CreateUserDialog({ - users.dialog.createUser.title + + users.dialog.createUser.title + - users.dialog.createUser.desc + users.dialog.createUser.desc @@ -127,17 +135,21 @@ export default function CreateUserDialog({ render={({ field }) => ( - users.dialog.createUser.user + + users.dialog.form.user + - users.dialog.createUser.user.desc + + users.dialog.form.user.desc + @@ -149,11 +161,13 @@ export default function CreateUserDialog({ render={({ field }) => ( - users.dialog.createUser.password + + users.dialog.form.password + ( - users.dialog.createUser.confirmPassword + + users.dialog.form.password.confirm + - users.dialog.createUser.password.match + + users.dialog.form.password.match + ) : ( <> - users.dialog.createUser.password.notMatch + + users.dialog.form.password.notMatch + )} @@ -207,7 +227,9 @@ export default function CreateUserDialog({ name="role" render={({ field }) => ( - role.title + + role.title + setName(e.target.value)} /> @@ -342,8 +358,8 @@ export function ExportContent({ }} > {selectedOption == "timeline" - ? t("export.select", {ns: "components/dialog"}) - : t("export.export", {ns: "components/dialog"})} + ? t("export.select", { ns: "components/dialog" }) + : t("export.export", { ns: "components/dialog" })}
@@ -598,10 +614,14 @@ export function ExportPreviewDialog({ > - export.fromTimeline.previewExport + + export.fromTimeline.previewExport + - export.fromTimeline.previewExport + + export.fromTimeline.previewExport + diff --git a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx index f50cb57c3..9a869a8eb 100644 --- a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx +++ b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx @@ -98,9 +98,12 @@ export default function MobileReviewSettingsDrawer({ ) .then((response) => { if (response.status == 200) { - toast.success(t("export.toast.success", { ns: "components/dialog"}), { - position: "top-center", - }); + toast.success( + t("export.toast.success", { ns: "components/dialog" }), + { + position: "top-center", + }, + ); setName(""); setRange(undefined); setMode("none"); diff --git a/web/src/components/overlay/RoleChangeDialog.tsx b/web/src/components/overlay/RoleChangeDialog.tsx index 577c748ff..8689764a7 100644 --- a/web/src/components/overlay/RoleChangeDialog.tsx +++ b/web/src/components/overlay/RoleChangeDialog.tsx @@ -1,3 +1,4 @@ +import { Trans } from "react-i18next"; import { Button } from "../ui/button"; import { Dialog, @@ -41,27 +42,16 @@ export default function RoleChangeDialog({ - Change User Role + users.dialog.changeRole.title - Update permissions for{" "} - {username} + users.dialog.changeRole.desc
-

Select the appropriate role for this user:

-
    -
  • - • Admin: Full access to all - features. -
  • -
  • - • Viewer: Limited to Live - dashboards, Review, Explore, and Exports only. -
  • -
+ users.dialog.changeRole.roleInfo
@@ -131,7 +142,7 @@ export default function SetPasswordDialog({ />

- Password strength:{" "} + users.dialog.form.password.strength {getStrengthLabel()}

@@ -139,7 +150,11 @@ export default function SetPasswordDialog({
- + {/* Password match indicator */} @@ -158,12 +173,20 @@ export default function SetPasswordDialog({ {password === confirmPassword ? ( <> - users.dialog.form.password.match + + + users.dialog.form.password.match + + ) : ( <> - users.dialog.form.password.notMatch + + + users.dialog.form.password.notMatch + + )}
diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 7cd45a4fe..08948dfaf 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -374,9 +374,12 @@ function ObjectDetailsTab({ .post(`events/${search.id}/description`, { description: desc }) .then((resp) => { if (resp.status == 200) { - toast.success(t("details.tips.descriptionSaved", {ns: "views/explore"}), { - position: "top-center", - }); + toast.success( + t("details.tips.descriptionSaved", { ns: "views/explore" }), + { + position: "top-center", + }, + ); } mutate( (key) => @@ -407,9 +410,15 @@ function ObjectDetailsTab({ error.response?.data?.message || error.response?.data?.detail || "Unknown error"; - toast.error(t("details.tips.saveDescriptionFailed", {ns: "views/explore", errorMessage}), { - position: "top-center", - }); + toast.error( + t("details.tips.saveDescriptionFailed", { + ns: "views/explore", + errorMessage, + }), + { + position: "top-center", + }, + ); setDesc(search.data.description); }); }, [desc, search, mutate]); @@ -580,7 +589,9 @@ function ObjectDetailsTab({ {averageEstimatedSpeed && (
{averageEstimatedSpeed}{" "} - {config?.ui.unit_system == "imperial" ? t("unit.speed.mph") : t("unit.speed.kph")}{" "} + {config?.ui.unit_system == "imperial" + ? t("unit.speed.mph") + : t("unit.speed.kph")}{" "} {velocityAngle != undefined && (