chore: add some i18n keys

This commit is contained in:
ZhaiSoul 2025-03-11 11:10:53 +08:00
parent 2dc6283727
commit b35fbef325
7 changed files with 162 additions and 42 deletions

View File

@ -8,6 +8,24 @@
"button": "Force Reload Now"
}
},
"explore": {
"plus": {
"submitToPlus": {
"label": "Submit To Frigate+",
"desc": "Objects in locations you want to avoid are not false positives. Submitting them as false positives will confuse the model."
},
"review": {
"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}}",
"state.submitted": "Submitted"
}
},
"video": {
"viewInHistory": "View in History"
}
},
"export": {
"time": {
"fromTimeline": "Select from Timeline",

View File

@ -7,6 +7,19 @@
"object_lifecycle": "object lifecycle"
},
"details": {
"item": {
"title": "Review Item Details",
"desc": "Review item details",
"button": {
"share": "Share this review item",
"viewInExplore": "View in Explore"
},
"tips": {
"mismatch_one": "{{count}} unavailable object was detected and included in this review item. Those objects either did not qualify as an alert or detection or have already been cleaned up/deleted.",
"mismatch_other": "{{count}} unavailable objects were detected and included in this review item. Those objects either did not qualify as an alert or detection or have already been cleaned up/deleted.",
"hasMissingObjects": "Adjust your configuration if you want Frigate to save tracked objects for the following labels: <em>{{objects}}</em>"
}
},
"label": "Label",
"editSubLable": "Edit sub label",
"editSubLable.desc": "Enter a new sub label for this {{label}}",
@ -14,7 +27,9 @@
"topScore": "Top Score",
"topScore.info": "The top score is the highest median score for the tracked object, so this may differ from the score shown on the search result thumbnail.",
"estimatedSpeed": "Estimated Speed",
"objects": "Objects",
"camera": "Camera",
"zones": "Zones",
"timestamp": "Timestamp",
"button": {
"findSimilar": "Find Similar"
@ -52,6 +67,10 @@
"submitToPlus": {
"label": "Submit to Frigate+",
"aria": "Submit to Frigate Plus"
},
"viewInHistory": {
"label": "View in History",
"aria": "View in History"
}
},
"dialog": {

View File

@ -8,6 +8,24 @@
"button": "强制刷新"
}
},
"explore": {
"plus": {
"submitToPlus": {
"label": "提交至 Frigate+",
"desc": "您希望避开的地点中的物体不应被视为误报。若将其作为误报提交可能会导致AI模型容易混淆相关物体的识别。"
},
"review": {
"true_one": "这是 {{label}}",
"true_other": "这是 {{label}}",
"false_one": "这不是 {{label}}",
"false_other": "这不是 {{label}}",
"state.submitted": "已提交"
}
},
"video": {
"viewInHistory": "在历史中查看"
}
},
"export": {
"time": {
"fromTimeline": "从时间线选择",

View File

@ -0,0 +1,3 @@
{
}

View File

@ -7,6 +7,19 @@
"object_lifecycle": "对象生命周期"
},
"details": {
"item": {
"title": "回放项目详情",
"desc": "回放项目详情",
"button": {
"share": "分享该回放",
"viewInExplore": "在探测中查看"
},
"tips": {
"mismatch_one": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
"mismatch_other": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
"hasMissingObjects": "如果希望 Frigate 保存以下标签的跟踪对象,请调整您的配置:<em>{{objects}}</em>"
}
},
"label": "标签",
"editSubLable": "编辑子标签",
"editSubLable.desc": "为 {{label}} 输入新的子标签",
@ -14,7 +27,9 @@
"topScore": "最高得分",
"topScore.info": "最高分是跟踪对象的最高中位数得分,因此可能与搜索结果缩略图上显示的得分不同。",
"estimatedSpeed": "预计速度",
"objects": "对象",
"camera": "摄像头",
"zones": "区域",
"timestamp": "时间",
"button": {
"findSimilar": "查找相似项"
@ -52,6 +67,10 @@
"submitToPlus": {
"label": "提交至 Frigate+",
"aria": "提交至 Frigate Plus"
},
"viewInHistory": {
"label": "在历史记录中查看",
"aria": "在历史记录中查看"
}
},
"dialog": {

View File

@ -42,7 +42,7 @@ import { DownloadVideoButton } from "@/components/button/DownloadVideoButton";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { LuSearch } from "react-icons/lu";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { t } from "i18next";
import { Trans, useTranslation } from "react-i18next";
type ReviewDetailDialogProps = {
review?: ReviewSegment;
@ -52,6 +52,7 @@ export default function ReviewDetailDialog({
review,
setReview,
}: ReviewDetailDialogProps) {
const { t } = useTranslation(["views/explore"]);
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
@ -96,8 +97,8 @@ export default function ReviewDetailDialog({
const formattedDate = useFormattedTimestamp(
review?.start_time ?? 0,
config?.ui.time_format == "24hour"
? t("time.formattedTimestampWithYear.24hour")
: t("time.formattedTimestampWithYear"),
? t("time.formattedTimestampWithYear.24hour", { ns: "common" })
: t("time.formattedTimestampWithYear", { ns: "common" }),
config?.ui.timezone,
);
@ -178,8 +179,10 @@ export default function ReviewDetailDialog({
<span tabIndex={0} className="sr-only" />
{pane == "overview" && (
<Header className="justify-center">
<Title>Review Item Details</Title>
<Description className="sr-only">Review item details</Description>
<Title>{t("details.item.title")}</Title>
<Description className="sr-only">
{t("details.item.desc")}
</Description>
<div
className={cn(
"absolute flex gap-2 lg:flex-col",
@ -200,7 +203,9 @@ export default function ReviewDetailDialog({
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>Share this review item</TooltipContent>
<TooltipContent>
{t("details.item.button.share")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
@ -212,7 +217,9 @@ export default function ReviewDetailDialog({
/>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>Download</TooltipContent>
<TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
@ -223,19 +230,25 @@ export default function ReviewDetailDialog({
<div className="flex w-full flex-row">
<div className="flex w-full flex-col gap-3">
<div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">Camera</div>
<div className="text-sm text-primary/40">
{t("details.camera")}
</div>
<div className="text-sm capitalize">
{review.camera.replaceAll("_", " ")}
</div>
</div>
<div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">Timestamp</div>
<div className="text-sm text-primary/40">
{t("details.timestamp")}
</div>
<div className="text-sm">{formattedDate}</div>
</div>
</div>
<div className="flex w-full flex-col items-center gap-2">
<div className="flex w-full flex-col gap-1.5 lg:pr-8">
<div className="text-sm text-primary/40">Objects</div>
<div className="text-sm text-primary/40">
{t("details.objects")}
</div>
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-auto text-sm capitalize">
{events?.map((event) => {
return (
@ -261,7 +274,9 @@ export default function ReviewDetailDialog({
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>View in Explore</TooltipContent>
<TooltipContent>
{t("details.item.button.viewInExplore")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
@ -271,7 +286,9 @@ export default function ReviewDetailDialog({
</div>
{review.data.zones.length > 0 && (
<div className="scrollbar-container flex max-h-32 w-full flex-col gap-1.5">
<div className="text-sm text-primary/40">Zones</div>
<div className="text-sm text-primary/40">
{t("details.zones")}
</div>
<div className="flex flex-col items-start gap-2 text-sm capitalize">
{review.data.zones.map((zone) => {
return (
@ -295,18 +312,23 @@ export default function ReviewDetailDialog({
(events?.length ?? 0) -
(review?.data.detections.length ?? 0),
);
const objectLabel =
detectedCount === 1 ? "object was" : "objects were";
return `${detectedCount} unavailable ${objectLabel} detected and included in this review item.`;
})()}{" "}
Those objects either did not qualify as an alert or detection
or have already been cleaned up/deleted.
return t("details.item.tips.mismatch", {
count: detectedCount,
});
})()}
{missingObjects.length > 0 && (
<div className="mt-2">
Adjust your configuration if you want Frigate to save
tracked objects for the following labels:{" "}
{missingObjects.join(", ")}
<Trans
ns="views/explore"
values={{
objects: missingObjects
.map((x) => t(x, { ns: "objects" }))
.join(", "),
}}
>
details.item.tips.hasMissingObjects
</Trans>
</div>
)}
</div>
@ -349,6 +371,8 @@ function EventItem({
setSelectedEvent,
setUpload,
}: EventItemProps) {
const { t } = useTranslation(["views/explore"]);
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
@ -418,7 +442,9 @@ function EventItem({
</Chip>
</a>
</TooltipTrigger>
<TooltipContent>Download</TooltipContent>
<TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</Tooltip>
{event.has_snapshot &&
@ -436,7 +462,9 @@ function EventItem({
<FrigatePlusIcon className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>Submit to Frigate+</TooltipContent>
<TooltipContent>
{t("itemMenu.submitToPlus.label")}
</TooltipContent>
</Tooltip>
)}
@ -453,7 +481,9 @@ function EventItem({
<FaArrowsRotate className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>View Object Lifecycle</TooltipContent>
<TooltipContent>
{t("itemMenu.viewObjectLifecycle.label")}
</TooltipContent>
</Tooltip>
)}
@ -471,7 +501,9 @@ function EventItem({
<FaImages className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>Find Similar</TooltipContent>
<TooltipContent>
{t("itemMenu.findSimilar.label")}
</TooltipContent>
</Tooltip>
)}
</div>

View File

@ -578,8 +578,8 @@ function ObjectDetailsTab({
<div className="flex flex-row items-center gap-2">
{averageEstimatedSpeed}{" "}
{config?.ui.unit_system == "imperial"
? t("unit.speed.mph")
: t("unit.speed.kph")}{" "}
? t("unit.speed.mph", { ns: "common" })
: t("unit.speed.kph", { ns: "common" })}{" "}
{velocityAngle != undefined && (
<span className="text-primary/40">
<FaArrowRight
@ -624,7 +624,7 @@ function ObjectDetailsTab({
/>
{config?.semantic_search.enabled && search.data.type == "object" && (
<Button
aria-label="Find similar tracked objects"
aria-label={t("itemMenu.findSimilar.aria")}
onClick={() => {
setSearch(undefined);
@ -633,7 +633,7 @@ function ObjectDetailsTab({
}
}}
>
Find Similar
{t("itemMenu.findSimilar.label")}
</Button>
)}
</div>
@ -855,12 +855,10 @@ export function ObjectSnapshotTab({
"text-lg font-semibold leading-none tracking-tight"
}
>
Submit To Frigate+
{t("explore.submitToPlus.label")}
</div>
<div className="text-sm text-muted-foreground">
Objects in locations you want to avoid are not false
positives. Submitting them as false positives will
confuse the model.
{t("explore.submitToPlus.desc")}
</div>
</div>
@ -875,9 +873,13 @@ export function ObjectSnapshotTab({
onSubmitToPlus(false);
}}
>
This is{" "}
{/^[aeiou]/i.test(search?.label || "") ? "an" : "a"}{" "}
{search?.label}
{/^[aeiou]/i.test(search?.label || "")
? t("explore.plus.review.true_other", {
label: search?.label,
})
: t("explore.plus.review.true_one", {
label: search?.label,
})}
</Button>
<Button
className="text-white"
@ -888,9 +890,13 @@ export function ObjectSnapshotTab({
onSubmitToPlus(true);
}}
>
This is not{" "}
{/^[aeiou]/i.test(search?.label || "") ? "an" : "a"}{" "}
{search?.label}
{/^[aeiou]/i.test(search?.label || "")
? t("explore.plus.review.false_other", {
label: search?.label,
})
: t("explore.plus.review.false_one", {
label: search?.label,
})}
</Button>
</>
)}
@ -898,7 +904,7 @@ export function ObjectSnapshotTab({
{state == "submitted" && (
<div className="flex flex-row items-center justify-center gap-2">
<FaCheckCircle className="text-success" />
Submitted
{t("explore.plus.review.state.submitted")}
</div>
)}
</div>
@ -917,6 +923,7 @@ type VideoTabProps = {
};
export function VideoTab({ search }: VideoTabProps) {
const { t } = useTranslation(["views/explore"]);
const navigate = useNavigate();
const { data: reviewItem } = useSWR<ReviewSegment>([
`review/event/${search.id}`,
@ -951,7 +958,9 @@ export function VideoTab({ search }: VideoTabProps) {
</Chip>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>View in History</TooltipContent>
<TooltipContent>
{t("itemMenu.viewInHistory.label")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
@ -966,7 +975,9 @@ export function VideoTab({ search }: VideoTabProps) {
</a>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>Download</TooltipContent>
<TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>