https://...). This is a browser limitation. Access Frigate securely to use notifications.",
+ "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.",
@@ -346,6 +374,15 @@
"cameras.desc": "Select which cameras to enable notifications for.",
"deviceSpecific": "Device Specific Settings",
"registerDevice": "Register This Device",
- "unregisterDevice": "Unregister This Device"
+ "unregisterDevice": "Unregister This Device",
+ "toast": {
+ "success": {
+ "registered": "Successfully registered for notifications. Restarting Frigate is required before any notifications (including a test notification) can be sent.",
+ "settingSaved": "Notification settings have been saved."
+ },
+ "error": {
+ "registerFailed": "Failed to save notification registration."
+ }
+ }
}
}
diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json
index c33dff7ef..78efe0719 100644
--- a/web/public/locales/en/views/system.json
+++ b/web/public/locales/en/views/system.json
@@ -16,7 +16,13 @@
"tag": "Tag",
"message": "Message"
},
- "tips": "Logs are streaming from the server"
+ "tips": "Logs are streaming from the server",
+ "toast": {
+ "error": {
+ "fetchingLogsFailed": "Error fetching logs: {{errorMessage}}",
+ "whileStreamingLogs": "Error while streaming logs: {{errorMessage}}"
+ }
+ }
},
"general": {
"title": "General",
@@ -47,7 +53,10 @@
"vbios": "VBios Info: {{vbios}}"
},
"closeInfo.label": "Close GPU info",
- "copyInfo.label": "Close GPU info"
+ "copyInfo.label": "Copy GPU info",
+ "toast": {
+ "success": "Copied GPU info to clipboard"
+ }
}
},
"otherProcesses": {
@@ -98,6 +107,14 @@
"skipped": "skipped",
"ffmpeg": "ffmpeg",
"capture": "capture"
+ },
+ "toast": {
+ "success": {
+ "copyToClipboard": "Copied probe data to clipboard."
+ },
+ "error": {
+ "unableToProbeCamera": "Unable to probe camera: {{errorMessage}}"
+ }
}
},
"lastRefreshed": "Last refreshed: ",
diff --git a/web/public/locales/zh-CN/common.json b/web/public/locales/zh-CN/common.json
index 64d7c84a1..980e40e9f 100644
--- a/web/public/locales/zh-CN/common.json
+++ b/web/public/locales/zh-CN/common.json
@@ -36,6 +36,8 @@
"second": "{{time}}秒",
"formattedTimestamp": "%m月%-d日 %I:%M:%S %p",
"formattedTimestamp.24hour": "%m月%-d日 %H:%M:%S",
+ "formattedTimestamp2": "%m/%d %I:%M:%S%P",
+ "formattedTimestamp2.24hour": "%d日%m月 %H:%M:%S",
"formattedTimestampExcludeSeconds": "%m月%-d日 %I:%M %p",
"formattedTimestampExcludeSeconds.24hour": "%m月%-d日 %H:%M",
"formattedTimestampWithYear": "%Y年%m月%-d日 %I:%M:%S %p",
@@ -44,10 +46,13 @@
},
"unit": {
"speed": {
- "mph": "公里/小时",
- "kph": "英里/小时"
+ "mph": "英里/小时",
+ "kph": "公里/小时"
}
},
+ "label": {
+ "back": "返回"
+ },
"pagination": {
"label": "分页",
"previous": "上一页",
@@ -140,6 +145,7 @@
"restart": "重启 Frigate"
},
"toast": {
+ "copyUrlToClipboard": "已复制链接到剪贴板。",
"save": {
"error": "保存配置信息失败: {{errorMessage}}",
"error.noMessage": "保存配置信息失败"
diff --git a/web/public/locales/zh-CN/components/player.json b/web/public/locales/zh-CN/components/player.json
index ffb64e0d3..5b70fd856 100644
--- a/web/public/locales/zh-CN/components/player.json
+++ b/web/public/locales/zh-CN/components/player.json
@@ -27,5 +27,13 @@
"droppedFrames.short.value": "{{droppedFrames}} 帧",
"decodedFrames": "解码帧数:",
"droppedFrameRate": "丢帧率:"
+ },
+ "toast": {
+ "success": {
+ "submittedFrigatePlus": "已成功提交帧到 Frigate+"
+ },
+ "error": {
+ "submitFrigatePlusFailed": "提交帧到 Frigate+ 失败"
+ }
}
}
\ No newline at end of file
diff --git a/web/public/locales/zh-CN/views/configEditor.json b/web/public/locales/zh-CN/views/configEditor.json
index 9f3931730..a8ad9292d 100644
--- a/web/public/locales/zh-CN/views/configEditor.json
+++ b/web/public/locales/zh-CN/views/configEditor.json
@@ -2,5 +2,13 @@
"configEditor": "配置编辑器",
"copyConfig": "复制配置",
"saveAndRestart": "保存并重启",
- "saveOnly": "只保存"
+ "saveOnly": "只保存",
+ "toast": {
+ "success": {
+ "copyToClipboard": "配置已复制到剪贴板。"
+ },
+ "error": {
+ "savingError": "保存配置时出错"
+ }
+ }
}
\ No newline at end of file
diff --git a/web/public/locales/zh-CN/views/explore.json b/web/public/locales/zh-CN/views/explore.json
index e35656f24..e221680c2 100644
--- a/web/public/locales/zh-CN/views/explore.json
+++ b/web/public/locales/zh-CN/views/explore.json
@@ -1,4 +1,5 @@
{
+ "generativeAI": "生成式 AI",
"exploreIsUnavailable": {
"title": "探索功能不可用",
"embeddingsReindexing": {
@@ -83,6 +84,16 @@
"mismatch_one": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
"mismatch_other": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
"hasMissingObjects": "如果希望 Frigate 保存以下标签的跟踪对象,请调整您的配置:{{objects}}"
+ },
+ "toast": {
+ "success": {
+ "regenerate": "已向 {{provider}} 请求新的描述。根据提供商的速度,生成新描述可能需要一些时间。",
+ "updatedSublabel": "成功更新子标签。"
+ },
+ "error": {
+ "regenerate": "调用 {{provider}} 生成新描述失败:{{errorMessage}}",
+ "updatedSublabelFailed": "更新子标签失败:{{errorMessage}}"
+ }
}
},
"label": "标签",
diff --git a/web/public/locales/zh-CN/views/exports.json b/web/public/locales/zh-CN/views/exports.json
index 776a79cfb..fe3f849ac 100644
--- a/web/public/locales/zh-CN/views/exports.json
+++ b/web/public/locales/zh-CN/views/exports.json
@@ -8,5 +8,10 @@
"title": "重命名导出",
"desc": "为此导出项目输入新名称。",
"saveExport": "保存导出"
+ },
+ "toast": {
+ "error": {
+ "renameExportFailed": "重命名导出失败:{{errorMessage}}"
+ }
}
}
\ No newline at end of file
diff --git a/web/public/locales/zh-CN/views/faceLibrary.json b/web/public/locales/zh-CN/views/faceLibrary.json
new file mode 100644
index 000000000..72b26a95e
--- /dev/null
+++ b/web/public/locales/zh-CN/views/faceLibrary.json
@@ -0,0 +1,40 @@
+{
+ "uploadFaceImage": {
+ "title": "上传人脸图片",
+ "desc": "上传图片以扫描人脸并包含在{{pageToggle}}中"
+ },
+ "createFaceLibrary": {
+ "title": "创建人脸库",
+ "desc": "创建一个新的人脸库"
+ },
+ "train": {
+ "title": "训练",
+ "aria": "选择训练"
+ },
+ "selectItem": "选择{{item}}",
+ "button": {
+ "deleteFaceAttempts": "尝试删除人脸",
+ "addFace": "添加人脸",
+ "uploadImage": "上传图片",
+ "reprocessFace:": "重新处理人脸"
+ },
+ "trainFaceAs:": "将人脸训练为:",
+ "trainFaceAsPerson:": "将人脸训练为人物",
+
+ "toast": {
+ "success": {
+ "uploadedImage": "图片上传成功。",
+ "addFaceLibrary": "人脸库添加成功。",
+ "deletedFace": "人脸删除成功。",
+ "trainedFace": "人脸训练成功。",
+ "updatedFaceScore": "人脸分数更新成功。"
+ },
+ "error": {
+ "uploadingImageFailed": "图片上传失败:{{errorMessage}}",
+ "addFaceLibraryFailed": "设置人脸名称失败:{{errorMessage}}",
+ "deleteFaceFailed": "删除失败:{{errorMessage}}",
+ "trainFailed": "训练失败:{{errorMessage}}",
+ "updateFaceScoreFailed": "更新人脸分数失败:{{errorMessage}}"
+ }
+ }
+}
diff --git a/web/public/locales/zh-CN/views/settings.json b/web/public/locales/zh-CN/views/settings.json
index acfbb6c86..a57fc87e0 100644
--- a/web/public/locales/zh-CN/views/settings.json
+++ b/web/public/locales/zh-CN/views/settings.json
@@ -47,6 +47,16 @@
"sunday": "星期天",
"monday": "星期一"
}
+ },
+ "toast": {
+ "success": {
+ "clearStoredLayout": "已清除 {{cameraName}} 的存储布局",
+ "clearStreamingSettings": "已清除所有摄像头组的视频流设置。"
+ },
+ "error": {
+ "clearStoredLayoutFailed": "清除存储布局失败:{{errorMessage}}",
+ "clearStreamingSettingsFailed": "清除视频流设置失败:{{errorMessage}}"
+ }
}
},
"explore": {
@@ -107,6 +117,14 @@
"filter": {
"all": "所有遮罩和区域"
},
+ "toast": {
+ "success": {
+ "copyCoordinates": "已复制 {{polyName}} 的坐标到剪贴板。"
+ },
+ "error": {
+ "copyCoordinatesFailed": "无法复制坐标到剪贴板。"
+ }
+ },
"form": {
"zoneName": {
"error": {
@@ -122,6 +140,12 @@
"inertia.error.mustBeAboveZero": "惯性必须大于 0。",
"loiteringTime.error.mustBeGreaterOrEqualZero": "徘徊时间必须大于或等于 0。",
"polygonDrawing": {
+ "removeLastPoint": "删除最后一个点",
+ "reset.label": "清除所有点",
+ "snapPoints": {
+ "true": "启用点对齐",
+ "false": "禁用点对齐"
+ },
"error": {
"mustBeFinished": "多边形绘制必须完成闭合后才能保存。"
}
@@ -262,12 +286,14 @@
"success": {
"createUser": "用户 {{user}} 创建成功",
"deleteUser": "用户 {{user}} 删除成功",
- "updatePassword": "已成功修改密码"
+ "updatePassword": "已成功修改密码",
+ "roleUpdated": "已更新 {{user}} 的权限组"
},
"error": {
"setPasswordFailed": "保存密码出现错误:{{errorMessage}}",
"createUserFailed": "创建用户失败:{{errorMessage}}",
- "deleteUserFailed": "删除用户失败:{{errorMessage}}"
+ "deleteUserFailed": "删除用户失败:{{errorMessage}}",
+ "roleUpdateFailed": "更新权限组失败:{{errorMessage}}"
}
},
"table": {
@@ -326,9 +352,16 @@
},
"notification": {
"title": "通知",
- "notificationSettings": "通知设置",
- "desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
- "documentation": "阅读文档(英文)",
+ "notificationSettings": {
+ "title": "通知设置",
+ "desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
+ "documentation": "阅读文档(英文)"
+ },
+ "notificationUnavailable": {
+ "title": "通知功能不可用",
+ "desc": "网页推送通知需要安全连接(https://...)。这是浏览器的限制。请通过安全方式访问 Frigate 以使用通知功能。",
+ "documentation": "阅读文档(英文)"
+ },
"email": "电子邮箱",
"email.placeholder": "例如:example@email.com",
"email.desc": "需要输入有效的电子邮件,在推送服务出现问题时,将使用此电子邮件进行通知。",
@@ -337,6 +370,15 @@
"cameras.desc": "选择要启用通知的摄像头。",
"deviceSpecific": "设备专用设置",
"registerDevice": "注册该设备",
- "unregisterDevice": "取消注册该设备"
+ "unregisterDevice": "取消注册该设备",
+ "toast": {
+ "success": {
+ "registered": "已成功注册通知。需要重启 Frigate 才能发送任何通知(包括测试通知)。",
+ "settingSaved": "通知设置已保存。"
+ },
+ "error": {
+ "registerFailed": "通知注册失败。"
+ }
+ }
}
}
diff --git a/web/public/locales/zh-CN/views/system.json b/web/public/locales/zh-CN/views/system.json
index 2b68723fe..20d6bba8d 100644
--- a/web/public/locales/zh-CN/views/system.json
+++ b/web/public/locales/zh-CN/views/system.json
@@ -16,7 +16,13 @@
"tag": "标签",
"message": "消息"
},
- "tips": "日志正在从服务器流式传输"
+ "tips": "日志正在从服务器流式传输",
+ "toast": {
+ "error": {
+ "fetchingLogsFailed": "获取日志出错:{{errorMessage}}",
+ "whileStreamingLogs": "流式传输日志时出错:{{errorMessage}}"
+ }
+ }
},
"general": {
"title": "常规",
@@ -47,7 +53,10 @@
"vbios": "VBios信息:{{vbios}}"
},
"closeInfo.label": "关闭GPU信息",
- "copyInfo.label": "复制GPU信息"
+ "copyInfo.label": "复制GPU信息",
+ "toast": {
+ "success": "已复制GPU信息到剪贴板"
+ }
}
},
"otherProcesses": {
@@ -98,6 +107,14 @@
"skipped": "跳过",
"ffmpeg": "ffmpeg编码器",
"capture": "捕获"
+ },
+ "toast": {
+ "success": {
+ "copyToClipboard": "已复制探测数据到剪贴板。"
+ },
+ "error": {
+ "unableToProbeCamera": "无法探测摄像头:{{errorMessage}}"
+ }
}
},
"lastRefreshed": "最后刷新时间:",
diff --git a/web/src/components/overlay/CameraInfoDialog.tsx b/web/src/components/overlay/CameraInfoDialog.tsx
index 76317dd8c..cc13b23ca 100644
--- a/web/src/components/overlay/CameraInfoDialog.tsx
+++ b/web/src/components/overlay/CameraInfoDialog.tsx
@@ -41,15 +41,25 @@ export default function CameraInfoDialog({
if (res.status === 200) {
setFfprobeInfo(res.data);
} else {
- toast.error(`Unable to probe camera: ${res.statusText}`, {
- position: "top-center",
- });
+ toast.error(
+ t("cameras.toast.success.copyToClipboard", {
+ errorMessage: res.statusText,
+ }),
+ {
+ position: "top-center",
+ },
+ );
}
})
.catch((error) => {
- toast.error(`Unable to probe camera: ${error.response.data.message}`, {
- position: "top-center",
- });
+ toast.error(
+ t("cameras.toast.success.copyToClipboard", {
+ errorMessage: error.response.data.message,
+ }),
+ {
+ position: "top-center",
+ },
+ );
});
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -57,7 +67,7 @@ export default function CameraInfoDialog({
const onCopyFfprobe = async () => {
copy(JSON.stringify(ffprobeInfo));
- toast.success("Copied probe data to clipboard.");
+ toast.success(t("cameras.toast.success.copyToClipboard"));
};
function gcd(a: number, b: number): number {
diff --git a/web/src/components/overlay/GPUInfoDialog.tsx b/web/src/components/overlay/GPUInfoDialog.tsx
index d6d227ef6..6a1755c9d 100644
--- a/web/src/components/overlay/GPUInfoDialog.tsx
+++ b/web/src/components/overlay/GPUInfoDialog.tsx
@@ -38,7 +38,7 @@ export default function GPUInfoDialog({
.replace(/\\t/g, "\t")
.replace(/\\n/g, "\n"),
);
- toast.success("Copied GPU info to clipboard.");
+ toast.success(t("general.hardwareInfo.gpuInfo.toast.success"));
};
if (gpuType == "vainfo") {
diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx
index dd775f582..9bc384861 100644
--- a/web/src/components/overlay/detail/SearchDetailDialog.tsx
+++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx
@@ -443,7 +443,12 @@ function ObjectDetailsTab({
.then((resp) => {
if (resp.status == 200) {
toast.success(
- `A new description has been requested from ${capitalizeAll(config?.genai.provider.replaceAll("_", " ") ?? "Generative AI")}. Depending on the speed of your provider, the new description may take some time to regenerate.`,
+ t("details.item.toast.success.regenerate", {
+ provider: capitalizeAll(
+ config?.genai.provider.replaceAll("_", " ") ??
+ t("generativeAI"),
+ ),
+ }),
{
position: "top-center",
duration: 7000,
@@ -457,12 +462,18 @@ function ObjectDetailsTab({
error.response?.data?.detail ||
"Unknown error";
toast.error(
- `Failed to call ${capitalizeAll(config?.genai.provider.replaceAll("_", " ") ?? "Generative AI")} for a new description: ${errorMessage}`,
+ t("details.item.toast.error.regenerate", {
+ provider: capitalizeAll(
+ config?.genai.provider.replaceAll("_", " ") ??
+ t("generativeAI"),
+ ),
+ errorMessage,
+ }),
{ position: "top-center" },
);
});
},
- [search, config],
+ [search, config, t],
);
const handleSubLabelSave = useCallback(
@@ -481,7 +492,7 @@ function ObjectDetailsTab({
})
.then((response) => {
if (response.status === 200) {
- toast.success("Successfully updated sub label.", {
+ toast.success(t("details.item.toast.success.updatedSublabel"), {
position: "top-center",
});
@@ -529,12 +540,17 @@ function ObjectDetailsTab({
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
- toast.error(`Failed to update sub label: ${errorMessage}`, {
- position: "top-center",
- });
+ toast.error(
+ t("details.item.toast.error.updatedSublabelFailed", {
+ errorMessage,
+ }),
+ {
+ position: "top-center",
+ },
+ );
});
},
- [search, apiHost, mutate, setSearch],
+ [search, apiHost, mutate, setSearch, t],
);
return (
diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx
index 6c04bb6dd..1350a70b1 100644
--- a/web/src/components/player/HlsVideoPlayer.tsx
+++ b/web/src/components/player/HlsVideoPlayer.tsx
@@ -18,6 +18,7 @@ import { useOverlayState } from "@/hooks/use-overlay-state";
import { usePersistence } from "@/hooks/use-persistence";
import { cn } from "@/lib/utils";
import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record";
+import { useTranslation } from "react-i18next";
// Android native hls does not seek correctly
const USE_NATIVE_HLS = !isAndroid;
@@ -63,6 +64,7 @@ export default function HlsVideoPlayer({
toggleFullscreen,
onError,
}: HlsVideoPlayerProps) {
+ const { t } = useTranslation("components/player");
const { data: config } = useSWR- Frigate can natively send push notifications to your device - when it is running in the browser or installed as a PWA. -
+{t("notification.notificationSettings.desc")}
+ {t("notification.notificationSettings.documentation")} +
{" "}https://...). This is a browser limitation. Access
- Frigate securely to use notifications.
+ + {t("notification.notificationUnavailable.documentation")} +
{" "}{t("notification.desc")}
+{t("notification.notificationSettings.desc")}