diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 1af1d70b4..707052b28 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -36,6 +36,8 @@ "second": "{{time}} seconds", "formattedTimestamp": "%b %-d, %I:%M:%S %p", "formattedTimestamp.24hour": "%b %-d, %H:%M:%S", + "formattedTimestamp2": "%m/%d %I:%M:%S%P", + "formattedTimestamp2.24hour": "%d %b %H:%M:%S", "formattedTimestampExcludeSeconds": "%b %-d, %I:%M %p", "formattedTimestampExcludeSeconds.24hour": "%b %-d, %H:%M", "formattedTimestampWithYear": "%b %-d %Y, %I:%M %p", @@ -48,6 +50,9 @@ "kph": "kph" } }, + "label": { + "back": "Go back" + }, "button": { "apply": "Apply", "reset": "Reset", @@ -75,7 +80,11 @@ "download": "Download", "info": "Info", "suspended": "Suspended", - "unsuspended": "Unsuspend" + "unsuspended": "Unsuspend", + "play": "Play", + "unselect": "Unselect", + "export": "Export", + "deleteNow": "Delete Now" }, "menu": { "system": "System", @@ -87,13 +96,15 @@ "languages": "Languages", "language": { "en": "English", - "zhCN": "简体中文(Simplified Chinese)" + "zhCN": "简体中文(Simplified Chinese)", + "withSystem.label": "Use the system settings for languag" }, "appearance": "Appearance", "darkMode": { "label": "Dark Mode", "light": "Light", - "dark": "Dark" + "dark": "Dark", + "withSystem.label": "Use the system settings for light or dark mode" }, "withSystem": "System", "theme": { @@ -136,5 +147,13 @@ "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." + }, + "pagination": { + "label": "pagination", + "previous": "Previous", + "previous.label": "Go to previous page", + "next": "Next", + "next.label": "Go to next page", + "more": "More pages" } } diff --git a/web/public/locales/en/components/auth.json b/web/public/locales/en/components/auth.json new file mode 100644 index 000000000..bec032838 --- /dev/null +++ b/web/public/locales/en/components/auth.json @@ -0,0 +1,15 @@ +{ + "form": { + "user": "Username", + "password": "Password", + "login": "Login", + "errors": { + "usernameRequired": "Username is required", + "passwordRequired": "Password is required", + "rateLimit": "Exceeded rate limit. Try again later.", + "loginFailed": "Login failed", + "unknownError": "Unknown error. Check logs.", + "webUnkownError": "Unknown error. Check console logs." + } + } +} \ No newline at end of file diff --git a/web/public/locales/en/components/camera.json b/web/public/locales/en/components/camera.json index ff511f914..049384a42 100644 --- a/web/public/locales/en/components/camera.json +++ b/web/public/locales/en/components/camera.json @@ -4,6 +4,7 @@ "add": "Add camera groups", "edit": "Edit camera groups", "delete": { + "label": "Delete Camera Group", "confirm": "Confirm Delete", "confirm.desc": "Are you sure you want to delete the camera group {{name}}?" }, @@ -25,6 +26,7 @@ "success": "Camera group ({{name}}) has been saved.", "camera": { "setting": { + "label": "Camera Streaming Settings", "title": "{{cameraName}} Streaming Settings", "desc": "Change the live streaming options for this camera group's dashboard. These settings are device/browser-specific.", "audioIsAvailable": "Audio is available for this stream", @@ -57,5 +59,19 @@ } } } + }, + "debug": { + "options": { + "label": "Settings", + "title": "Options", + "showOptions": "Show Options", + "hideOptions": "Hide Options" + }, + "boundingBox": "Bounding Box", + "timestamp": "Timestamp", + "zones": "Zones", + "mask": "Mask", + "motion": "Motion", + "regions": "Regions" } } diff --git a/web/public/locales/en/components/dialog.json b/web/public/locales/en/components/dialog.json index 85666eef4..c58faca21 100644 --- a/web/public/locales/en/components/dialog.json +++ b/web/public/locales/en/components/dialog.json @@ -15,10 +15,12 @@ "desc": "Objects in locations you want to avoid are not false positives. Submitting them as false positives will confuse the model." }, "review": { + "true.label": "Confirm this label for Frigate Plus", "true_one": "This is a {{label}}", "true_other": "This is an {{label}}", "false_one": "This is not a {{label}}", "false_other": "This is not an {{label}}", + "false.label": "Do not confirm this label for Frigate Plus", "state.submitted": "Submitted" } }, @@ -31,13 +33,18 @@ "fromTimeline": "Select from Timeline", "lastHour_one": "Last Hour", "lastHour_other": "Last {{count}} Hours", - "custom": "Custom" + "custom": "Custom", + "start": "Start Time", + "start.label": "Select Start Time", + "end": "End Time", + "end.label": "Select End Time" }, "name": { "placeholder": "Name the Export" }, "select": "Select", "export": "Export", + "selectOrExport": "Select or Export", "toast": { "success": "Successfully started export. View the file in the /exports folder.", "error": { @@ -70,13 +77,15 @@ "desc": "Provide a name for this saved search.", "placeholder": "Enter a name for your search", "overwrite": "{{searchName}} already exists. Saving will overwrite the existing value.", - "success": "Search ({{searchName}}) has been saved." + "success": "Search ({{searchName}}) has been saved.", + "button.save.label": "Save this search" } }, "recording": { "confirmDelete": { "title": "Confirm Delete", - "desc": "Are you sure you want to delete all recorded video associated with this review item?

Hold the Shift key to bypass this dialog in the future." + "desc": "Are you sure you want to delete all recorded video associated with this review item?

Hold the Shift key to bypass this dialog in the future.", + "desc.selected": "Are you sure you want to delete all recorded video associated with this review item?

Hold the Shift key to bypass this dialog in the future." }, "button": { "export": "Export", diff --git a/web/public/locales/en/components/filter.json b/web/public/locales/en/components/filter.json index 5a99aaa0f..0904f2504 100644 --- a/web/public/locales/en/components/filter.json +++ b/web/public/locales/en/components/filter.json @@ -1,6 +1,7 @@ { "filter": "Filter", "labels": { + "label": "Labels", "all": "All Labels", "all.short": "Labels", "count": "{{count}} Labels" @@ -14,6 +15,7 @@ "all.short": "Dates" }, "more": "More Filters", + "reset.label": "Reset filters to default values", "timeRange": "Time Range", "zones.label": "Zones", "subLabels": { @@ -42,12 +44,16 @@ "relevance": "Relevance" }, "cameras": { + "label": "Cameras Filter", "all": "All Cameras", "all.short": "Cameras" }, "review": { "showReviewed": "Show Reviewed" }, + "motion": { + "showMotionOnly": "Show Motion Only" + }, "explore": { "settings": { "title": "Settings", @@ -65,6 +71,30 @@ "description": "Description" } } + }, + "date": { + "selectDateBy": { + "label": "Select a date to filter by" + } } + }, + "logSettings": { + "label": "Filter log level", + "filterBySeverity": "Filter logs by severity", + "loading": "Loading", + "loading.desc": "When the log pane is scrolled to the bottom, new logs automatically stream as they are added.", + "disableLogStreaming": "Disable log streaming", + "allLogs": "All logs" + }, + "trackedObjectDelete": { + "title": "Confirm Delete", + "desc": "Deleting these {{objectLength}} tracked objects removes the snapshot, any saved embeddings, and any associated object lifecycle entries. Recorded footage of these tracked objects in History view will NOT be deleted.

Are you sure you want to proceed?

Hold the Shift key to bypass this dialog in the future.", + "toast": { + "success": "Tracked objects deleted successfully.", + "error": "Failed to delete tracked objects: {{errorMessage}}" + } + }, + "zoneMask": { + "filterBy": "Filter by zone mask" } } diff --git a/web/public/locales/en/components/input.json b/web/public/locales/en/components/input.json new file mode 100644 index 000000000..6cb0ea10a --- /dev/null +++ b/web/public/locales/en/components/input.json @@ -0,0 +1,10 @@ +{ + "button": { + "downloadVideo": { + "label": "Download Video", + "toast": { + "success": "Your review item video has started downloading." + } + } + } +} \ No newline at end of file diff --git a/web/public/locales/en/components/player.json b/web/public/locales/en/components/player.json index 90b197826..7e4fec646 100644 --- a/web/public/locales/en/components/player.json +++ b/web/public/locales/en/components/player.json @@ -1,5 +1,9 @@ { "noRecordingsFoundForThisTime": "No recordings found for this time", "noPreviewFound": "No Preview Found", - "noPreviewFoundFor": "No Preview Found for {{cameraName}}" + "noPreviewFoundFor": "No Preview Found for {{cameraName}}", + "submitFrigatePlus": { + "title": "Submit this frame to Frigate+?", + "submit": "Submit" + } } \ No newline at end of file diff --git a/web/public/locales/en/views/events.json b/web/public/locales/en/views/events.json index 85a8bdde2..332817b00 100644 --- a/web/public/locales/en/views/events.json +++ b/web/public/locales/en/views/events.json @@ -12,8 +12,10 @@ "motion": "No motion data found" }, "timeline": "Timeline", + "timeline.aria": "Select timeline", "events": { "label": "Events", + "aria": "Select events", "noFoundForTimePeriod": "No events found for this time period." }, "documentTitle": "Review - Frigate", @@ -22,5 +24,12 @@ }, "calendarFilter": { "last24Hours": "Last 24 Hours" - } + }, + "markAsReviewed": "Mark as Reviewed", + "markTheseItemsAsReviewed": "Mark these items as reviewed", + "newReviewItems": { + "label": "View new review items", + "button": "New Items To Review" + }, + "camera": "Camera" } diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index d6839fdcd..29e0665dd 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -34,6 +34,43 @@ "video": "video", "object_lifecycle": "object lifecycle" }, + "objectLifecycle": { + "title": "Object Lifecycle", + "noImageFound": "No image found for this timestamp.", + "createObjectMask": "Create Object Mask", + "adjustAnnotationSettings": "Adjust annotation settings", + "scrollViewTips": "Scroll to view the significant moments of this object's lifecycle.", + "autoTrackingTips": "Bounding box positions will be inaccurate for autotracking cameras.", + "lifecycleItemDesc": { + "visible": "{{label}} detected", + "entered_zone": "{{label}} entered {{zones}}", + "active": "{{label}} became active", + "stationary": "{{label}} became stationary", + "attribute": { + "faceOrLicense_plate": "{{attribute}} detected for {{label}}", + "other": "{{label}} recognized as {{attribute}}" + }, + "gone": "{{label}} left", + "heard": "{{label}} heard", + "external": "{{label}} detected" + }, + "annotationSettings": { + "title": "Annotation Settings", + "showAllZones": "Show All Zones", + "showAllZones.desc": "Always show zones on frames where objects have entered a zone.", + "offset": { + "label": "Annotation Offset", + "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 annotation_offset field can be used to adjust this.", + "documentation": "Read the documentation ", + "millisecondsToOffset": "Milliseconds to offset detect annotations by. Default: 0", + "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." + } + }, + "carousel": { + "previous": "Previous slide", + "next": "Next slide" + } + }, "details": { "item": { "title": "Review Item Details", @@ -68,6 +105,8 @@ "aiTips": "Frigate will not request a description from your Generative AI provider until the tracked object's lifecycle has ended." }, "button.regenerate": "Regenerate", + "button.regenerate.label": "Regenerate tracked object description", + "expandRegenerationMenu": "Expand regeneration menu", "regenerateFromSnapshot": "Regenerate from Snapshot", "regenerateFromThumbnails": "Regenerate from Thumbnails", "tips": { @@ -99,6 +138,9 @@ "viewInHistory": { "label": "View in History", "aria": "View in History" + }, + "deleteTrackedObject": { + "label": "Delete this tracked object" } }, "dialog": { diff --git a/web/public/locales/en/views/exports.json b/web/public/locales/en/views/exports.json index bf2b228af..60cf2b7fe 100644 --- a/web/public/locales/en/views/exports.json +++ b/web/public/locales/en/views/exports.json @@ -3,5 +3,11 @@ "search": "Search", "noExports": "No exports found", "deleteExport": "Delete Export", - "deleteExport.desc": "Are you sure you want to delete {{exportName}}?" + "deleteExport.desc": "Are you sure you want to delete {{exportName}}?", + "editExport": { + "title": "Rename Export", + "desc": "Enter a new name for this export.", + "saveExport": "Save Export" + } + } \ No newline at end of file diff --git a/web/public/locales/en/views/live.json b/web/public/locales/en/views/live.json index f5c33bd4f..41ac714b7 100644 --- a/web/public/locales/en/views/live.json +++ b/web/public/locales/en/views/live.json @@ -12,6 +12,11 @@ }, "ptz": { "move": { + "clickMove": { + "label": "Click in the frame to center the camera", + "enable": "Enable click to move", + "disable": "Disable click to move" + }, "left": { "label": "Move PTZ camera to the left" }, @@ -37,7 +42,8 @@ "center": { "label": "Click in the frame to center the PTZ camera" } - } + }, + "presets": "PTZ camera presets" }, "camera": { "enable": "Enable Camera", @@ -118,8 +124,7 @@ "playInBackground": { "label": "Play in background", "tips": "Enable this option to continue streaming when the player is hidden." - }, - "": "" + } }, "cameraSettings": { "title": "{{camera}} Settings", @@ -129,5 +134,16 @@ "snapshots": "Snapshots", "audioDetection": "Audio Detection", "autotracking": "Autotracking" + }, + "history": { + "label": "Show historical footage" + }, + "effectiveRetainMode": { + "modes": { + "all": "All", + "motion": "Motion", + "active_objects": "Active Objects" + }, + "notAllTips": "Your {{source}} recording retention configuration is set to mode: {{effectiveRetainMode}}, so this on-demand recording will only keep segments with {{effectiveRetainModeName}}." } } diff --git a/web/public/locales/en/views/recording.json b/web/public/locales/en/views/recording.json index d8bd373a9..9033d7819 100644 --- a/web/public/locales/en/views/recording.json +++ b/web/public/locales/en/views/recording.json @@ -2,6 +2,7 @@ "export": "Export", "calendar": "Calendar", "filter": "Filter", + "filters": "Filters", "toast": { "error": { "noValidTimeSelected": "No valid time range selected", diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 496622f0d..06f024f9d 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -122,6 +122,17 @@ "inertia.error.mustBeAboveZero": "Inertia must be above 0.", "loiteringTime.error.mustBeGreaterOrEqualZero": "Loitering time must be greater than or equal to 0.", "polygonDrawing": { + "removeLastPoint": "Remove last point", + "reset.label": "Clear all points", + "snapPoints": { + "true": "Snap points", + "false": "Don't Snap points" + }, + "delete": { + "title": "Confirm Delete", + "desc": "Are you sure you want to delete the {{type}} {{name}}?", + "success": "{{name}} has been deleted." + }, "error": { "mustBeFinished": "Polygon drawing must be finished before saving." } diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json index 6bc50402e..c33dff7ef 100644 --- a/web/public/locales/en/views/system.json +++ b/web/public/locales/en/views/system.json @@ -1,7 +1,23 @@ { "title": "System", "metrics": "System metrics", - "logs": "System logs", + "logs": { + "download": { + "label": "Download Logs" + }, + "copy": { + "label": "Copy to Clipboard", + "success": "Copied logs to clipboard", + "error": "Could not copy logs to clipboard" + }, + "type": { + "label": "Type", + "timestamp": "Timestamp", + "tag": "Tag", + "message": "Message" + }, + "tips": "Logs are streaming from the server" + }, "general": { "title": "General", "detector": { @@ -15,7 +31,24 @@ "gpuUsage": "GPU Usage", "gpuMemory": "GPU Memory", "gpuEncoder": "GPU Encoder", - "gpuDecoder": "GPU Decoder" + "gpuDecoder": "GPU Decoder", + "gpuInfo": { + "vainfoOutput": { + "title": "Vainfo Output", + "returnCode": "Return Code: {{code}}", + "processOutput": "Process Output:", + "processError": "Process Error:" + }, + "nvidiaSMIOutput": { + "title": "Nvidia SMI Output", + "name": "Name: {{name}}", + "driver": "Driver: {{driver}}", + "cudaComputerCapability": "CUDA Compute Capability: {{cuda_compute}}", + "vbios": "VBios Info: {{vbios}}" + }, + "closeInfo.label": "Close GPU info", + "copyInfo.label": "Close GPU info" + } }, "otherProcesses": { "title": "Other Processes", @@ -28,12 +61,14 @@ "overview": "Overview", "recordings": { "title": "Recordings", - "tips": "This value represents the total storage used by the recordings in Frigate's database. Frigate does not track storage usage for all files on your disk." + "tips": "This value represents the total storage used by the recordings in Frigate's database. Frigate does not track storage usage for all files on your disk.", + "earliestRecording": "Earliest recording available:" }, "cameraStorage": { "title": "Camera Storage", "camera": "Camera", "unused": "Unused", + "unusedStorageInformation": "Unused Storage Information", "storageUsed": "Storage Used", "percentageOfTotalUsed": "Percentage of Total Used", "bandwidth": "Bandwidth", diff --git a/web/public/locales/zh-CN/common.json b/web/public/locales/zh-CN/common.json index 4a9d17f45..64d7c84a1 100644 --- a/web/public/locales/zh-CN/common.json +++ b/web/public/locales/zh-CN/common.json @@ -48,6 +48,14 @@ "kph": "英里/小时" } }, + "pagination": { + "label": "分页", + "previous": "上一页", + "previous.label": "转到上一页", + "next": "下一页", + "next.label": "转到下一页", + "more": "更多页面" + }, "button": { "apply": "应用", "reset": "重置", @@ -75,7 +83,11 @@ "download": "下载", "info": "信息", "suspended": "已暂停", - "unsuspended": "取消暂停" + "unsuspended": "取消暂停", + "play": "播放", + "unselect": "取消选择", + "export": "导出", + "deleteNow": "立即删除" }, "menu": { "system": "系统", @@ -87,13 +99,15 @@ "languages": "languages / 语言", "language": { "en": "English", - "zhCN": "简体中文" + "zhCN": "简体中文", + "withSystem.label": "使用系统语言设置" }, "appearance": "外观", "darkMode": { "label": "深色模式", "light": "浅色", - "dark": "深色" + "dark": "深色", + "withSystem.label": "使用系统深色模式设置" }, "withSystem": "跟随系统", "theme": { diff --git a/web/public/locales/zh-CN/components/auth.json b/web/public/locales/zh-CN/components/auth.json new file mode 100644 index 000000000..3e9a163b7 --- /dev/null +++ b/web/public/locales/zh-CN/components/auth.json @@ -0,0 +1,15 @@ +{ + "form": { + "user": "用户名", + "password": "密码", + "login": "登录", + "errors": { + "usernameRequired": "用户名不能为空", + "passwordRequired": "密码不能为空", + "rateLimit": "超出请求限制,请稍后再试。", + "loginFailed": "登录失败", + "unknownError": "未知错误,请检查日志。", + "webUnkownError": "未知错误,请检查控制台日志。" + } + } +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/components/camera.json b/web/public/locales/zh-CN/components/camera.json index 756c8f79d..c8a95adec 100644 --- a/web/public/locales/zh-CN/components/camera.json +++ b/web/public/locales/zh-CN/components/camera.json @@ -4,6 +4,7 @@ "add": "添加摄像头组", "edit": "编辑摄像头组", "delete": { + "label": "删除摄像头组", "confirm": "确认删除", "confirm.desc": "你确定要删除摄像头组 {{name}} 吗?" }, @@ -25,6 +26,7 @@ "success": "摄像头组({{name}})保存成功。", "camera": { "setting": { + "label": "摄像头视频流设置", "title": "{{cameraName}} 视频流设置", "desc": "更改此摄像头组仪表板的实时视频流选项。这些设置特定于设备/浏览器。", "audioIsAvailable": "此视频流支持音频", @@ -57,5 +59,19 @@ } } } + }, + "debug": { + "options": { + "label": "设置", + "title": "选项", + "showOptions": "显示选项", + "hideOptions": "隐藏选项" + }, + "boundingBox": "边界框", + "timestamp": "时间戳", + "zones": "区域", + "mask": "遮罩", + "motion": "运动", + "regions": "区域" } } diff --git a/web/public/locales/zh-CN/components/dialog.json b/web/public/locales/zh-CN/components/dialog.json index 28041b01d..54a669f17 100644 --- a/web/public/locales/zh-CN/components/dialog.json +++ b/web/public/locales/zh-CN/components/dialog.json @@ -15,8 +15,10 @@ "desc": "您希望避开的地点中的物体不应被视为误报。若将其作为误报提交,可能会导致AI模型容易混淆相关物体的识别。" }, "review": { + "true.label": "为 Frigate Plus 确认此标签", "true_one": "这是 {{label}}", "true_other": "这是 {{label}}", + "false.label": "不为 Frigate Plus 确认此标签", "false_one": "这不是 {{label}}", "false_other": "这不是 {{label}}", "state.submitted": "已提交" @@ -31,13 +33,18 @@ "fromTimeline": "从时间线选择", "lastHour_one": "最后1小时", "lastHour_other": "最后 {{count}} 小时", - "custom": "自定义" + "custom": "自定义", + "start": "开始时间", + "start.label": "选择开始时间", + "end": "结束时间", + "end.label": "选择结束时间" }, "name": { "placeholder": "导出项目的名字" }, "select": "选择", "export": "导出", + "selectOrExport": "选择或导出", "toast": { "success": "导出成功。进入 /exports 目录查看文件。", "error": { @@ -70,13 +77,15 @@ "desc": "请为此已保存的搜索提供一个名称。", "placeholder": "请输入搜索名称", "overwrite": "{{searchName}} 已存在。保存将覆盖现有值。", - "success": "搜索 ({{searchName}}) 已保存。" + "success": "搜索 ({{searchName}}) 已保存。", + "button.save.label": "保存此搜索" } }, "recording": { "confirmDelete": { "title": "确认删除", - "desc": "您确定要删除与此审核项相关的所有录制视频吗?

提示:按住 Shift 键点击删除可跳过此对话框。" + "desc": "您确定要删除与此审核项相关的所有录制视频吗?

提示:按住 Shift 键点击删除可跳过此对话框。", + "desc.selected": "您确定要删除与此审核项相关的所有录制视频吗?

提示:按住 Shift 键点击删除可跳过此对话框。" }, "button": { "export": "导出", diff --git a/web/public/locales/zh-CN/components/filter.json b/web/public/locales/zh-CN/components/filter.json index 0c9315c23..a051e5f2b 100644 --- a/web/public/locales/zh-CN/components/filter.json +++ b/web/public/locales/zh-CN/components/filter.json @@ -1,6 +1,7 @@ { "filter": "过滤器", "labels": { + "label": "标签", "all": "所有标签", "all.short": "标签", "count": "{{count}} 个标签" @@ -14,6 +15,7 @@ "all.short": "日期" }, "more": "更多筛选项", + "reset.label": "重置筛选器为默认值", "timeRange": "时间范围", "zones.label": "区域", "subLabels": { @@ -42,12 +44,16 @@ "relevance": "关联性" }, "cameras": { + "label": "摄像头筛选", "all": "所有摄像头", "all.short": "摄像头" }, "review": { "showReviewed": "显示已查看的项目" }, + "motion": { + "showMotionOnly": "仅显示运动" + }, "explore": { "settings": { "title": "设置", @@ -64,8 +70,31 @@ "thumbnailImage": "缩略图", "description": "描述" } + }, + "date": { + "selectDateBy": { + "label": "选择日期进行筛选" + } } } + }, + "logSettings": { + "label": "日志级别筛选", + "filterBySeverity": "按严重程度筛选日志", + "loading": "加载中", + "loading.desc": "当日志面板滚动到底部时,新的日志会自动流式加载。", + "disableLogStreaming": "禁用日志流式加载", + "allLogs": "所有日志" + }, + "trackedObjectDelete": { + "title": "确认删除", + "desc": "删除这 {{objectLength}} 个跟踪对象将移除快照、任何已保存的嵌入和任何相关的对象生命周期条目。历史视图中这些跟踪对象的录制片段将不会被删除。

您确定要继续吗?

按住 Shift 键可在将来跳过此对话框。", + "toast": { + "success": "跟踪对象删除成功。", + "error": "删除跟踪对象失败:{{errorMessage}}" + } + }, + "zoneMask": { + "filterBy": "按区域遮罩筛选" } - } diff --git a/web/public/locales/zh-CN/components/input.json b/web/public/locales/zh-CN/components/input.json index 544b7b4dd..3b4478076 100644 --- a/web/public/locales/zh-CN/components/input.json +++ b/web/public/locales/zh-CN/components/input.json @@ -1,3 +1,10 @@ { - + "button": { + "downloadVideo": { + "label": "下载视频", + "toast": { + "success": "下载成功" + } + } + } } \ No newline at end of file diff --git a/web/public/locales/zh-CN/components/player.json b/web/public/locales/zh-CN/components/player.json index c706a15e3..0d582e472 100644 --- a/web/public/locales/zh-CN/components/player.json +++ b/web/public/locales/zh-CN/components/player.json @@ -1,5 +1,9 @@ { "noRecordingsFoundForThisTime": "找不到此次录制", "noPreviewFound": "没有找到预览", - "noPreviewFoundFor": "没有在 {{cameraName}} 下找到预览" + "noPreviewFoundFor": "没有在 {{cameraName}} 下找到预览", + "submitFrigatePlus": { + "title": "提交此帧到 Frigate+?", + "submit": "提交" + } } \ No newline at end of file diff --git a/web/public/locales/zh-CN/views/events.json b/web/public/locales/zh-CN/views/events.json index 964912bb1..cc12c9838 100644 --- a/web/public/locales/zh-CN/views/events.json +++ b/web/public/locales/zh-CN/views/events.json @@ -12,8 +12,10 @@ "motion": "还没有运动类数据" }, "timeline": "时间线", + "timeline.aria": "选择时间线", "events": { "label": "事件", + "aria": "选择事件", "noFoundForTimePeriod": "未找到该时间段的事件。" }, "documentTitle": "预览 - Frigate", @@ -22,5 +24,12 @@ }, "calendarFilter": { "last24Hours": "过去24小时" - } + }, + "markAsReviewed": "标记为已审核", + "markTheseItemsAsReviewed": "将这些项目标记为已审核", + "newReviewItems": { + "label": "查看新的审核项目", + "button": "新的待审核项目" + }, + "camera": "摄像头" } diff --git a/web/public/locales/zh-CN/views/explore.json b/web/public/locales/zh-CN/views/explore.json index 399e99dce..dcca305eb 100644 --- a/web/public/locales/zh-CN/views/explore.json +++ b/web/public/locales/zh-CN/views/explore.json @@ -34,6 +34,43 @@ "video": "视频", "object_lifecycle": "对象生命周期" }, + "objectLifecycle": { + "title": "对象生命周期", + "noImageFound": "未找到此时间戳的图像。", + "createObjectMask": "创建对象遮罩", + "adjustAnnotationSettings": "调整标注设置", + "scrollViewTips": "滚动查看此对象生命周期的重要时刻。", + "autoTrackingTips": "自动跟踪摄像头的边界框位置可能不准确。", + "lifecycleItemDesc": { + "visible": "检测到 {{label}}", + "entered_zone": "{{label}} 进入 {{zones}}", + "active": "{{label}} 变为活动状态", + "stationary": "{{label}} 变为静止状态", + "attribute": { + "faceOrLicense_plate": "检测到 {{label}} 的 {{attribute}}", + "other": "{{label}} 识别为 {{attribute}}" + }, + "gone": "{{label}} 离开", + "heard": "听到 {{label}}", + "external": "检测到 {{label}}" + }, + "annotationSettings": { + "title": "标注设置", + "showAllZones": "显示所有区域", + "showAllZones.desc": "在对象进入区域的帧上始终显示区域。", + "offset": { + "label": "标注偏移", + "desc": "这些数据来自摄像头的检测源,但是叠加在录制源的图像上。这两个流不太可能完全同步。因此,边界框和录像不会完全对齐。但是,可以使用 annotation_offset 字段来调整这个问题。", + "documentation": "阅读文档(英文) ", + "millisecondsToOffset": "检测标注的偏移毫秒数。默认值:0", + "tips": "提示:假设有一个人从左向右走的事件片段。如果事件时间线上的边界框始终在人的左侧,则应该减小该值。同样,如果一个人从左向右走,而边界框始终在人的前面,则应该增加该值。" + } + }, + "carousel": { + "previous": "上一张", + "next": "下一张" + } + }, "details": { "item": { "title": "回放项目详情", @@ -68,6 +105,8 @@ "aiTips": "在跟踪对象的生命周期结束之前,Frigate 不会向您的生成式 AI 提供商请求描述。" }, "button.regenerate": "重新生成", + "button.regenerate.label": "重新生成跟踪对象描述", + "expandRegenerationMenu": "展开重新生成菜单", "regenerateFromSnapshot": "从快照重新生成", "regenerateFromThumbnails": "从缩略图重新生成", "tips": { @@ -99,6 +138,9 @@ "viewInHistory": { "label": "在历史记录中查看", "aria": "在历史记录中查看" + }, + "deleteTrackedObject": { + "label": "删除此跟踪对象" } }, "dialog": { diff --git a/web/public/locales/zh-CN/views/exports.json b/web/public/locales/zh-CN/views/exports.json index b88c86f49..776a79cfb 100644 --- a/web/public/locales/zh-CN/views/exports.json +++ b/web/public/locales/zh-CN/views/exports.json @@ -3,5 +3,10 @@ "search": "搜索", "noExports": "没有找到导出的项目", "deleteExport": "删除导出的项目", - "deleteExport.desc": "你确定要删除 {{exportName}} 吗?" + "deleteExport.desc": "你确定要删除 {{exportName}} 吗?", + "editExport": { + "title": "重命名导出", + "desc": "为此导出项目输入新名称。", + "saveExport": "保存导出" + } } \ No newline at end of file diff --git a/web/public/locales/zh-CN/views/live.json b/web/public/locales/zh-CN/views/live.json index 03230152b..a23ab26ca 100644 --- a/web/public/locales/zh-CN/views/live.json +++ b/web/public/locales/zh-CN/views/live.json @@ -12,6 +12,11 @@ }, "ptz": { "move": { + "clickMove": { + "label": "点击画面以使摄像头居中", + "enable": "启用点击移动", + "disable": "禁用点击移动" + }, "left": { "label": "PTZ摄像头向左移动" }, @@ -37,7 +42,8 @@ "center": { "label": "点击将PTZ摄像头画面居中" } - } + }, + "presets": "PTZ摄像头预设" }, "camera": { "enable": "开启摄像头", @@ -128,5 +134,16 @@ "snapshots": "快照", "audioDetection": "音频检测", "autotracking": "自动跟踪" + }, + "history": { + "label": "显示历史录像" + }, + "effectiveRetainMode": { + "modes": { + "all": "全部", + "motion": "运动", + "active_objects": "活动对象" + }, + "notAllTips": "您的 {{source}} 录制保留配置设置为 mode: {{effectiveRetainMode}},因此此按需录制将仅保留包含 {{effectiveRetainModeName}} 的片段。" } } diff --git a/web/public/locales/zh-CN/views/recording.json b/web/public/locales/zh-CN/views/recording.json index a27769af1..37cab2514 100644 --- a/web/public/locales/zh-CN/views/recording.json +++ b/web/public/locales/zh-CN/views/recording.json @@ -2,6 +2,7 @@ "export": "导出", "calendar": "日历", "filter": "筛选", + "filters": "筛选条件", "toast": { "error": { "noValidTimeSelected": "未选择有效的时间范围", diff --git a/web/public/locales/zh-CN/views/system.json b/web/public/locales/zh-CN/views/system.json index b35bd7ae5..2b68723fe 100644 --- a/web/public/locales/zh-CN/views/system.json +++ b/web/public/locales/zh-CN/views/system.json @@ -1,7 +1,23 @@ { "title": "系统", "metrics": "系统指标", - "logs": "系统日志", + "logs": { + "download": { + "label": "下载日志" + }, + "copy": { + "label": "复制到剪贴板", + "success": "已复制日志到剪贴板", + "error": "无法复制日志到剪贴板" + }, + "type": { + "label": "类型", + "timestamp": "时间戳", + "tag": "标签", + "message": "消息" + }, + "tips": "日志正在从服务器流式传输" + }, "general": { "title": "常规", "detector": { @@ -15,7 +31,24 @@ "gpuUsage": "GPU使用率", "gpuMemory": "GPU显存", "gpuEncoder": "GPU编码", - "gpuDecoder": "GPU解码" + "gpuDecoder": "GPU解码", + "gpuInfo": { + "vainfoOutput": { + "title": "Vainfo 输出", + "returnCode": "返回代码:{{code}}", + "processOutput": "进程输出:", + "processError": "进程错误:" + }, + "nvidiaSMIOutput": { + "title": "Nvidia SMI 输出", + "name": "名称:{{name}}", + "driver": "驱动:{{driver}}", + "cudaComputerCapability": "CUDA计算能力:{{cuda_compute}}", + "vbios": "VBios信息:{{vbios}}" + }, + "closeInfo.label": "关闭GPU信息", + "copyInfo.label": "复制GPU信息" + } }, "otherProcesses": { "title": "其他进程", @@ -28,12 +61,14 @@ "overview": "概览", "recordings": { "title": "录制内容", - "tips": "该值表示 Frigate 数据库中录制内容所使用的总存储空间。Frigate 不会追踪磁盘上所有文件的存储使用情况。" + "tips": "该值表示 Frigate 数据库中录制内容所使用的总存储空间。Frigate 不会追踪磁盘上所有文件的存储使用情况。", + "earliestRecording": "最早的可用录制:" }, "cameraStorage": { "title": "摄像头存储", "camera": "摄像头", "unused": "未使用", + "unusedStorageInformation": "未使用存储信息", "storageUsed": "存储使用", "percentageOfTotalUsed": "总使用率", "bandwidth": "带宽", diff --git a/web/src/components/auth/AuthForm.tsx b/web/src/components/auth/AuthForm.tsx index 85bd6bccb..a90696dd7 100644 --- a/web/src/components/auth/AuthForm.tsx +++ b/web/src/components/auth/AuthForm.tsx @@ -21,16 +21,18 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { AuthContext } from "@/context/auth-context"; +import { useTranslation } from "react-i18next"; interface UserAuthFormProps extends React.HTMLAttributes {} export function UserAuthForm({ className, ...props }: UserAuthFormProps) { + const { t } = useTranslation(["components/auth"]); const [isLoading, setIsLoading] = React.useState(false); const { login } = React.useContext(AuthContext); const formSchema = z.object({ - user: z.string().min(1, "Username is required"), - password: z.string().min(1, "Password is required"), + user: z.string().min(1, t("form.errors.usernameRequired")), + password: z.string().min(1, t("form.errors.passwordRequired")), }); const form = useForm>({ @@ -62,20 +64,20 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { if (axios.isAxiosError(error)) { const err = error as AxiosError; if (err.response?.status === 429) { - toast.error("Exceeded rate limit. Try again later.", { + toast.error(t("form.errors.rateLimit"), { position: "top-center", }); } else if (err.response?.status === 401) { - toast.error("Login failed", { + toast.error(t("form.errors.loginFailed"), { position: "top-center", }); } else { - toast.error("Unknown error. Check logs.", { + toast.error(t("form.errors.unknownError"), { position: "top-center", }); } } else { - toast.error("Unknown error. Check console logs.", { + toast.error(t("form.errors.webUnkownError"), { position: "top-center", }); } @@ -92,7 +94,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { name="user" render={({ field }) => ( - User + {t("form.user")} ( - Password + {t("form.password")} {isLoading && } - Login + {t("form.login")} diff --git a/web/src/components/button/DownloadVideoButton.tsx b/web/src/components/button/DownloadVideoButton.tsx index 750b35607..094cf9308 100644 --- a/web/src/components/button/DownloadVideoButton.tsx +++ b/web/src/components/button/DownloadVideoButton.tsx @@ -3,6 +3,7 @@ import { toast } from "sonner"; import { FaDownload } from "react-icons/fa"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; type DownloadVideoButtonProps = { source: string; @@ -17,6 +18,7 @@ export function DownloadVideoButton({ startTime, className, }: DownloadVideoButtonProps) { + const { t } = useTranslation(["components/input"]); const formattedDate = formatUnixTimestampToDateTime(startTime, { strftime_fmt: "%D-%T", time_style: "medium", @@ -25,7 +27,7 @@ export function DownloadVideoButton({ const filename = `${camera}_${formattedDate}.mp4`; const handleDownloadStart = () => { - toast.success("Your review item video has started downloading.", { + toast.success(t("button.downloadVideo.toast.success"), { position: "top-center", }); }; @@ -36,7 +38,7 @@ export function DownloadVideoButton({ asChild className="flex items-center gap-2" size="sm" - aria-label="Download Video" + aria-label={t("button.downloadVideo.label")} > ( `${cameraConfig?.name}-feed`, @@ -59,17 +61,21 @@ export default function DebugCameraImage({ onClick={handleToggleSettings} variant="link" size="sm" - aria-label="Settings" + aria-label={t("debug.options.label")} > {" "} - {showSettings ? "Hide" : "Show"} Options + + {showSettings + ? t("debug.options.hideOptions") + : t("debug.options.showOptions")} + {showSettings ? ( - Options + {t("debug.options.title")}
@@ -99,7 +106,7 @@ function DebugSettings({ handleSetOption, options }: DebugSettingsProps) { handleSetOption("bbox", isChecked); }} /> - +
- +
- +
- +
- +
- +
); diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx index a41e2252a..1fc37b36f 100644 --- a/web/src/components/card/AnimatedEventCard.tsx +++ b/web/src/components/card/AnimatedEventCard.tsx @@ -18,6 +18,7 @@ import { Skeleton } from "../ui/skeleton"; import { Button } from "../ui/button"; import { FaCircleCheck } from "react-icons/fa6"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; type AnimatedEventCardProps = { event: ReviewSegment; @@ -29,6 +30,7 @@ export function AnimatedEventCard({ selectedGroup, updateEvents, }: AnimatedEventCardProps) { + const { t } = useTranslation(["views/events"]); const { data: config } = useSWR("config"); const apiHost = useApiHost(); @@ -121,7 +123,7 @@ export function AnimatedEventCard({ - Mark as Reviewed + {t("markAsReviewed")} )} {previews != undefined && ( diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index c47532df8..fcd970904 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -20,6 +20,7 @@ import { MdEditSquare } from "react-icons/md"; import { baseUrl } from "@/api/baseUrl"; import { cn } from "@/lib/utils"; import { shareOrCopy } from "@/utils/browserUtil"; +import { useTranslation } from "react-i18next"; type ExportProps = { className: string; @@ -36,6 +37,7 @@ export default function ExportCard({ onRename, onDelete, }: ExportProps) { + const { t } = useTranslation(["views/exports"]); const [hovered, setHovered] = useState(false); const [loading, setLoading] = useState( exportedRecording.thumb_path.length > 0, @@ -89,10 +91,8 @@ export default function ExportCard({ } }} > - Rename Export - - Enter a new name for this export. - + {t("editExport.title")} + {t("editExport.desc")} {editName && ( <> @@ -207,7 +207,7 @@ export default function ExportCard({ {!exportedRecording.in_progress && ( diff --git a/web/src/components/filter/CalendarFilterButton.tsx b/web/src/components/filter/CalendarFilterButton.tsx index d04adf187..91124d65a 100644 --- a/web/src/components/filter/CalendarFilterButton.tsx +++ b/web/src/components/filter/CalendarFilterButton.tsx @@ -15,6 +15,7 @@ import { DateRange } from "react-day-picker"; import { useState } from "react"; import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import { t } from "i18next"; +import { useTranslation } from "react-i18next"; type CalendarFilterButtonProps = { reviewSummary?: ReviewSummary; @@ -28,16 +29,17 @@ export default function CalendarFilterButton({ day, updateSelectedDay, }: CalendarFilterButtonProps) { + const { t } = useTranslation(["components/filter"]); const [open, setOpen] = useState(false); const selectedDate = useFormattedTimestamp( day == undefined ? 0 : day?.getTime() / 1000 + 1, - t("time.formattedTimestampOnlyMonthAndDay"), + t("time.formattedTimestampOnlyMonthAndDay", { ns: "common" }), ); const trigger = ( ); const content = (
-
Filter
+
{t("filter")}
- Filter logs by severity. + {t("logSettings.filterBySeverity")}
-
Loading
+
{t("logSettings.loading")}
- When the log pane is scrolled to the bottom, new logs - automatically stream as they are added. + {t("logSettings.loading.desc")}
{ setLogSettings({ @@ -105,7 +109,7 @@ export function GeneralFilterContent({ className="mx-2 cursor-pointer text-primary" htmlFor="allLabels" > - All Logs + {t("logSettings.allLogs")} { setSelectedReviews([]); }, [setSelectedReviews]); @@ -68,22 +70,24 @@ export default function ReviewActionGroup({ > - Confirm Delete + + {t("recording.confirmDelete.title")} + - Are you sure you want to delete all recorded video associated with - the selected review items? -
-
- Hold the Shift key to bypass this dialog in the future. + + recording.confirmDelete.desc.selected +
- Cancel + + {t("button.cancel", { ns: "common" })} + - Delete + {t("button.delete", { ns: "common" })}
@@ -97,14 +101,14 @@ export default function ReviewActionGroup({ className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary" onClick={onClearSelected} > - Unselect + {t("button.unselect", { ns: "common" })}
{selectedReviews.length == 1 && ( )} diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index f038e0daa..fe2b4034b 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -286,7 +286,7 @@ function ShowReviewFilter({ -
@@ -624,7 +627,7 @@ function ShowMotionOnlyButton({
diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 6fc9bfe07..454ab71d7 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -253,7 +253,6 @@ function GeneralFilterButton({ return t("labels.count", { count: selectedLabels.length, - ns: "components/filter", }); }, [selectedLabels, t]); @@ -270,7 +269,7 @@ function GeneralFilterButton({ size="sm" variant={selectedLabels?.length ? "select" : "default"} className="flex items-center gap-2 capitalize" - aria-label="Labels" + aria-label={t("labels.label")} >
); diff --git a/web/src/components/graph/CombinedStorageGraph.tsx b/web/src/components/graph/CombinedStorageGraph.tsx index e579d65bc..5e988b52b 100644 --- a/web/src/components/graph/CombinedStorageGraph.tsx +++ b/web/src/components/graph/CombinedStorageGraph.tsx @@ -205,11 +205,15 @@ export function CombinedStorageGraph({ diff --git a/web/src/components/icons/IconPicker.tsx b/web/src/components/icons/IconPicker.tsx index 4e83cbb93..b74029e0b 100644 --- a/web/src/components/icons/IconPicker.tsx +++ b/web/src/components/icons/IconPicker.tsx @@ -71,7 +71,7 @@ export default function IconPicker({ {!selectedIcon?.name || !selectedIcon?.Icon ? ( diff --git a/web/src/components/input/SaveSearchDialog.tsx b/web/src/components/input/SaveSearchDialog.tsx index 348f9e1c4..5eebdda74 100644 --- a/web/src/components/input/SaveSearchDialog.tsx +++ b/web/src/components/input/SaveSearchDialog.tsx @@ -79,14 +79,17 @@ export function SaveSearchDialog({
)} - diff --git a/web/src/components/menu/AccountSettings.tsx b/web/src/components/menu/AccountSettings.tsx index 03adfbe12..408802b34 100644 --- a/web/src/components/menu/AccountSettings.tsx +++ b/web/src/components/menu/AccountSettings.tsx @@ -117,7 +117,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) { className={ isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Set Password" + aria-label={t("menu.user.setPassword")} onClick={() => setPasswordDialogOpen(true)} > @@ -128,7 +128,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) { className={ isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Log out" + aria-label={t("menu.user.logout")} >
diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index 185845d20..b4d3479e7 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -182,7 +182,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Set Password" + aria-label={t("menu.user.setPassword")} onClick={() => setPasswordDialogOpen(true)} > @@ -195,7 +195,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Log out" + aria-label={t("menu.user.logout", { ns: "common" })} > @@ -216,7 +216,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex w-full items-center p-2 text-sm" } - aria-label="System metrics" + aria-label={t("menu.systemMetrics")} > {t("menu.systemMetrics")} @@ -229,7 +229,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex w-full items-center p-2 text-sm" } - aria-label="System logs" + aria-label={t("menu.systemLogs")} > {t("menu.systemLogs")} @@ -252,7 +252,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex w-full items-center p-2 text-sm" } - aria-label="Settings" + aria-label={t("menu.settings")} > {t("menu.settings")} @@ -267,7 +267,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex w-full items-center p-2 text-sm" } - aria-label="Configuration editor" + aria-label={t("menu.configurationEditor")} > {t("menu.configurationEditor")} @@ -340,7 +340,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Use the system settings for language" + aria-label={t("menu.language.withSystem.label")} onClick={() => setLanguage(systemLanguage)} > {language === systemLanguage ? ( @@ -377,7 +377,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Light mode" + aria-label={t("menu.darkMode.light")} onClick={() => setTheme("light")} > {theme === "light" ? ( @@ -397,7 +397,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Dark mode" + aria-label={t("menu.darkMode.dark")} onClick={() => setTheme("dark")} > {theme === "dark" ? ( @@ -417,7 +417,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Use the system settings for light or dark mode" + aria-label={t("menu.darkMode.withSystem.label")} onClick={() => setTheme("system")} > {theme === "system" ? ( @@ -514,7 +514,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { ? "cursor-pointer" : "flex items-center p-2 text-sm" } - aria-label="Restart Frigate" + aria-label={t("menu.restart")} onClick={() => setRestartDialogOpen(true)} > diff --git a/web/src/components/menu/SearchResultActions.tsx b/web/src/components/menu/SearchResultActions.tsx index 2f45ab698..9afd4b1cf 100644 --- a/web/src/components/menu/SearchResultActions.tsx +++ b/web/src/components/menu/SearchResultActions.tsx @@ -150,7 +150,7 @@ export default function SearchResultActions({ )} setDeleteDialogOpen(true)} > diff --git a/web/src/components/mobile/MobilePage.tsx b/web/src/components/mobile/MobilePage.tsx index 169b5e524..22305614a 100644 --- a/web/src/components/mobile/MobilePage.tsx +++ b/web/src/components/mobile/MobilePage.tsx @@ -5,6 +5,7 @@ import { IoMdArrowRoundBack } from "react-icons/io"; import { cn } from "@/lib/utils"; import { isPWA } from "@/utils/isPWA"; import { Button } from "@/components/ui/button"; +import { t } from "i18next"; const MobilePageContext = createContext<{ open: boolean; @@ -160,7 +161,7 @@ export function MobilePageHeader({ >
@@ -81,34 +94,52 @@ export default function GPUInfoDialog({ - Nvidia SMI Output + + {t("general.hardwareInfo.gpuInfo.nvidiaSMIOutput.title")} + {nvinfo ? (
-
Name: {nvinfo["0"].name}
+
+ {t("general.hardwareInfo.gpuInfo.nvidiaSMIOutput.name", { + name: nvinfo["0"].name, + })} +

-
Driver: {nvinfo["0"].driver}
+
+ {t("general.hardwareInfo.gpuInfo.nvidiaSMIOutput.name", { + name: nvinfo["0"].driver, + })} +

-
Cuda Compute Capability: {nvinfo["0"].cuda_compute}
+
+ {t("general.hardwareInfo.gpuInfo.nvidiaSMIOutput.name", { + name: nvinfo["0"].cuda_compute, + })} +

-
VBios Info: {nvinfo["0"].vbios}
+
+ {t("general.hardwareInfo.gpuInfo.nvidiaSMIOutput.name", { + name: nvinfo["0"].vbios, + })} +
) : ( )}
diff --git a/web/src/components/overlay/MobileCameraDrawer.tsx b/web/src/components/overlay/MobileCameraDrawer.tsx index c12bc0ab2..49ccf739a 100644 --- a/web/src/components/overlay/MobileCameraDrawer.tsx +++ b/web/src/components/overlay/MobileCameraDrawer.tsx @@ -3,6 +3,7 @@ import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Button } from "../ui/button"; import { FaVideo } from "react-icons/fa"; import { isMobile } from "react-device-detect"; +import { t } from "i18next"; type MobileCameraDrawerProps = { allCameras: string[]; @@ -25,7 +26,7 @@ export default function MobileCameraDrawer({ diff --git a/web/src/components/overlay/SetPasswordDialog.tsx b/web/src/components/overlay/SetPasswordDialog.tsx index 3bdef6cfa..bb04e2690 100644 --- a/web/src/components/overlay/SetPasswordDialog.tsx +++ b/web/src/components/overlay/SetPasswordDialog.tsx @@ -201,7 +201,7 @@ export default function SetPasswordDialog({
) : ( - "Save" + t("button.save", { ns: "common" }) )} diff --git a/web/src/components/overlay/detail/ObjectLifecycle.tsx b/web/src/components/overlay/detail/ObjectLifecycle.tsx index 47e2ed318..1809ff56f 100644 --- a/web/src/components/overlay/detail/ObjectLifecycle.tsx +++ b/web/src/components/overlay/detail/ObjectLifecycle.tsx @@ -54,6 +54,7 @@ import { useNavigate } from "react-router-dom"; import { ObjectPath } from "./ObjectPath"; import { getLifecycleItemDescription } from "@/utils/lifecycleUtil"; import { IoPlayCircleOutline } from "react-icons/io5"; +import { useTranslation } from "react-i18next"; type ObjectLifecycleProps = { className?: string; @@ -68,6 +69,8 @@ export default function ObjectLifecycle({ fullscreen = false, setPane, }: ObjectLifecycleProps) { + const { t } = useTranslation(["views/explore"]); + const { data: eventSequence } = useSWR([ "timeline", { @@ -334,12 +337,16 @@ export default function ObjectLifecycle({
)} @@ -361,7 +368,7 @@ export default function ObjectLifecycle({
- No image found for this timestamp. + {t("objectLifecycle.noImageFound")}
)} @@ -468,7 +475,9 @@ export default function ObjectLifecycle({ ) } > -
Create Object Mask
+
+ {t("objectLifecycle.createObjectMask")} +
@@ -477,7 +486,7 @@ export default function ObjectLifecycle({
- Object Lifecycle + {t("objectLifecycle.title")}
@@ -485,7 +494,7 @@ export default function ObjectLifecycle({
- Scroll to view the significant moments of this object's lifecycle. + {t("objectLifecycle.scrollViewTips")}
{current + 1} of {eventSequence.length} @@ -509,7 +520,7 @@ export default function ObjectLifecycle({
{config?.cameras[event.camera]?.onvif.autotracking.enabled_in_config && (
- Bounding box positions will be inaccurate for autotracking cameras. + {t("objectLifecycle.autoTrackingTips")}
)} {showControls && ( @@ -559,8 +570,8 @@ export default function ObjectLifecycle({ timezone: config.ui.timezone, strftime_fmt: config.ui.time_format == "24hour" - ? "%d %b %H:%M:%S" - : "%m/%d %I:%M:%S%P", + ? t("time.formattedTimestamp2.24hour") + : t("time.formattedTimestamp2"), time_style: "medium", date_style: "medium", })} diff --git a/web/src/components/overlay/detail/ReviewDetailDialog.tsx b/web/src/components/overlay/detail/ReviewDetailDialog.tsx index c014d2ad7..f27543484 100644 --- a/web/src/components/overlay/detail/ReviewDetailDialog.tsx +++ b/web/src/components/overlay/detail/ReviewDetailDialog.tsx @@ -193,7 +193,7 @@ export default function ReviewDetailDialog({ @@ -695,14 +695,14 @@ function ObjectDetailsTab({ regenerateDescription("snapshot")} > {t("details.regenerateFromSnapshot")} regenerateDescription("thumbnails")} > {t("details.regenerateFromThumbnails")} @@ -716,7 +716,7 @@ function ObjectDetailsTab({ !config?.cameras[search.camera].genai.enabled) && ( - Remove last point + + {t("masksAndZones.form.polygonDrawing.removeLastPoint")} + - Reset + {t("button.reset", { ns: "common" })} - {snapPoints ? "Don't snap points" : "Snap points"} + {snapPoints + ? t("masksAndZones.form.polygonDrawing.snapPoints.false") + : t("masksAndZones.form.polygonDrawing.snapPoints.true")}
diff --git a/web/src/components/settings/PolygonItem.tsx b/web/src/components/settings/PolygonItem.tsx index 57dafab95..b3cd6aea6 100644 --- a/web/src/components/settings/PolygonItem.tsx +++ b/web/src/components/settings/PolygonItem.tsx @@ -36,7 +36,7 @@ import { reviewQueries } from "@/utils/zoneEdutUtil"; import IconWrapper from "../ui/icon-wrapper"; import { StatusBarMessagesContext } from "@/context/statusbar-provider"; import { buttonVariants } from "../ui/button"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; type PolygonItemProps = { polygon: Polygon; @@ -177,14 +177,25 @@ export default function PolygonItem({ .put(`config/set?${url}`, { requires_restart: 0 }) .then((res) => { if (res.status === 200) { - toast.success(`${polygon?.name} has been deleted.`, { - position: "top-center", - }); + toast.success( + t("masksAndZones.form.polygonDrawing.delete.success", { + name: polygon?.name, + }), + { + position: "top-center", + }, + ); updateConfig(); } else { - toast.error(`Failed to save config changes: ${res.statusText}`, { - position: "top-center", - }); + toast.error( + t("toast.save.error", { + errorMessage: res.statusText, + ns: "common", + }), + { + position: "top-center", + }, + ); } }) .catch((error) => { @@ -192,7 +203,7 @@ export default function PolygonItem({ error.response?.data?.message || error.response?.data?.detail || "Unknown error"; - toast.error(`Failed to save config changes: ${errorMessage}`, { + toast.error(t("toast.save.error", { errorMessage, ns: "common" }), { position: "top-center", }); }) @@ -200,7 +211,7 @@ export default function PolygonItem({ setIsLoading(false); }); }, - [updateConfig, cameraConfig], + [updateConfig, cameraConfig, t], ); const handleDelete = () => { @@ -255,19 +266,30 @@ export default function PolygonItem({ > - Confirm Delete + + {t("masksAndZones.form.polygonDrawing.delete.title")} + - Are you sure you want to delete the{" "} - {polygon.type.replace("_", " ")} {polygon.name}? + + masksAndZones.form.polygonDrawing.delete.desc + - Cancel + + {t("button.cancel", { ns: "common" })} + - Delete + {t("button.delete", { ns: "common" })} @@ -281,26 +303,26 @@ export default function PolygonItem({ { setActivePolygonIndex(index); setEditPane(polygon.type); }} > - Edit + {t("button.edit", { ns: "common" })} handleCopyCoordinates(index)} > - Copy + {t("button.copy", { ns: "common" })} setDeleteDialogOpen(true)} > - Delete + {t("button.delete", { ns: "common" })} diff --git a/web/src/components/settings/SearchSettings.tsx b/web/src/components/settings/SearchSettings.tsx index 72c26ec6d..aff3bdaf6 100644 --- a/web/src/components/settings/SearchSettings.tsx +++ b/web/src/components/settings/SearchSettings.tsx @@ -48,7 +48,7 @@ export default function ExploreSettings({ const trigger = ( diff --git a/web/src/components/ui/carousel.tsx b/web/src/components/ui/carousel.tsx index 7667f4e83..ea38705ee 100644 --- a/web/src/components/ui/carousel.tsx +++ b/web/src/components/ui/carousel.tsx @@ -6,6 +6,7 @@ import { ArrowLeft, ArrowRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; +import { useTranslation } from "react-i18next"; type CarouselApi = UseEmblaCarouselType[1]; type UseCarouselParameters = Parameters; @@ -196,6 +197,7 @@ const CarouselPrevious = React.forwardRef< HTMLButtonElement, React.ComponentProps >(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { t } = useTranslation(["views/explore"]); const { orientation, scrollPrev, canScrollPrev } = useCarousel(); return ( @@ -210,13 +212,13 @@ const CarouselPrevious = React.forwardRef< : "-top-12 left-1/2 -translate-x-1/2 rotate-90", className, )} - aria-label="Previous slide" + aria-label={t("objectLifecycle.carousel.previous")} disabled={!canScrollPrev} onClick={scrollPrev} {...props} > - Previous slide + {t("objectLifecycle.carousel.previous")} ); }); @@ -226,6 +228,7 @@ const CarouselNext = React.forwardRef< HTMLButtonElement, React.ComponentProps >(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { t } = useTranslation(["views/explore"]); const { orientation, scrollNext, canScrollNext } = useCarousel(); return ( @@ -240,13 +243,13 @@ const CarouselNext = React.forwardRef< : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className, )} - aria-label="Next slide" + aria-label={t("objectLifecycle.carousel.next")} disabled={!canScrollNext} onClick={scrollNext} {...props} > - Next slide + {t("objectLifecycle.carousel.next")} ); }); diff --git a/web/src/components/ui/pagination.tsx b/web/src/components/ui/pagination.tsx index ea40d196d..0926a3d25 100644 --- a/web/src/components/ui/pagination.tsx +++ b/web/src/components/ui/pagination.tsx @@ -3,11 +3,12 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" import { cn } from "@/lib/utils" import { ButtonProps, buttonVariants } from "@/components/ui/button" +import { t } from "i18next" const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (