feat: add more live i18n keys

This commit is contained in:
ZhaiSoul 2025-03-13 00:15:48 +08:00
parent 59c6e1732f
commit ec86f456dd
3 changed files with 132 additions and 55 deletions

View File

@ -72,6 +72,17 @@
"disable": "Hide Stream Stats" "disable": "Hide Stream Stats"
}, },
"manualRecording": { "manualRecording": {
"title": "On-Demand Recording",
"tips": "Start a manual event based on this camera's recording retention settings.",
"playInBackground": {
"label": "Play in background",
"desc": "Enable this option to continue streaming when the player is hidden."
},
"showStats": {
"label": "Show Stats",
"desc": "Enable this option to show stream statistics as an overlay on the camera feed."
},
"debugView": "Debug View",
"start": "Start on-demand recording", "start": "Start on-demand recording",
"started": "Started manual on-demand recording.", "started": "Started manual on-demand recording.",
"failedToStart": "Failed to start manual on-demand recording.", "failedToStart": "Failed to start manual on-demand recording.",
@ -85,5 +96,38 @@
"audio": "Audio", "audio": "Audio",
"suspend:": { "suspend:": {
"forTime": "Suspend for: " "forTime": "Suspend for: "
},
"stream": {
"title": "Stream",
"audio": {
"tips": "Audio must be output from your camera and configured in go2rtc for this stream.",
"tips.documentation": "Read the documentation ",
"available": "Audio is available for this stream",
"unavailable": "Audio is not available for this stream"
},
"twoWayTalk": {
"tips": "Your device must suppport the feature and WebRTC must be configured for two-way talk.",
"tips.documentation": "Read the documentation ",
"available": "Two-way talk is available for this stream",
"unavailable": "Two-way talk is unavailable for this stream"
},
"lowBandwidth": {
"tips": "Live view is in low-bandwidth mode due to buffering or stream errors.",
"resetStream": "Reset stream"
},
"playInBackground": {
"label": "Play in background",
"tips": "Enable this option to continue streaming when the player is hidden."
},
"": ""
},
"cameraSettings": {
"title": "{{camera}} Settings",
"cameraEnabled": "Camera Enabled",
"objectDetection": "Object Detection",
"recording": "Recording",
"snapshots": "Snapshots",
"audioDetection": "Audio Detection",
"autotracking": "Autotracking"
} }
} }

View File

@ -72,6 +72,17 @@
"disable": "隐藏视频流统计信息" "disable": "隐藏视频流统计信息"
}, },
"manualRecording": { "manualRecording": {
"title": "按需录制",
"tips": "根据此摄像机的录制保留设置,手动启动事件。",
"playInBackground": {
"label": "后台播放",
"desc": "启用此选项可在播放器隐藏时继续视频流播放。"
},
"showStats": {
"label": "显示统计信息",
"desc": "启用此选项可在摄像机画面上叠加显示视频流统计信息。"
},
"debugView": "调试视图",
"start": "开始手动按需录制", "start": "开始手动按需录制",
"started": "已启用手动按需录制", "started": "已启用手动按需录制",
"failedToStart": "启动手动录制失败", "failedToStart": "启动手动录制失败",
@ -85,5 +96,37 @@
"audio": "音频", "audio": "音频",
"suspend": { "suspend": {
"forTime": "暂停时长:" "forTime": "暂停时长:"
},
"stream": {
"title": "视频流",
"audio": {
"tips": "音频必须从摄像机输出并在 go2rtc 中配置为此视频流使用。",
"tips.documentation": "阅读文档 ",
"available": "此视频流支持音频",
"unavailable": "此视频流不支持音频"
},
"twoWayTalk": {
"tips": "您的设备必须支持此功能,并且必须配置 WebRTC 以支持双向对讲。",
"tips.documentation": "阅读文档 ",
"available": "此视频流支持双向对讲",
"unavailable": "此视频流不支持双向对讲"
},
"lowBandwidth": {
"tips": "由于缓冲或视频流错误,实时视图处于低带宽模式。",
"resetStream": "重置视频流"
},
"playInBackground": {
"label": "后台播放",
"tips": "启用此选项可在播放器隐藏时继续视频流播放。"
}
},
"cameraSettings": {
"title": "{{camera}} 设置",
"cameraEnabled": "摄像机已启用",
"objectDetection": "对象检测",
"recording": "录制",
"snapshots": "快照",
"audioDetection": "音频检测",
"autotracking": "自动跟踪"
} }
} }

View File

@ -1233,7 +1233,7 @@ function FrigateCameraFeatures({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns: "common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
@ -1261,7 +1261,7 @@ function FrigateCameraFeatures({
{isRestreamed && {isRestreamed &&
Object.values(camera.live.streams).length > 0 && ( Object.values(camera.live.streams).length > 0 && (
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Label htmlFor="streaming-method">Stream</Label> <Label htmlFor="streaming-method">{t("stream.title")}</Label>
<Select <Select
value={streamName} value={streamName}
onValueChange={(value) => { onValueChange={(value) => {
@ -1296,22 +1296,21 @@ function FrigateCameraFeatures({
{supportsAudioOutput ? ( {supportsAudioOutput ? (
<> <>
<LuCheck className="size-4 text-success" /> <LuCheck className="size-4 text-success" />
<div>Audio is available for this stream</div> <div>{t("stream.audio.available")}</div>
</> </>
) : ( ) : (
<> <>
<LuX className="size-4 text-danger" /> <LuX className="size-4 text-danger" />
<div>Audio is unavailable for this stream</div> <div>{t("stream.audio.unavailable")}</div>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns:"common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
Audio must be output from your camera and {t("stream.audio.tips")}
configured in go2rtc for this stream.
<div className="mt-2 flex items-center text-primary"> <div className="mt-2 flex items-center text-primary">
<Link <Link
to="https://docs.frigate.video/configuration/live" to="https://docs.frigate.video/configuration/live"
@ -1319,7 +1318,7 @@ function FrigateCameraFeatures({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
Read the documentation{" "} {t("stream.audio.tips.documentation")}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -1337,25 +1336,24 @@ function FrigateCameraFeatures({
<> <>
<LuCheck className="size-4 text-success" /> <LuCheck className="size-4 text-success" />
<div> <div>
Two-way talk is available for this stream {t("stream.twoWayTalk.available")}
</div> </div>
</> </>
) : ( ) : (
<> <>
<LuX className="size-4 text-danger" /> <LuX className="size-4 text-danger" />
<div> <div>
Two-way talk is unavailable for this stream {t("stream.twoWayTalk.available")}
</div> </div>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns: "common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
Your device must suppport the feature and {t("stream.twoWayTalk.tips")}
WebRTC must be configured for two-way talk.
<div className="mt-2 flex items-center text-primary"> <div className="mt-2 flex items-center text-primary">
<Link <Link
to="https://docs.frigate.video/configuration/live/#webrtc-extra-configuration" to="https://docs.frigate.video/configuration/live/#webrtc-extra-configuration"
@ -1363,7 +1361,7 @@ function FrigateCameraFeatures({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
Read the documentation{" "} {t("stream.twoWayTalk.tips.documentation")}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -1380,20 +1378,19 @@ function FrigateCameraFeatures({
<IoIosWarning className="mr-1 size-8 text-danger" /> <IoIosWarning className="mr-1 size-8 text-danger" />
<p className="text-sm"> <p className="text-sm">
Live view is in low-bandwidth mode due to buffering {t("stream.lowBandwidth.tips")}
or stream errors.
</p> </p>
</div> </div>
<Button <Button
className={`flex items-center gap-2.5 rounded-lg`} className={`flex items-center gap-2.5 rounded-lg`}
aria-label="Reset the stream" aria-label={t("stream.lowBandwidth.resetStream")}
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setLowBandwidth(false)} onClick={() => setLowBandwidth(false)}
> >
<MdOutlineRestartAlt className="size-5 text-primary-variant" /> <MdOutlineRestartAlt className="size-5 text-primary-variant" />
<div className="text-primary-variant"> <div className="text-primary-variant">
Reset stream {t("stream.lowBandwidth.resetStream")}
</div> </div>
</Button> </Button>
</div> </div>
@ -1407,7 +1404,7 @@ function FrigateCameraFeatures({
className="mx-0 cursor-pointer text-primary" className="mx-0 cursor-pointer text-primary"
htmlFor="backgroundplay" htmlFor="backgroundplay"
> >
Play in background {t("stream.playInBackground.label")}
</Label> </Label>
<Switch <Switch
className="ml-1" className="ml-1"
@ -1419,8 +1416,7 @@ function FrigateCameraFeatures({
/> />
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Enable this option to continue streaming when the player is {t("stream.playInBackground.tips")}
hidden.
</p> </p>
</div> </div>
)} )}
@ -1476,7 +1472,7 @@ function FrigateCameraFeatures({
variant="primary" variant="primary"
Icon={FaCog} Icon={FaCog}
isActive={false} isActive={false}
title={`${camera} Settings`} title={t("cameraSettings.title", { camera })}
/> />
</DrawerTrigger> </DrawerTrigger>
<DrawerContent className="rounded-2xl px-2 py-4"> <DrawerContent className="rounded-2xl px-2 py-4">
@ -1484,14 +1480,14 @@ function FrigateCameraFeatures({
{isAdmin && ( {isAdmin && (
<> <>
<FilterSwitch <FilterSwitch
label="Camera Enabled" label={t("cameraSettings.cameraEnabled")}
isChecked={enabledState == "ON"} isChecked={enabledState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendEnabled(enabledState == "ON" ? "OFF" : "ON") sendEnabled(enabledState == "ON" ? "OFF" : "ON")
} }
/> />
<FilterSwitch <FilterSwitch
label="Object Detection" label={t("cameraSettings.objectDetection")}
isChecked={detectState == "ON"} isChecked={detectState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendDetect(detectState == "ON" ? "OFF" : "ON") sendDetect(detectState == "ON" ? "OFF" : "ON")
@ -1499,7 +1495,7 @@ function FrigateCameraFeatures({
/> />
{recordingEnabled && ( {recordingEnabled && (
<FilterSwitch <FilterSwitch
label="Recording" label={t("cameraSettings.recording")}
isChecked={recordState == "ON"} isChecked={recordState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendRecord(recordState == "ON" ? "OFF" : "ON") sendRecord(recordState == "ON" ? "OFF" : "ON")
@ -1507,7 +1503,7 @@ function FrigateCameraFeatures({
/> />
)} )}
<FilterSwitch <FilterSwitch
label="Snapshots" label={t("cameraSettings.snapshots")}
isChecked={snapshotState == "ON"} isChecked={snapshotState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON") sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
@ -1515,7 +1511,7 @@ function FrigateCameraFeatures({
/> />
{audioDetectEnabled && ( {audioDetectEnabled && (
<FilterSwitch <FilterSwitch
label="Audio Detection" label={t("cameraSettings.audioDetection")}
isChecked={audioState == "ON"} isChecked={audioState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendAudio(audioState == "ON" ? "OFF" : "ON") sendAudio(audioState == "ON" ? "OFF" : "ON")
@ -1524,7 +1520,7 @@ function FrigateCameraFeatures({
)} )}
{autotrackingEnabled && ( {autotrackingEnabled && (
<FilterSwitch <FilterSwitch
label="Autotracking" label={t("cameraSettings.autotracking")}
isChecked={autotrackingState == "ON"} isChecked={autotrackingState == "ON"}
onCheckedChange={() => onCheckedChange={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
@ -1550,7 +1546,7 @@ function FrigateCameraFeatures({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns: "common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 text-xs"> <PopoverContent className="w-80 text-xs">
@ -1577,7 +1573,7 @@ function FrigateCameraFeatures({
)} )}
{isRestreamed && Object.values(camera.live.streams).length > 0 && ( {isRestreamed && Object.values(camera.live.streams).length > 0 && (
<div className="mt-1 p-2"> <div className="mt-1 p-2">
<div className="mb-1 text-sm">Stream</div> <div className="mb-1 text-sm">{t("stream.title")}</div>
<Select <Select
value={streamName} value={streamName}
onValueChange={(value) => { onValueChange={(value) => {
@ -1611,22 +1607,21 @@ function FrigateCameraFeatures({
{supportsAudioOutput ? ( {supportsAudioOutput ? (
<> <>
<LuCheck className="size-4 text-success" /> <LuCheck className="size-4 text-success" />
<div>Audio is available for this stream</div> <div>{t("stream.audio.available")}</div>
</> </>
) : ( ) : (
<> <>
<LuX className="size-4 text-danger" /> <LuX className="size-4 text-danger" />
<div>Audio is unavailable for this stream</div> <div>{t("stream.audio.unavailable")}</div>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns: "common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52 text-xs"> <PopoverContent className="w-52 text-xs">
Audio must be output from your camera and configured {t("stream.audio.tips")}
in go2rtc for this stream.
<div className="mt-2 flex items-center text-primary"> <div className="mt-2 flex items-center text-primary">
<Link <Link
to="https://docs.frigate.video/configuration/live" to="https://docs.frigate.video/configuration/live"
@ -1634,7 +1629,7 @@ function FrigateCameraFeatures({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
Read the documentation{" "} {t("stream.audio.tips.documentation")}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -1651,22 +1646,21 @@ function FrigateCameraFeatures({
{supports2WayTalk ? ( {supports2WayTalk ? (
<> <>
<LuCheck className="size-4 text-success" /> <LuCheck className="size-4 text-success" />
<div>Two-way talk is available for this stream</div> <div>{t("stream.twoWayTalk.available")}</div>
</> </>
) : ( ) : (
<> <>
<LuX className="size-4 text-danger" /> <LuX className="size-4 text-danger" />
<div>Two-way talk is unavailable for this stream</div> <div>{t("stream.twoWayTalk.unavailable")}</div>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="cursor-pointer p-0"> <div className="cursor-pointer p-0">
<LuInfo className="size-4" /> <LuInfo className="size-4" />
<span className="sr-only">Info</span> <span className="sr-only">{t("button.info", {ns: "common"})}</span>
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52 text-xs"> <PopoverContent className="w-52 text-xs">
Your device must suppport the feature and WebRTC {t("stream.twoWayTalk.tips")}
must be configured for two-way talk.
<div className="mt-2 flex items-center text-primary"> <div className="mt-2 flex items-center text-primary">
<Link <Link
to="https://docs.frigate.video/configuration/live/#webrtc-extra-configuration" to="https://docs.frigate.video/configuration/live/#webrtc-extra-configuration"
@ -1674,7 +1668,7 @@ function FrigateCameraFeatures({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
Read the documentation{" "} {t("stream.twoWayTalk.tips.documentation")}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -1690,8 +1684,7 @@ function FrigateCameraFeatures({
<IoIosWarning className="mr-1 size-8 text-danger" /> <IoIosWarning className="mr-1 size-8 text-danger" />
<p className="text-sm"> <p className="text-sm">
Live view is in low-bandwidth mode due to buffering or {t("stream.lowBandwidth.tips")}
stream errors.
</p> </p>
</div> </div>
<Button <Button
@ -1702,7 +1695,7 @@ function FrigateCameraFeatures({
onClick={() => setLowBandwidth(false)} onClick={() => setLowBandwidth(false)}
> >
<MdOutlineRestartAlt className="size-5 text-primary-variant" /> <MdOutlineRestartAlt className="size-5 text-primary-variant" />
<div className="text-primary-variant">Reset stream</div> <div className="text-primary-variant">{t("stream.lowBandwidth.resetStream")}</div>
</Button> </Button>
</div> </div>
)} )}
@ -1710,7 +1703,7 @@ function FrigateCameraFeatures({
)} )}
<div className="flex flex-col gap-1 px-2"> <div className="flex flex-col gap-1 px-2">
<div className="mb-1 text-sm font-medium leading-none"> <div className="mb-1 text-sm font-medium leading-none">
On-Demand Recording {t("manualRecording.title")}
</div> </div>
<Button <Button
onClick={handleEventButtonClick} onClick={handleEventButtonClick}
@ -1722,43 +1715,40 @@ function FrigateCameraFeatures({
{t("manualRecording." + isRecording ? "end" : "start")} {t("manualRecording." + isRecording ? "end" : "start")}
</Button> </Button>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Start a manual event based on this camera's recording retention {t("manualRecording.tips")}
settings.
</p> </p>
</div> </div>
{isRestreamed && ( {isRestreamed && (
<> <>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<FilterSwitch <FilterSwitch
label="Play in Background" label={t("manualRecording.playInBackground.label")}
isChecked={playInBackground} isChecked={playInBackground}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setPlayInBackground(checked); setPlayInBackground(checked);
}} }}
/> />
<p className="mx-2 -mt-2 text-sm text-muted-foreground"> <p className="mx-2 -mt-2 text-sm text-muted-foreground">
Enable this option to continue streaming when the player is {t("manualRecording.playInBackground.desc")}
hidden.
</p> </p>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<FilterSwitch <FilterSwitch
label="Show Stats" label={t("manualRecording.showStats.label")}
isChecked={showStats} isChecked={showStats}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setShowStats(checked); setShowStats(checked);
}} }}
/> />
<p className="mx-2 -mt-2 text-sm text-muted-foreground"> <p className="mx-2 -mt-2 text-sm text-muted-foreground">
Enable this option to show stream statistics as an overlay on {t("manualRecording.showStats.desc")}
the camera feed.
</p> </p>
</div> </div>
</> </>
)} )}
<div className="mb-3 flex flex-col gap-1 px-2"> <div className="mb-3 flex flex-col gap-1 px-2">
<div className="flex items-center justify-between text-sm font-medium leading-none"> <div className="flex items-center justify-between text-sm font-medium leading-none">
Debug View {t("manualRecording.debugView")}
<LuExternalLink <LuExternalLink
onClick={() => onClick={() =>
navigate(`/settings?page=debug&camera=${camera.name}`) navigate(`/settings?page=debug&camera=${camera.name}`)