mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-06 05:27:44 +03:00
feat: add more document title i18n keys
This commit is contained in:
parent
18ff446465
commit
29087cfcbc
@ -123,6 +123,8 @@
|
|||||||
"live": "Live",
|
"live": "Live",
|
||||||
"live.allCameras": "All Cameras",
|
"live.allCameras": "All Cameras",
|
||||||
"live.cameras": "Cameras",
|
"live.cameras": "Cameras",
|
||||||
|
"live.cameras.count_one": "{{count}} Camera",
|
||||||
|
"live.cameras.count_other": "{{count}} Cameras",
|
||||||
"review": "Review",
|
"review": "Review",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
@ -156,5 +158,16 @@
|
|||||||
"next": "Next",
|
"next": "Next",
|
||||||
"next.label": "Go to next page",
|
"next.label": "Go to next page",
|
||||||
"more": "More pages"
|
"more": "More pages"
|
||||||
}
|
},
|
||||||
|
"accessDefined": {
|
||||||
|
"documentTitle": "Access Defined - Frigate",
|
||||||
|
"title": "Access Defined",
|
||||||
|
"desc": "You don't have permission to view this page."
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"documentTitle": "Not Found - Frigate",
|
||||||
|
"title": "404",
|
||||||
|
"desc": "Page not found"
|
||||||
|
},
|
||||||
|
"selectItem": "Select {{item}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "Config Editor - Frigate",
|
||||||
"configEditor": "Config Editor",
|
"configEditor": "Config Editor",
|
||||||
"copyConfig": "Copy Config",
|
"copyConfig": "Copy Config",
|
||||||
"saveAndRestart": "Save & Restart",
|
"saveAndRestart": "Save & Restart",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "Explore - Frigate",
|
||||||
"generativeAI": "Generative AI",
|
"generativeAI": "Generative AI",
|
||||||
"exploreIsUnavailable": {
|
"exploreIsUnavailable": {
|
||||||
"title": "Explore is Unavailable",
|
"title": "Explore is Unavailable",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "Face Library - Frigate",
|
||||||
"uploadFaceImage": {
|
"uploadFaceImage": {
|
||||||
"title": "Upload Face Image",
|
"title": "Upload Face Image",
|
||||||
"desc": "Upload an image to scan for faces and include for {{pageToggle}}"
|
"desc": "Upload an image to scan for faces and include for {{pageToggle}}"
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": {
|
||||||
|
"default": "Settings - Frigate",
|
||||||
|
"authentication": "Authentication Settings - Frigate",
|
||||||
|
"camera": "Camera Settings - Frigate",
|
||||||
|
"classification": "Classification Settings - Frigate",
|
||||||
|
"masksAndZones": "Mask and Zone Editor - Frigate",
|
||||||
|
"motionTuner": "Motion Tuner - Frigate",
|
||||||
|
"object": "Object Settings - Frigate",
|
||||||
|
"general": "General Settings - Frigate"
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"uiSettings": "UI Settings",
|
"uiSettings": "UI Settings",
|
||||||
"exploreSettings": "Explore Settings",
|
"classificationSettings": "Classification Settings",
|
||||||
"cameraSettings": "Camera Settings",
|
"cameraSettings": "Camera Settings",
|
||||||
"masksAndZones": "Masks / Zones",
|
"masksAndZones": "Masks / Zones",
|
||||||
"motionTuner": "Motion Tuner",
|
"motionTuner": "Motion Tuner",
|
||||||
@ -9,6 +19,16 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"notifications": "Notifications"
|
"notifications": "Notifications"
|
||||||
},
|
},
|
||||||
|
"dialog": {
|
||||||
|
"unsavedChanges": {
|
||||||
|
"title": "You have unsaved changes.",
|
||||||
|
"desc": "Do you want to save your changes before continuing?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cameraSetting": {
|
||||||
|
"camera": "Camera",
|
||||||
|
"noCamera": "No Camera"
|
||||||
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"title": "General Settings",
|
"title": "General Settings",
|
||||||
"liveDashboard": {
|
"liveDashboard": {
|
||||||
@ -376,7 +396,10 @@
|
|||||||
"desc": "Web push notifications require a secure context (<code>https://...</code>). This is a browser limitation. Access Frigate securely to use notifications.",
|
"desc": "Web push notifications require a secure context (<code>https://...</code>). This is a browser limitation. Access Frigate securely to use notifications.",
|
||||||
"documentation": "Read the Documentation"
|
"documentation": "Read the Documentation"
|
||||||
},
|
},
|
||||||
|
"globalSettings": {
|
||||||
|
"title": "Global Settings",
|
||||||
|
"desc": "Temporarily suspend notifications for specific cameras on all registered devices."
|
||||||
|
},
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"email.placeholder": "e.g. example@email.com",
|
"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.",
|
"email.desc": "A valid email is required and will be used to notify you if there are any issues with the push service.",
|
||||||
@ -386,6 +409,19 @@
|
|||||||
"deviceSpecific": "Device Specific Settings",
|
"deviceSpecific": "Device Specific Settings",
|
||||||
"registerDevice": "Register This Device",
|
"registerDevice": "Register This Device",
|
||||||
"unregisterDevice": "Unregister This Device",
|
"unregisterDevice": "Unregister This Device",
|
||||||
|
"sendTestNotification": "Send a test notification",
|
||||||
|
"active": "Notifications Active",
|
||||||
|
"suspended": "Notifications suspended {{time}}",
|
||||||
|
"suspendTime": {
|
||||||
|
"5minutes": "Suspend for 5 minutes",
|
||||||
|
"10minutes": "Suspend for 10 minutes",
|
||||||
|
"30minutes": "Suspend for 30 minutes",
|
||||||
|
"1hour": "Suspend for 1 hour",
|
||||||
|
"12hours": "Suspend for 12 hours",
|
||||||
|
"24hours": "Suspend for 24 hours",
|
||||||
|
"untilRestart": "Suspend until restart"
|
||||||
|
},
|
||||||
|
"cancelSuspension": "Cancel Suspension",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"registered": "Successfully registered for notifications. Restarting Frigate is required before any notifications (including a test notification) can be sent.",
|
"registered": "Successfully registered for notifications. Restarting Frigate is required before any notifications (including a test notification) can be sent.",
|
||||||
|
|||||||
@ -1,4 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": {
|
||||||
|
"cameras": "Cameras Stats - Frigate",
|
||||||
|
"storage": "Storage Stats - Frigate",
|
||||||
|
"general": "General Stats - Frigate",
|
||||||
|
"features": "Features Stats- Frigate",
|
||||||
|
"logs": {
|
||||||
|
"frigate": "Frigate Logs - Frigate",
|
||||||
|
"go2rtc": "Go2RTC Logs - Frigate",
|
||||||
|
"nginx": "Nginx Logs - Frigate"
|
||||||
|
}
|
||||||
|
},
|
||||||
"title": "System",
|
"title": "System",
|
||||||
"metrics": "System metrics",
|
"metrics": "System metrics",
|
||||||
"logs": {
|
"logs": {
|
||||||
|
|||||||
@ -130,6 +130,8 @@
|
|||||||
"live": "实时监控",
|
"live": "实时监控",
|
||||||
"live.allCameras": "所有摄像头",
|
"live.allCameras": "所有摄像头",
|
||||||
"live.cameras": "摄像头",
|
"live.cameras": "摄像头",
|
||||||
|
"live.cameras.count_one": "{{count}} 个摄像头",
|
||||||
|
"live.cameras.count_other": "{{count}} 个摄像头",
|
||||||
"review": "回放",
|
"review": "回放",
|
||||||
"explore": "探测",
|
"explore": "探测",
|
||||||
"export": "导出",
|
"export": "导出",
|
||||||
@ -156,5 +158,16 @@
|
|||||||
"admin": "管理员",
|
"admin": "管理员",
|
||||||
"viewer": "查看者",
|
"viewer": "查看者",
|
||||||
"desc": "管理员可以完全访问 Frigate UI 的所有功能。查看者则仅限于在 UI 中查看摄像头、审核项和历史录像。"
|
"desc": "管理员可以完全访问 Frigate UI 的所有功能。查看者则仅限于在 UI 中查看摄像头、审核项和历史录像。"
|
||||||
}
|
},
|
||||||
|
"accessDefined": {
|
||||||
|
"documentTitle": "没有权限 - Frigate",
|
||||||
|
"title": "没有权限",
|
||||||
|
"desc": "您没有权限查看此页面。"
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"documentTitle": "没有找到页面 - Frigate",
|
||||||
|
"title": "404",
|
||||||
|
"desc": "页面未找到"
|
||||||
|
},
|
||||||
|
"selectItem": "选择 {{item}}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "配置编辑器 - Frigate",
|
||||||
"configEditor": "配置编辑器",
|
"configEditor": "配置编辑器",
|
||||||
"copyConfig": "复制配置",
|
"copyConfig": "复制配置",
|
||||||
"saveAndRestart": "保存并重启",
|
"saveAndRestart": "保存并重启",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "探索 - Frigate",
|
||||||
"generativeAI": "生成式 AI",
|
"generativeAI": "生成式 AI",
|
||||||
"exploreIsUnavailable": {
|
"exploreIsUnavailable": {
|
||||||
"title": "探索功能不可用",
|
"title": "探索功能不可用",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": "人脸库 - Frigate",
|
||||||
"uploadFaceImage": {
|
"uploadFaceImage": {
|
||||||
"title": "上传人脸图片",
|
"title": "上传人脸图片",
|
||||||
"desc": "上传图片以扫描人脸并包含在{{pageToggle}}中"
|
"desc": "上传图片以扫描人脸并包含在{{pageToggle}}中"
|
||||||
|
|||||||
@ -1,7 +1,23 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": {
|
||||||
|
"default": "设置 - Frigate",
|
||||||
|
"authentication": "身份验证设置 - Frigate",
|
||||||
|
"camera": "摄像头设置 - Frigate",
|
||||||
|
"classification": "分类设置 - Frigate",
|
||||||
|
"masksAndZones": "遮罩和区域编辑器 - Frigate",
|
||||||
|
"motionTuner": "运动调整器 - Frigate",
|
||||||
|
"object": "对象设置 - Frigate",
|
||||||
|
"general": "常规设置 - Frigate"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"unsavedChanges": {
|
||||||
|
"title": "你有未保存的更改。",
|
||||||
|
"desc": "是否要在继续之前保存更改?"
|
||||||
|
}
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"uiSettings": "界面设置",
|
"uiSettings": "界面设置",
|
||||||
"exploreSettings": "搜索设置",
|
"classificationSettings": "分类设置",
|
||||||
"cameraSettings": "摄像头设置",
|
"cameraSettings": "摄像头设置",
|
||||||
"masksAndZones": "遮罩/ 区域",
|
"masksAndZones": "遮罩/ 区域",
|
||||||
"motionTuner": "运动调整器",
|
"motionTuner": "运动调整器",
|
||||||
@ -373,6 +389,10 @@
|
|||||||
"desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
|
"desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
|
||||||
"documentation": "阅读文档(英文)"
|
"documentation": "阅读文档(英文)"
|
||||||
},
|
},
|
||||||
|
"globalSettings": {
|
||||||
|
"title": "全局设置",
|
||||||
|
"desc": "临时暂停所有已注册设备上特定摄像头的通知。"
|
||||||
|
},
|
||||||
"notificationUnavailable": {
|
"notificationUnavailable": {
|
||||||
"title": "通知功能不可用",
|
"title": "通知功能不可用",
|
||||||
"desc": "网页推送通知需要安全连接(<code>https://...</code>)。这是浏览器的限制。请通过安全方式访问 Frigate 以使用通知功能。",
|
"desc": "网页推送通知需要安全连接(<code>https://...</code>)。这是浏览器的限制。请通过安全方式访问 Frigate 以使用通知功能。",
|
||||||
@ -387,6 +407,19 @@
|
|||||||
"deviceSpecific": "设备专用设置",
|
"deviceSpecific": "设备专用设置",
|
||||||
"registerDevice": "注册该设备",
|
"registerDevice": "注册该设备",
|
||||||
"unregisterDevice": "取消注册该设备",
|
"unregisterDevice": "取消注册该设备",
|
||||||
|
"sendTestNotification": "发送测试通知",
|
||||||
|
"active": "通知已启用",
|
||||||
|
"suspended": "通知已暂停 {{time}}",
|
||||||
|
"suspendTime": {
|
||||||
|
"5minutes": "暂停 5 分钟",
|
||||||
|
"10minutes": "暂停 10 分钟",
|
||||||
|
"30minutes": "暂停 30 分钟",
|
||||||
|
"1hour": "暂停 1 小时",
|
||||||
|
"12hours": "暂停 12 小时",
|
||||||
|
"24hours": "暂停 24 小时",
|
||||||
|
"untilRestart": "暂停直到重启"
|
||||||
|
},
|
||||||
|
"cancelSuspension": "取消暂停",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"registered": "已成功注册通知。需要重启 Frigate 才能发送任何通知(包括测试通知)。",
|
"registered": "已成功注册通知。需要重启 Frigate 才能发送任何通知(包括测试通知)。",
|
||||||
|
|||||||
@ -1,4 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"documentTitle": {
|
||||||
|
"cameras": "摄像头统计 - Frigate",
|
||||||
|
"storage": "存储统计 - Frigate",
|
||||||
|
"general": "常规统计 - Frigate",
|
||||||
|
"features": "功能统计 - Frigate",
|
||||||
|
"logs": {
|
||||||
|
"frigate": "Frigate 日志 - Frigate",
|
||||||
|
"go2rtc": "Go2RTC 日志 - Frigate",
|
||||||
|
"nginx": "Nginx 日志 - Frigate"
|
||||||
|
}
|
||||||
|
},
|
||||||
"title": "系统",
|
"title": "系统",
|
||||||
"metrics": "系统指标",
|
"metrics": "系统指标",
|
||||||
"logs": {
|
"logs": {
|
||||||
|
|||||||
@ -288,24 +288,22 @@ export default function ReviewCard({
|
|||||||
>
|
>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Confirm Delete</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
{t("recording.confirmDelete.title")}
|
||||||
|
</AlertDialogTitle>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Are you sure you want to delete all recorded video associated with
|
<Trans ns="components/dialog">recording.confirmDelete.desc</Trans>
|
||||||
this review item?
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Hold the <em>Shift</em> key to bypass this dialog in the future.
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={() => setOptionsOpen(false)}>
|
<AlertDialogCancel onClick={() => setOptionsOpen(false)}>
|
||||||
Cancel
|
{t("button.cancel", { ns: "common" })}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
className={buttonVariants({ variant: "destructive" })}
|
className={buttonVariants({ variant: "destructive" })}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
>
|
>
|
||||||
Delete
|
{t("button.delete", { ns: "common" })}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
@ -318,7 +316,7 @@ export default function ReviewCard({
|
|||||||
onClick={onExport}
|
onClick={onExport}
|
||||||
>
|
>
|
||||||
<FaCompactDisc className="text-secondary-foreground" />
|
<FaCompactDisc className="text-secondary-foreground" />
|
||||||
<div className="text-primary">Export</div>
|
<div className="text-primary">{t("recording.button.export")}</div>
|
||||||
</div>
|
</div>
|
||||||
{!event.has_been_reviewed && (
|
{!event.has_been_reviewed && (
|
||||||
<div
|
<div
|
||||||
@ -326,7 +324,9 @@ export default function ReviewCard({
|
|||||||
onClick={onMarkAsReviewed}
|
onClick={onMarkAsReviewed}
|
||||||
>
|
>
|
||||||
<FaCircleCheck className="text-secondary-foreground" />
|
<FaCircleCheck className="text-secondary-foreground" />
|
||||||
<div className="text-primary">Mark as reviewed</div>
|
<div className="text-primary">
|
||||||
|
{t("recording.button.markAsReviewed")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
@ -335,7 +335,9 @@ export default function ReviewCard({
|
|||||||
>
|
>
|
||||||
<HiTrash className="text-secondary-foreground" />
|
<HiTrash className="text-secondary-foreground" />
|
||||||
<div className="text-primary">
|
<div className="text-primary">
|
||||||
{bypassDialogRef.current ? "Delete Now" : "Delete"}
|
{bypassDialogRef.current
|
||||||
|
? t("recording.button.deleteNow")
|
||||||
|
: t("button.delete", { ns: "common" })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
|
|||||||
@ -44,8 +44,12 @@ export function CamerasFilterButton({
|
|||||||
if (!selectedCameras || selectedCameras.length == 0) {
|
if (!selectedCameras || selectedCameras.length == 0) {
|
||||||
return t("menu.live.allCameras", { ns: "common" });
|
return t("menu.live.allCameras", { ns: "common" });
|
||||||
}
|
}
|
||||||
|
return t("menu.live.cameras.count", {
|
||||||
return `${selectedCameras.includes("birdseye") ? selectedCameras.length - 1 : selectedCameras.length} Camera${selectedCameras.length !== 1 ? "s" : ""}`;
|
count: selectedCameras.includes("birdseye")
|
||||||
|
? selectedCameras.length - 1
|
||||||
|
: selectedCameras.length,
|
||||||
|
ns: "common",
|
||||||
|
});
|
||||||
}, [selectedCameras, t]);
|
}, [selectedCameras, t]);
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
|
|||||||
@ -1,21 +1,20 @@
|
|||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { FaExclamationTriangle } from "react-icons/fa";
|
import { FaExclamationTriangle } from "react-icons/fa";
|
||||||
|
|
||||||
export default function AccessDenied() {
|
export default function AccessDenied() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Access Denied - Frigate";
|
document.title = t("accessDefined.documentTitle");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center text-center">
|
<div className="flex min-h-screen flex-col items-center justify-center text-center">
|
||||||
<FaExclamationTriangle className="mb-4 size-8" />
|
<FaExclamationTriangle className="mb-4 size-8" />
|
||||||
<Heading as="h2" className="mb-2">
|
<Heading as="h2" className="mb-2">
|
||||||
Access Denied
|
{t("accessDefined.title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
<p className="text-primary-variant">
|
<p className="text-primary-variant">{t("accessDefined.desc")}</p>
|
||||||
You don't have permission to view this page.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,8 +29,8 @@ function ConfigEditor() {
|
|||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Config Editor - Frigate";
|
document.title = t("documentTitle");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const { data: config } = useSWR<string>("config/raw");
|
const { data: config } = useSWR<string>("config/raw");
|
||||||
|
|
||||||
|
|||||||
@ -38,8 +38,8 @@ export default function FaceLibrary() {
|
|||||||
// title
|
// title
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Face Library - Frigate";
|
document.title = t("documentTitle");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const [page, setPage] = useState<string>();
|
const [page, setPage] = useState<string>();
|
||||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
||||||
|
|||||||
@ -49,8 +49,8 @@ function Logs() {
|
|||||||
const lastFetchedIndexRef = useRef(-1);
|
const lastFetchedIndexRef = useRef(-1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Logs - Frigate`;
|
document.title = t("documentTitle.logs." + logService);
|
||||||
}, [logService]);
|
}, [logService, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tabsRef.current) {
|
if (tabsRef.current) {
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
function NoMatch() {
|
function NoMatch() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Not Found - Frigate";
|
document.title = t("notFound.documentTitle");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Heading as="h2">404</Heading>
|
<Heading as="h2">{t("notFound.title")}</Heading>
|
||||||
<p>Page not found</p>
|
<p>{t("notFound.desc")}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,8 +185,8 @@ export default function Settings() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Settings - Frigate";
|
document.title = t("documentTitle.default");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col p-2">
|
<div className="flex size-full flex-col p-2">
|
||||||
@ -215,7 +215,10 @@ export default function Settings() {
|
|||||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "uiSettings" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "uiSettings" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
||||||
value={item}
|
value={item}
|
||||||
data-nav-item={item}
|
data-nav-item={item}
|
||||||
aria-label={`Select ${item}`}
|
aria-label={t("selectItem", {
|
||||||
|
item: t("menu." + item),
|
||||||
|
ns: "common",
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div className="capitalize">{t("menu." + item)}</div>
|
<div className="capitalize">{t("menu." + item)}</div>
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
@ -284,17 +287,19 @@ export default function Settings() {
|
|||||||
>
|
>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>You have unsaved changes.</AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
|
{t("dialog.unsavedChanges.title")}
|
||||||
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Do you want to save your changes before continuing?
|
{t("dialog.unsavedChanges.desc")}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={() => handleDialog(false)}>
|
<AlertDialogCancel onClick={() => handleDialog(false)}>
|
||||||
Cancel
|
{t("button.cancel", { ns: "common" })}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={() => handleDialog(true)}>
|
<AlertDialogAction onClick={() => handleDialog(true)}>
|
||||||
Save
|
{t("button.save", { ns: "common" })}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
@ -319,6 +324,8 @@ function CameraSelectButton({
|
|||||||
cameraEnabledStates,
|
cameraEnabledStates,
|
||||||
currentPage,
|
currentPage,
|
||||||
}: CameraSelectButtonProps) {
|
}: CameraSelectButtonProps) {
|
||||||
|
const { t } = useTranslation(["views/settings"]);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
if (!allCameras.length) {
|
if (!allCameras.length) {
|
||||||
@ -334,7 +341,7 @@ function CameraSelectButton({
|
|||||||
<FaVideo className="text-background dark:text-primary" />
|
<FaVideo className="text-background dark:text-primary" />
|
||||||
<div className="hidden text-background dark:text-primary md:block">
|
<div className="hidden text-background dark:text-primary md:block">
|
||||||
{selectedCamera == undefined
|
{selectedCamera == undefined
|
||||||
? "No Camera"
|
? t("cameraSetting.noCamera")
|
||||||
: selectedCamera.replaceAll("_", " ")}
|
: selectedCamera.replaceAll("_", " ")}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
@ -344,7 +351,7 @@ function CameraSelectButton({
|
|||||||
{isMobile && (
|
{isMobile && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuLabel className="flex justify-center">
|
<DropdownMenuLabel className="flex justify-center">
|
||||||
Camera
|
{t("cameraSetting.camera")}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import Logo from "@/components/Logo";
|
|||||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||||
import CameraMetrics from "@/views/system/CameraMetrics";
|
import CameraMetrics from "@/views/system/CameraMetrics";
|
||||||
import { useHashState } from "@/hooks/use-overlay-state";
|
import { useHashState } from "@/hooks/use-overlay-state";
|
||||||
import { capitalizeFirstLetter } from "@/utils/stringUtil";
|
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import FeatureMetrics from "@/views/system/FeatureMetrics";
|
import FeatureMetrics from "@/views/system/FeatureMetrics";
|
||||||
@ -54,9 +53,9 @@ function System() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pageToggle) {
|
if (pageToggle) {
|
||||||
document.title = `${capitalizeFirstLetter(pageToggle)} Stats - Frigate`;
|
document.title = t("documentTitle." + pageToggle);
|
||||||
}
|
}
|
||||||
}, [pageToggle]);
|
}, [pageToggle, t]);
|
||||||
|
|
||||||
// stats collection
|
// stats collection
|
||||||
|
|
||||||
|
|||||||
@ -36,11 +36,12 @@ export default function ExploreView({
|
|||||||
setSimilaritySearch,
|
setSimilaritySearch,
|
||||||
onSelectSearch,
|
onSelectSearch,
|
||||||
}: ExploreViewProps) {
|
}: ExploreViewProps) {
|
||||||
|
const { t } = useTranslation(["views/explore"]);
|
||||||
// title
|
// title
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Explore - Frigate";
|
document.title = t("documentTitle");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
// data
|
// data
|
||||||
|
|
||||||
|
|||||||
@ -49,8 +49,8 @@ export default function AuthenticationView() {
|
|||||||
>();
|
>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Authentication Settings - Frigate";
|
document.title = t("documentTitle.authentication");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const onSavePassword = useCallback(
|
const onSavePassword = useCallback(
|
||||||
(user: string, password: string) => {
|
(user: string, password: string) => {
|
||||||
|
|||||||
@ -193,8 +193,8 @@ export default function ClassificationSettingsView({
|
|||||||
}, [changedValue]);
|
}, [changedValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Classification Settings - Frigate";
|
document.title = t("documentTitle.classification");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
|
|||||||
@ -185,8 +185,8 @@ export default function MasksAndZonesView({
|
|||||||
setActivePolygonIndex(undefined);
|
setActivePolygonIndex(undefined);
|
||||||
setHoveredPolygonIndex(null);
|
setHoveredPolygonIndex(null);
|
||||||
setUnsavedChanges(false);
|
setUnsavedChanges(false);
|
||||||
document.title = "Mask and Zone Editor - Frigate";
|
document.title = t("documentTitle.masksAndZones");
|
||||||
}, [allPolygons, setUnsavedChanges]);
|
}, [allPolygons, setUnsavedChanges, t]);
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
setAllPolygons([...(editingPolygons ?? [])]);
|
setAllPolygons([...(editingPolygons ?? [])]);
|
||||||
@ -425,8 +425,8 @@ export default function MasksAndZonesView({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Mask and Zone Editor - Frigate";
|
document.title = t("documentTitle.masksAndZones");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
if (!cameraConfig && !selectedCamera) {
|
if (!cameraConfig && !selectedCamera) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
|
|||||||
@ -179,8 +179,8 @@ export default function MotionTunerView({
|
|||||||
}, [changedValue, selectedCamera]);
|
}, [changedValue, selectedCamera]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Motion Tuner - Frigate";
|
document.title = t("documentTitle.motionTuner");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
if (!cameraConfig && !selectedCamera) {
|
if (!cameraConfig && !selectedCamera) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
|
|||||||
@ -560,10 +560,10 @@ export default function NotificationView({
|
|||||||
</Button>
|
</Button>
|
||||||
{registration != null && registration.active && (
|
{registration != null && registration.active && (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Send a test notification"
|
aria-label={t("notification.sendTestNotification")}
|
||||||
onClick={() => sendTestNotification("notification_test")}
|
onClick={() => sendTestNotification("notification_test")}
|
||||||
>
|
>
|
||||||
Send a test notification
|
{t("notification.sendTestNotification")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -573,14 +573,11 @@ export default function NotificationView({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
<Heading as="h4" className="my-2">
|
<Heading as="h4" className="my-2">
|
||||||
Global Settings
|
{t("notification.globalSettings.title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="max-w-xl">
|
<div className="max-w-xl">
|
||||||
<div className="mb-5 mt-2 flex flex-col gap-2 text-sm text-primary-variant">
|
<div className="mb-5 mt-2 flex flex-col gap-2 text-sm text-primary-variant">
|
||||||
<p>
|
<p>{t("notification.globalSettings.desc")}</p>
|
||||||
Temporarily suspend notifications for specific cameras
|
|
||||||
on all registered devices.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -680,12 +677,13 @@ export function CameraNotificationSwitch({
|
|||||||
|
|
||||||
{!isSuspended ? (
|
{!isSuspended ? (
|
||||||
<div className="flex flex-row items-center gap-2 text-sm text-success">
|
<div className="flex flex-row items-center gap-2 text-sm text-success">
|
||||||
Notifications Active
|
{t("notification.active")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-row items-center gap-2 text-sm text-danger">
|
<div className="flex flex-row items-center gap-2 text-sm text-danger">
|
||||||
Notifications suspended{" "}
|
{t("notification.suspended", {
|
||||||
{formatSuspendedUntil(notificationSuspendUntil)}
|
time: formatSuspendedUntil(notificationSuspendUntil),
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -698,13 +696,27 @@ export function CameraNotificationSwitch({
|
|||||||
<SelectValue placeholder="Suspend" />
|
<SelectValue placeholder="Suspend" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="5">Suspend for 5 minutes</SelectItem>
|
<SelectItem value="5">
|
||||||
<SelectItem value="10">Suspend for 10 minutes</SelectItem>
|
{t("notification.suspendTime.5minutes")}
|
||||||
<SelectItem value="30">Suspend for 30 minutes</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="60">Suspend for 1 hour</SelectItem>
|
<SelectItem value="10">
|
||||||
<SelectItem value="840">Suspend for 12 hours</SelectItem>
|
{t("notification.suspendTime.10minutes")}
|
||||||
<SelectItem value="1440">Suspend for 24 hours</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="off">Suspend until restart</SelectItem>
|
<SelectItem value="30">
|
||||||
|
{t("notification.suspendTime.30minutes")}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="60">
|
||||||
|
{t("notification.suspendTime.1hour")}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="840">
|
||||||
|
{t("notification.suspendTime.12hour")}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="1440">
|
||||||
|
{t("notification.suspendTime.24hour")}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="off">
|
||||||
|
{t("notification.suspendTime.untilRestart")}
|
||||||
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
) : (
|
) : (
|
||||||
@ -713,7 +725,7 @@ export function CameraNotificationSwitch({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCancelSuspension}
|
onClick={handleCancelSuspension}
|
||||||
>
|
>
|
||||||
Cancel Suspension
|
{t("notification.cancelSuspension")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -136,8 +136,8 @@ export default function ObjectSettingsView({
|
|||||||
}, [options, optionsLoaded]);
|
}, [options, optionsLoaded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Object Settings - Frigate";
|
document.title = t("documentTitle.object");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
if (!cameraConfig) {
|
if (!cameraConfig) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
|
|||||||
@ -86,8 +86,8 @@ export default function UiSettingsView() {
|
|||||||
}, [config, t]);
|
}, [config, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "General Settings - Frigate";
|
document.title = t("documentTitle.general");
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user