mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-05 13:07:44 +03:00
feat: add Player i18n keys
This commit is contained in:
parent
54ff88917d
commit
412cde0656
@ -5,5 +5,27 @@
|
|||||||
"submitFrigatePlus": {
|
"submitFrigatePlus": {
|
||||||
"title": "Submit this frame to Frigate+?",
|
"title": "Submit this frame to Frigate+?",
|
||||||
"submit": "Submit"
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"livePlayerRequiredIOSVersion": "iOS 17.1 or greater is required for this live stream type.",
|
||||||
|
"streamOffline": {
|
||||||
|
"title": "Stream Offline",
|
||||||
|
"desc": "No frames have been received on the {{cameraName}} <code>detect</code> stream, check error logs"
|
||||||
|
},
|
||||||
|
"cameraDisabled": "Camera is disabled",
|
||||||
|
"stats": {
|
||||||
|
"streamType": "Stream Type:",
|
||||||
|
"streamType.short": "Type",
|
||||||
|
"bandwidth": "Bandwidth:",
|
||||||
|
"bandwidth.short": "Bandwidth",
|
||||||
|
"latency": "Latency:",
|
||||||
|
"latency.short": "Latency",
|
||||||
|
"latency.value": "{{secounds}} seconds",
|
||||||
|
"latency.short.value": "{{secounds}} sec",
|
||||||
|
"totalFrames": "Total Frames:",
|
||||||
|
"droppedFrames": "Dropped Frames:",
|
||||||
|
"droppedFrames.short": "Dropped",
|
||||||
|
"droppedFrames.short.value": "{{droppedFrames}} frames",
|
||||||
|
"decodedFrames": "Decoded Frames:",
|
||||||
|
"droppedFrameRate": "Dropped Frame Rate:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,5 +5,27 @@
|
|||||||
"submitFrigatePlus": {
|
"submitFrigatePlus": {
|
||||||
"title": "提交此帧到 Frigate+?",
|
"title": "提交此帧到 Frigate+?",
|
||||||
"submit": "提交"
|
"submit": "提交"
|
||||||
|
},
|
||||||
|
"livePlayerRequiredIOSVersion": "此直播流类型需要 iOS 17.1 或更高版本。",
|
||||||
|
"streamOffline": {
|
||||||
|
"title": "视频流离线",
|
||||||
|
"desc": "未在 {{cameraName}} 的 <code>detect</code> 流上接收到任何帧,请检查错误日志"
|
||||||
|
},
|
||||||
|
"cameraDisabled": "摄像机已禁用",
|
||||||
|
"stats": {
|
||||||
|
"streamType": "流类型:",
|
||||||
|
"streamType.short": "类型",
|
||||||
|
"bandwidth": "带宽:",
|
||||||
|
"bandwidth.short": "带宽",
|
||||||
|
"latency": "延迟:",
|
||||||
|
"latency.short": "延迟",
|
||||||
|
"latency.value": "{{secounds}} 秒",
|
||||||
|
"latency.short.value": "{{secounds}} 秒",
|
||||||
|
"totalFrames": "总帧数:",
|
||||||
|
"droppedFrames": "丢帧数:",
|
||||||
|
"droppedFrames.short": "丢帧",
|
||||||
|
"droppedFrames.short.value": "{{droppedFrames}} 帧",
|
||||||
|
"decodedFrames": "解码帧数:",
|
||||||
|
"droppedFrameRate": "丢帧率:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,6 +23,7 @@ import { TooltipPortal } from "@radix-ui/react-tooltip";
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import { PlayerStats } from "./PlayerStats";
|
import { PlayerStats } from "./PlayerStats";
|
||||||
import { LuVideoOff } from "react-icons/lu";
|
import { LuVideoOff } from "react-icons/lu";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type LivePlayerProps = {
|
type LivePlayerProps = {
|
||||||
cameraRef?: (ref: HTMLDivElement | null) => void;
|
cameraRef?: (ref: HTMLDivElement | null) => void;
|
||||||
@ -71,6 +72,8 @@ export default function LivePlayer({
|
|||||||
onError,
|
onError,
|
||||||
onResetLiveMode,
|
onResetLiveMode,
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
|
const { t } = useTranslation(["components/player"]);
|
||||||
|
|
||||||
const internalContainerRef = useRef<HTMLDivElement | null>(null);
|
const internalContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// stats
|
// stats
|
||||||
@ -272,7 +275,7 @@ export default function LivePlayer({
|
|||||||
} else {
|
} else {
|
||||||
player = (
|
player = (
|
||||||
<div className="w-5xl text-center text-sm">
|
<div className="w-5xl text-center text-sm">
|
||||||
iOS 17.1 or greater is required for this live stream type.
|
{t("livePlayerRequiredIOSVersion")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -400,12 +403,17 @@ export default function LivePlayer({
|
|||||||
{offline && !showStillWithoutActivity && cameraEnabled && (
|
{offline && !showStillWithoutActivity && cameraEnabled && (
|
||||||
<div className="absolute inset-0 left-1/2 top-1/2 flex h-96 w-96 -translate-x-1/2 -translate-y-1/2">
|
<div className="absolute inset-0 left-1/2 top-1/2 flex h-96 w-96 -translate-x-1/2 -translate-y-1/2">
|
||||||
<div className="flex flex-col items-center justify-center rounded-lg bg-background/50 p-5">
|
<div className="flex flex-col items-center justify-center rounded-lg bg-background/50 p-5">
|
||||||
<p className="my-5 text-lg">Stream offline</p>
|
<p className="my-5 text-lg">{t("streamOffline.title")}</p>
|
||||||
<TbExclamationCircle className="mb-3 size-10" />
|
<TbExclamationCircle className="mb-3 size-10" />
|
||||||
<p className="max-w-96 text-center">
|
<p className="max-w-96 text-center">
|
||||||
No frames have been received on the{" "}
|
<Trans
|
||||||
{capitalizeFirstLetter(cameraConfig.name)} <code>detect</code>{" "}
|
values={{
|
||||||
stream, check error logs
|
cameraName: capitalizeFirstLetter(cameraConfig.name),
|
||||||
|
ns: "components/player",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
streamOffline.desc
|
||||||
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -416,7 +424,7 @@ export default function LivePlayer({
|
|||||||
<div className="flex h-32 flex-col items-center justify-center rounded-lg p-4 md:h-48 md:w-48">
|
<div className="flex h-32 flex-col items-center justify-center rounded-lg p-4 md:h-48 md:w-48">
|
||||||
<LuVideoOff className="mb-2 size-8 md:size-10" />
|
<LuVideoOff className="mb-2 size-8 md:size-10" />
|
||||||
<p className="max-w-32 text-center text-sm md:max-w-40 md:text-base">
|
<p className="max-w-32 text-center text-sm md:max-w-40 md:text-base">
|
||||||
Camera is disabled
|
{t("cameraDisabled")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PlayerStatsType } from "@/types/live";
|
import { PlayerStatsType } from "@/types/live";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type PlayerStatsProps = {
|
type PlayerStatsProps = {
|
||||||
stats: PlayerStatsType;
|
stats: PlayerStatsType;
|
||||||
@ -7,45 +8,46 @@ type PlayerStatsProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
||||||
|
const { t } = useTranslation(["components/player"]);
|
||||||
const fullStatsContent = (
|
const fullStatsContent = (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Stream Type:</span>{" "}
|
<span className="text-white/70">{t("stats.streamType")}</span>{" "}
|
||||||
<span className="text-white">{stats.streamType}</span>
|
<span className="text-white">{stats.streamType}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Bandwidth:</span>{" "}
|
<span className="text-white/70">{t("stats.bandwidth")}</span>{" "}
|
||||||
<span className="text-white">{stats.bandwidth.toFixed(2)} kbps</span>
|
<span className="text-white">{stats.bandwidth.toFixed(2)} kbps</span>
|
||||||
</p>
|
</p>
|
||||||
{stats.latency != undefined && (
|
{stats.latency != undefined && (
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Latency:</span>{" "}
|
<span className="text-white/70">{t("stats.latency")}</span>{" "}
|
||||||
<span
|
<span
|
||||||
className={`text-white ${stats.latency > 2 ? "text-danger" : ""}`}
|
className={`text-white ${stats.latency > 2 ? "text-danger" : ""}`}
|
||||||
>
|
>
|
||||||
{stats.latency.toFixed(2)} seconds
|
{t("stats.latency.value", { secounds: stats.latency.toFixed(2) })}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Total Frames:</span>{" "}
|
<span className="text-white/70">{t("stats.totalFrames")}</span>{" "}
|
||||||
<span className="text-white">{stats.totalFrames}</span>
|
<span className="text-white">{stats.totalFrames}</span>
|
||||||
</p>
|
</p>
|
||||||
{stats.droppedFrames != undefined && (
|
{stats.droppedFrames != undefined && (
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Dropped Frames:</span>{" "}
|
<span className="text-white/70">{t("stats.droppedFrames")}</span>{" "}
|
||||||
<span className="text-white">{stats.droppedFrames}</span>
|
<span className="text-white">{stats.droppedFrames}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{stats.decodedFrames != undefined && (
|
{stats.decodedFrames != undefined && (
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Decoded Frames:</span>{" "}
|
<span className="text-white/70">{t("stats.decodedFrames")}</span>{" "}
|
||||||
<span className="text-white">{stats.decodedFrames}</span>
|
<span className="text-white">{stats.decodedFrames}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{stats.droppedFrameRate != undefined && (
|
{stats.droppedFrameRate != undefined && (
|
||||||
<p>
|
<p>
|
||||||
<span className="text-white/70">Dropped Frame Rate:</span>{" "}
|
<span className="text-white/70">{t("stats.droppedFrameRate")}</span>{" "}
|
||||||
<span className="text-white">
|
<span className="text-white">
|
||||||
{stats.droppedFrameRate.toFixed(2)}%
|
{stats.droppedFrameRate.toFixed(2)}%
|
||||||
</span>
|
</span>
|
||||||
@ -57,27 +59,35 @@ export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
|||||||
const minimalStatsContent = (
|
const minimalStatsContent = (
|
||||||
<div className="flex flex-row items-center justify-center gap-4">
|
<div className="flex flex-row items-center justify-center gap-4">
|
||||||
<div className="flex flex-col items-center justify-start gap-1">
|
<div className="flex flex-col items-center justify-start gap-1">
|
||||||
<span className="text-white/70">Type</span>
|
<span className="text-white/70">{t("stats.streamType.short")}</span>
|
||||||
<span className="text-white">{stats.streamType}</span>
|
<span className="text-white">{stats.streamType}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col items-center gap-1">
|
||||||
<span className="text-white/70">Bandwidth</span>{" "}
|
<span className="text-white/70">{t("stats.bandwidth.short")}</span>{" "}
|
||||||
<span className="text-white">{stats.bandwidth.toFixed(2)} kbps</span>
|
<span className="text-white">{stats.bandwidth.toFixed(2)} kbps</span>
|
||||||
</div>
|
</div>
|
||||||
{stats.latency != undefined && (
|
{stats.latency != undefined && (
|
||||||
<div className="hidden flex-col items-center gap-1 md:flex">
|
<div className="hidden flex-col items-center gap-1 md:flex">
|
||||||
<span className="text-white/70">Latency</span>
|
<span className="text-white/70">{t("stats.latency.short")}</span>
|
||||||
<span
|
<span
|
||||||
className={`text-white ${stats.latency >= 2 ? "text-danger" : ""}`}
|
className={`text-white ${stats.latency >= 2 ? "text-danger" : ""}`}
|
||||||
>
|
>
|
||||||
{stats.latency.toFixed(2)} sec
|
{t("stats.latency.short.value", {
|
||||||
|
secounds: stats.latency.toFixed(2),
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{stats.droppedFrames != undefined && (
|
{stats.droppedFrames != undefined && (
|
||||||
<div className="flex flex-col items-center justify-end gap-1">
|
<div className="flex flex-col items-center justify-end gap-1">
|
||||||
<span className="text-white/70">Dropped</span>
|
<span className="text-white/70">
|
||||||
<span className="text-white">{stats.droppedFrames} frames</span>
|
{t("stats.droppedFrames.short")}
|
||||||
|
</span>
|
||||||
|
<span className="text-white">
|
||||||
|
{t("stats.droppedFrames.short.value", {
|
||||||
|
droppedFrames: stats.droppedFrames,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user