add more translation

This commit is contained in:
ZhaiSoul 2024-12-26 22:22:38 +08:00
parent f45242d22c
commit 2ace4b896a
12 changed files with 356 additions and 52 deletions

View File

@ -1,12 +1,106 @@
{ {
"object.person": "Person", "object.person": "Person",
"object.cat": "Cat", "object.bicycle": "Bicycle",
"object.car": "Car", "object.car": "Car",
"object.motorcycle": "Motorcycle",
"object.airplane": "Airplane",
"object.bus": "Bus",
"object.train": "Train",
"object.boat": "Boat",
"object.traffic_light": "Traffic Light",
"object.fire_hydrant": "Fire Hydrant",
"object.street_sign": "Street Sign",
"object.stop_sign": "Stop Sign",
"object.parking_meter": "Parking Meter",
"object.bench": "Bench",
"object.bird": "Bird",
"object.cat": "Cat",
"object.dog": "Dog",
"object.horse": "Horse",
"object.sheep": "Sheep",
"object.cow": "Cow",
"object.elephant": "Elephant",
"object.bear": "Bear",
"object.zebra": "Zebra",
"object.giraffe": "Giraffe",
"object.hat": "Hat",
"object.backpack": "Backpack",
"object.umbrella": "Umbrella",
"object.shoe": "Shoe",
"object.eye_glasses": "Eye Glasses",
"object.handbag": "Handbag",
"object.tie": "Tie",
"object.suitcase": "Suitcase",
"object.frisbee": "Frisbee",
"object.skis": "Skis",
"object.snowboard": "Snowboard",
"object.sports_ball": "Sports Ball",
"object.kite": "Kite",
"object.baseball_bat": "Baseball Bat",
"object.baseball_glove": "Baseball Glove",
"object.skateboard": "Skateboard",
"object.surfboard": "Surfboard",
"object.tennis_racket": "Tennis Racket",
"object.bottle": "Bottle",
"object.plate": "Plate",
"object.wine_glass": "Wine Glass",
"object.cup": "Cup",
"object.fork": "Fork",
"object.knife": "Knife",
"object.spoon": "Spoon",
"object.bowl": "Bowl",
"object.banana": "Banana",
"object.apple": "Apple",
"object.sandwich": "Sandwich",
"object.orange": "Orange",
"object.broccoli": "Broccoli",
"object.carrot": "Carrot",
"object.hot_dog": "Hot Dog",
"object.pizza": "Pizza",
"object.donut": "Donut",
"object.cake": "Cake",
"object.chair": "Chair",
"object.couch": "Couch",
"object.potted_plant": "Potted Plant",
"object.bed": "Bed",
"object.mirror": "Mirror",
"object.dining_table": "Dining Table",
"object.window": "Window",
"object.desk": "Desk",
"object.toilet": "Toilet",
"object.door": "Door",
"object.tv": "TV",
"object.laptop": "Laptop",
"object.mouse": "Mouse",
"object.remote": "Remote",
"object.keyboard": "Keyboard",
"object.cell_phone": "Cell Phone",
"object.microwave": "Microwave",
"object.oven": "Oven",
"object.toaster": "Toaster",
"object.sink": "Sink",
"object.refrigerator": "Refrigerator",
"object.blender": "Blender",
"object.book": "Book",
"object.clock": "Clock",
"object.vase": "Vase",
"object.scissors": "Scissors",
"object.teddy_bear": "Teddy Bear",
"object.hair_dryer": "Hair Dryer",
"object.toothbrush": "Toothbrush",
"object.hair_brush": "Hair Brush",
"ui.time.justNow": "Just now", "ui.time.justNow": "Just now",
"ui.dialog.restart.title": "Are you sure you want to restart Frigate?",
"ui.dialog.restart.button": "Restart",
"ui.dialog.restart.restarting.title": "Frigate is Restarting",
"ui.dialog.restart.restarting.content": "This page will reload in {{countdown}} seconds.",
"ui.dialog.restart.restarting.button": "Force Reload Now",
"ui.stats.ffmpegHighCpuUsage": "{{camera}} has high FFMPEG CPU usage ({{ffmpegAvg}}%)", "ui.stats.ffmpegHighCpuUsage": "{{camera}} has high FFMPEG CPU usage ({{ffmpegAvg}}%)",
"ui.stats.detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)", "ui.stats.detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)",
"ui.stats.healthy": "System is healthy",
"ui.system.general": "General", "ui.system.general": "General",
"ui.system.storage": "Storage", "ui.system.storage": "Storage",
@ -116,7 +210,38 @@
"ui.save": "Save", "ui.save": "Save",
"ui.saving": "Saving...", "ui.saving": "Saving...",
"ui.cancel": "Cancel", "ui.cancel": "Cancel",
"ui.close": "Close",
"ui.copy": "Copy", "ui.copy": "Copy",
"ui.back": "Back",
"ui.history": "History",
"ui.fullscreen": "Fullscreen",
"ui.pictureInPicture": "Picture in Picture",
"ui.on": "ON",
"ui.off": "OFF",
"ui.delete": "Delete",
"ui.live.twoWayTalk.enable": "Enable Two Way Talk",
"ui.live.twoWayTalk.disable": "Disable Two Way Talk",
"ui.live.cameraAudio.enable": "Enable Camera Audio",
"ui.live.cameraAudio.disable": "Disable Camera Audio",
"ui.live.ptz.move.left.label": "Move PTZ camera to the left",
"ui.live.ptz.move.up.label": "Move PTZ camera up",
"ui.live.ptz.move.down.label": "Move PTZ camera down",
"ui.live.ptz.move.right.label": "Move PTZ camera to the right",
"ui.live.ptz.zoom.in.label": "Zoom PTZ camera in",
"ui.live.ptz.zoom.out.label": "Zoom PTZ camera out",
"ui.live.ptz.frame.center.label": "Click in the frame to center the PTZ camera",
"ui.live.detect.enable": "Enable Detect",
"ui.live.detect.disable": "Disable Detect",
"ui.live.recording.enable": "Enable Recording",
"ui.live.recording.disable": "Disable Recording",
"ui.live.snapshots.enable": "Enable Snapshots",
"ui.live.snapshots.disable": "Disable Snapshots",
"ui.live.audioDetect.enable": "Enable Audio Detect",
"ui.live.audioDetect.disable": "Disable Audio Detect",
"ui.live.autotracking.enable": "Enable Autotracking",
"ui.live.autotracking.disable": "Disable Autotracking",
"ui.calendarFilter.last24Hours": "Last 24 Hours", "ui.calendarFilter.last24Hours": "Last 24 Hours",
@ -256,6 +381,27 @@
"ui.settingView.debug.regions.desc": "Show a box of the region of interest sent to the object detector", "ui.settingView.debug.regions.desc": "Show a box of the region of interest sent to the object detector",
"ui.settingView.debug.regions.tips": "<p className=\"mb-2\"><strong>Region Boxes</strong></p><br><p>Bright green boxes will be overlaid on areas of interest in the frame that are being sent to the object detector.</p>", "ui.settingView.debug.regions.tips": "<p className=\"mb-2\"><strong>Region Boxes</strong></p><br><p>Bright green boxes will be overlaid on areas of interest in the frame that are being sent to the object detector.</p>",
"ui.settingView.users": "Users",
"ui.settingView.users.addUser": "Add User",
"ui.settingView.users.updatePassword": "Update Password",
"ui.settingView.users.dialog.createUser": "Create User",
"ui.settingView.users.dialog.createUser.user": "User",
"ui.settingView.users.dialog.createUser.password": "Password",
"ui.settingView.users.dialog.deleteUser": "Delete User",
"ui.settingView.users.dialog.deleteUser.warn": "Are you sure?",
"ui.settingView.users.dialog.setPassword": "Set Password",
"ui.settingView.notification": "Notifications",
"ui.settingView.notification.notificationSettings": "Notification Settings",
"ui.settingView.notification.desc": "Frigate can natively send push notifications to your device when it is running in the browser or installed as a PWA.",
"ui.settingView.notification.documentation": "Read the Documentation",
"ui.settingView.notification.email": "Email",
"ui.settingView.notification.email.placeholder": "example@email.com",
"ui.settingView.notification.email.desc": "Entering a valid email is required, as this is used by the push server in case problems occur.",
"ui.settingView.notification.registerDevice": "Register for notifications on this device",
"ui.settingView.notification.unregisterDevice": "Unregister for notifications on this device",
"ui.configEditorView.configEditor": "Config Editor", "ui.configEditorView.configEditor": "Config Editor",
"ui.configEditorView.copyConfig": "Copy Config", "ui.configEditorView.copyConfig": "Copy Config",
"ui.configEditorView.saveAndRestart": "Save & Restart", "ui.configEditorView.saveAndRestart": "Save & Restart",

View File

@ -1,13 +1,107 @@
{ {
"object.person": "人", "object.person": "人",
"object.bicycle": "自行车",
"object.car": "汽车",
"object.motorcycle": "摩托车",
"object.airplane": "飞机",
"object.bus": "公交车",
"object.train": "火车",
"object.boat": "船",
"object.traffic_light": "交通灯",
"object.fire_hydrant": "消防栓",
"object.street_sign": "路标",
"object.stop_sign": "停车标志",
"object.parking_meter": "停车计时器",
"object.bench": "长椅",
"object.bird": "鸟",
"object.cat": "猫", "object.cat": "猫",
"object.car": "车", "object.dog": "狗",
"object.horse": "马",
"object.sheep": "羊",
"object.cow": "牛",
"object.elephant": "大象",
"object.bear": "熊",
"object.zebra": "斑马",
"object.giraffe": "长颈鹿",
"object.hat": "帽子",
"object.backpack": "背包",
"object.umbrella": "雨伞",
"object.shoe": "鞋子",
"object.eye_glasses": "眼镜",
"object.handbag": "手提包",
"object.tie": "领带",
"object.suitcase": "手提箱",
"object.frisbee": "飞盘",
"object.skis": "滑雪板",
"object.snowboard": "滑雪板",
"object.sports_ball": "运动球",
"object.kite": "风筝",
"object.baseball_bat": "棒球棒",
"object.baseball_glove": "棒球手套",
"object.skateboard": "滑板",
"object.surfboard": "冲浪板",
"object.tennis_racket": "网球拍",
"object.bottle": "瓶子",
"object.plate": "盘子",
"object.wine_glass": "酒杯",
"object.cup": "杯子",
"object.fork": "叉子",
"object.knife": "刀",
"object.spoon": "勺子",
"object.bowl": "碗",
"object.banana": "香蕉",
"object.apple": "苹果",
"object.sandwich": "三明治",
"object.orange": "橙子",
"object.broccoli": "西兰花",
"object.carrot": "胡萝卜",
"object.hot_dog": "热狗",
"object.pizza": "披萨",
"object.donut": "甜甜圈",
"object.cake": "蛋糕",
"object.chair": "椅子",
"object.couch": "沙发",
"object.potted_plant": "盆栽植物",
"object.bed": "床",
"object.mirror": "镜子",
"object.dining_table": "餐桌",
"object.window": "窗户",
"object.desk": "桌子",
"object.toilet": "厕所",
"object.door": "门",
"object.tv": "电视",
"object.laptop": "笔记本电脑",
"object.mouse": "鼠标",
"object.remote": "遥控器",
"object.keyboard": "键盘",
"object.cell_phone": "手机",
"object.microwave": "微波炉",
"object.oven": "烤箱",
"object.toaster": "烤面包机",
"object.sink": "水槽",
"object.refrigerator": "冰箱",
"object.blender": "搅拌机",
"object.book": "书",
"object.clock": "时钟",
"object.vase": "花瓶",
"object.scissors": "剪刀",
"object.teddy_bear": "泰迪熊",
"object.hair_dryer": "吹风机",
"object.toothbrush": "牙刷",
"object.hair_brush": "发刷",
"ui.time.justNow": "刚才", "ui.time.justNow": "刚才",
"ui.dialog.restart.title": "你确定要重启 Frigate?",
"ui.dialog.restart.button": "重启",
"ui.dialog.restart.restarting.title": "Frigate 正在重启",
"ui.dialog.restart.restarting.content": "该页面将会在 {{countdown}} 秒后自动刷新。",
"ui.dialog.restart.restarting.button": "强制刷新",
"ui.stats.ffmpegHighCpuUsage": "{{camera}} 的 FFMPEG CPU 使用率较高({{ffmpegAvg}}%", "ui.stats.ffmpegHighCpuUsage": "{{camera}} 的 FFMPEG CPU 使用率较高({{ffmpegAvg}}%",
"ui.stats.detectHighCpuUsage": "{{camera}} 的 探测 CPU 使用率较高({{detectAvg}}%", "ui.stats.detectHighCpuUsage": "{{camera}} 的 探测 CPU 使用率较高({{detectAvg}}%",
"ui.stats.healthy": "系统运行正常",
"ui.system.general": "常规", "ui.system.general": "常规",
"ui.system.storage": "存储", "ui.system.storage": "存储",
@ -117,7 +211,39 @@
"ui.save": "保存", "ui.save": "保存",
"ui.saving": "保存中……", "ui.saving": "保存中……",
"ui.cancel": "取消", "ui.cancel": "取消",
"ui.close": "关闭",
"ui.copy": "复制", "ui.copy": "复制",
"ui.back": "返回",
"ui.history": "历史",
"ui.fullscreen": "全屏",
"ui.exitFullscreen": "退出全屏",
"ui.pictureInPicture": "画中画",
"ui.on": "开",
"ui.off": "关",
"ui.delete": "删除",
"ui.live.twoWayTalk.enable": "开启双向对话",
"ui.live.twoWayTalk.disable": "关闭双向对话",
"ui.live.cameraAudio.enable": "开启摄像头音频",
"ui.live.cameraAudio.disable": "关闭摄像头音频",
"ui.live.ptz.move.left.label": "PTZ摄像头向左移动",
"ui.live.ptz.move.up.label": "PTZ摄像头向上移动",
"ui.live.ptz.move.down.label": "PTZ摄像头向下移动",
"ui.live.ptz.move.right.label": "PTZ摄像头向右移动",
"ui.live.ptz.zoom.in.label": "PTZ摄像头放大",
"ui.live.ptz.zoom.out.label": "PTZ摄像头缩小",
"ui.live.ptz.frame.center.label": "点击将PTZ摄像头画面居中",
"ui.live.detect.enable": "启用检测",
"ui.live.detect.disable": "关闭检测",
"ui.live.recording.enable": "启用录制",
"ui.live.recording.disable": "关闭录制",
"ui.live.snapshots.enable": "启用快照",
"ui.live.snapshots.disable": "关闭快照",
"ui.live.audioDetect.enable": "启用音频检测",
"ui.live.audioDetect.disable": "关闭音频检测",
"ui.live.autotracking.enable": "启用自动追踪",
"ui.live.autotracking.disable": "关闭自动追踪",
"ui.calendarFilter.last24Hours": "过去24小时", "ui.calendarFilter.last24Hours": "过去24小时",
@ -257,6 +383,27 @@
"ui.settingView.debug.regions.desc": "显示发送到运动检测器感兴趣范围的框。", "ui.settingView.debug.regions.desc": "显示发送到运动检测器感兴趣范围的框。",
"ui.settingView.debug.regions.tips": "<p className=\"mb-2\"><strong>范围框</strong></p><br><p>将在帧中发送到目标检测器的感兴趣范围上叠加绿色框。</p>", "ui.settingView.debug.regions.tips": "<p className=\"mb-2\"><strong>范围框</strong></p><br><p>将在帧中发送到目标检测器的感兴趣范围上叠加绿色框。</p>",
"ui.settingView.users": "用户管理",
"ui.settingView.users.addUser": "添加用户",
"ui.settingView.users.updatePassword": "修改密码",
"ui.settingView.users.dialog.createUser": "创建用户",
"ui.settingView.users.dialog.createUser.user": "用户名",
"ui.settingView.users.dialog.createUser.password": "密码",
"ui.settingView.users.dialog.deleteUser": "删除该用户",
"ui.settingView.users.dialog.deleteUser.warn": "你确定要删除该用户吗?",
"ui.settingView.users.dialog.setPassword": "修改密码",
"ui.settingView.notification": "通知",
"ui.settingView.notification.notificationSettings": "通知设置",
"ui.settingView.notification.desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
"ui.settingView.notification.documentation": "阅读文档(英文)",
"ui.settingView.notification.email": "电子邮箱",
"ui.settingView.notification.email.placeholder": "例如example@email.com",
"ui.settingView.notification.email.desc": "Frigate 在浏览器中运行或作为 PWA 应用安装时,可以原生向你的设备发送推送通知。",
"ui.settingView.notification.registerDevice": "为这个设备注册通知",
"ui.settingView.notification.unregisterDevice": "取消这个设备注册通知",
"ui.configEditorView.configEditor": "配置编辑器", "ui.configEditorView.configEditor": "配置编辑器",
"ui.configEditorView.copyConfig": "复制配置", "ui.configEditorView.copyConfig": "复制配置",
"ui.configEditorView.saveAndRestart": "保存并重启", "ui.configEditorView.saveAndRestart": "保存并重启",

View File

@ -5,6 +5,7 @@ import {
} from "@/context/statusbar-provider"; } from "@/context/statusbar-provider";
import useStats, { useAutoFrigateStats } from "@/hooks/use-stats"; import useStats, { useAutoFrigateStats } from "@/hooks/use-stats";
import { useContext, useEffect, useMemo } from "react"; import { useContext, useEffect, useMemo } from "react";
import { Trans } from "react-i18next";
import { FaCheck } from "react-icons/fa"; import { FaCheck } from "react-icons/fa";
import { IoIosWarning } from "react-icons/io"; import { IoIosWarning } from "react-icons/io";
import { MdCircle } from "react-icons/md"; import { MdCircle } from "react-icons/md";
@ -129,7 +130,7 @@ export default function Statusbar() {
{Object.entries(messages).length === 0 ? ( {Object.entries(messages).length === 0 ? (
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center gap-2 text-sm">
<FaCheck className="size-3 text-green-500" /> <FaCheck className="size-3 text-green-500" />
System is healthy <Trans>ui.stats.healthy</Trans>
</div> </div>
) : ( ) : (
Object.entries(messages).map(([key, messageArray]) => ( Object.entries(messages).map(([key, messageArray]) => (

View File

@ -20,6 +20,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "../ui/dialog"; } from "../ui/dialog";
import { Trans } from "react-i18next";
type CreateUserOverlayProps = { type CreateUserOverlayProps = {
show: boolean; show: boolean;
@ -63,7 +64,7 @@ export default function CreateUserDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Create User</DialogTitle> <DialogTitle><Trans>ui.settingView.users.dialog.createUser</Trans></DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}> <form onSubmit={form.handleSubmit(onSubmit)}>
@ -71,7 +72,7 @@ export default function CreateUserDialog({
name="user" name="user"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>User</FormLabel> <FormLabel><Trans>ui.settingView.users.dialog.createUser.user</Trans></FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -86,7 +87,7 @@ export default function CreateUserDialog({
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Password</FormLabel> <FormLabel><Trans>ui.settingView.users.dialog.createUser.password</Trans></FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -104,7 +105,7 @@ export default function CreateUserDialog({
disabled={isLoading} disabled={isLoading}
> >
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />} {isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
Create User <Trans>ui.settingView.users.dialog.createUser</Trans>
</Button> </Button>
</DialogFooter> </DialogFooter>
</form> </form>

View File

@ -1,3 +1,4 @@
import { Trans } from "react-i18next";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { import {
Dialog, Dialog,
@ -21,9 +22,9 @@ export default function DeleteUserDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Delete User</DialogTitle> <DialogTitle><Trans>ui.settingView.users.dialog.deleteUser</Trans></DialogTitle>
</DialogHeader> </DialogHeader>
<div>Are you sure?</div> <div><Trans>ui.settingView.users.dialog.deleteUser.warn</Trans></div>
<DialogFooter> <DialogFooter>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"
@ -32,7 +33,7 @@ export default function DeleteUserDialog({
size="sm" size="sm"
onClick={onDelete} onClick={onDelete}
> >
Delete <Trans>ui.delete</Trans>
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -8,6 +8,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "../ui/dialog"; } from "../ui/dialog";
import { Trans } from "react-i18next";
type SetPasswordProps = { type SetPasswordProps = {
show: boolean; show: boolean;
@ -25,7 +26,7 @@ export default function SetPasswordDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}> <DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader> <DialogHeader>
<DialogTitle>Set Password</DialogTitle> <DialogTitle><Trans>ui.settingView.users.dialog.setPassword</Trans></DialogTitle>
</DialogHeader> </DialogHeader>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
@ -43,7 +44,7 @@ export default function SetPasswordDialog({
onSave(password!); onSave(password!);
}} }}
> >
Save <Trans>ui.save</Trans>
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -18,6 +18,8 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import { baseUrl } from "@/api/baseUrl"; import { baseUrl } from "@/api/baseUrl";
import { t } from "i18next";
import { Trans } from "react-i18next";
type RestartDialogProps = { type RestartDialogProps = {
isOpen: boolean; isOpen: boolean;
@ -79,13 +81,13 @@ export default function RestartDialog({
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle> <AlertDialogTitle>
Are you sure you want to restart Frigate? <Trans>ui.dialog.restart.title</Trans>
</AlertDialogTitle> </AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel><Trans>ui.cancel</Trans></AlertDialogCancel>
<AlertDialogAction onClick={handleRestart}> <AlertDialogAction onClick={handleRestart}>
Restart <Trans>ui.dialog.restart.button</Trans>
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
@ -100,10 +102,10 @@ export default function RestartDialog({
<ActivityIndicator /> <ActivityIndicator />
<SheetHeader className="mt-5 text-center"> <SheetHeader className="mt-5 text-center">
<SheetTitle className="text-center"> <SheetTitle className="text-center">
Frigate is Restarting <Trans>ui.dialog.restart.restarting.title</Trans>
</SheetTitle> </SheetTitle>
<SheetDescription className="text-center"> <SheetDescription className="text-center">
<div>This page will reload in {countdown} seconds.</div> <div>{t("ui.dialog.restart.restarting.content", {countdown})}</div>
</SheetDescription> </SheetDescription>
</SheetHeader> </SheetHeader>
<Button <Button
@ -112,7 +114,7 @@ export default function RestartDialog({
aria-label="Force reload now" aria-label="Force reload now"
onClick={handleForceReload} onClick={handleForceReload}
> >
Force Reload Now <Trans>ui.dialog.restart.restarting.button</Trans>
</Button> </Button>
</div> </div>
</SheetContent> </SheetContent>

View File

@ -42,6 +42,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import { t } from "i18next";
type DraggableGridLayoutProps = { type DraggableGridLayoutProps = {
cameras: CameraConfig[]; cameras: CameraConfig[];
@ -519,7 +520,7 @@ export default function DraggableGridLayout({
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{fullscreen ? "Exit Fullscreen" : "Fullscreen"} {fullscreen ? t("ui.exitFullscreen") : t("ui.fullscreen")}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</> </>

View File

@ -85,6 +85,8 @@ import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr"; import useSWR from "swr";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useSessionPersistence } from "@/hooks/use-session-persistence"; import { useSessionPersistence } from "@/hooks/use-session-persistence";
import { Trans } from "react-i18next";
import { t } from "i18next";
type LiveCameraViewProps = { type LiveCameraViewProps = {
config?: FrigateConfig; config?: FrigateConfig;
@ -363,7 +365,7 @@ export default function LiveCameraView({
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
> >
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && <div className="text-primary">Back</div>} {isDesktop && <div className="text-primary"><Trans>ui.back</Trans></div>}
</Button> </Button>
<Button <Button
className="flex items-center gap-2.5 rounded-lg" className="flex items-center gap-2.5 rounded-lg"
@ -383,7 +385,7 @@ export default function LiveCameraView({
}} }}
> >
<LuHistory className="size-5 text-secondary-foreground" /> <LuHistory className="size-5 text-secondary-foreground" />
{isDesktop && <div className="text-primary">History</div>} {isDesktop && <div className="text-primary"><Trans>ui.history</Trans></div>}
</Button> </Button>
</div> </div>
) : ( ) : (
@ -412,7 +414,7 @@ export default function LiveCameraView({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={fullscreen ? FaCompress : FaExpand} Icon={fullscreen ? FaCompress : FaExpand}
isActive={fullscreen} isActive={fullscreen}
title={fullscreen ? "Close" : "Fullscreen"} title={fullscreen ? t("ui.close") : t("ui.fullscreen")}
onClick={toggleFullscreen} onClick={toggleFullscreen}
/> />
)} )}
@ -422,7 +424,7 @@ export default function LiveCameraView({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={LuPictureInPicture} Icon={LuPictureInPicture}
isActive={pip} isActive={pip}
title={pip ? "Close" : "Picture in Picture"} title={pip ? t("ui.close") : t("ui.pictureInPicture")}
onClick={() => { onClick={() => {
if (!pip) { if (!pip) {
setPip(true); setPip(true);
@ -665,7 +667,7 @@ function PtzControlPanel({
{ptz?.features?.includes("pt") && ( {ptz?.features?.includes("pt") && (
<> <>
<TooltipButton <TooltipButton
label="Move camera left" label={t("ui.live.ptz.move.left.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("MOVE_LEFT"); sendPtz("MOVE_LEFT");
@ -680,7 +682,7 @@ function PtzControlPanel({
<FaAngleLeft /> <FaAngleLeft />
</TooltipButton> </TooltipButton>
<TooltipButton <TooltipButton
label="Move camera up" label={t("ui.live.ptz.move.up.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("MOVE_UP"); sendPtz("MOVE_UP");
@ -695,7 +697,7 @@ function PtzControlPanel({
<FaAngleUp /> <FaAngleUp />
</TooltipButton> </TooltipButton>
<TooltipButton <TooltipButton
label="Move camera down" label={t("ui.live.ptz.move.down.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("MOVE_DOWN"); sendPtz("MOVE_DOWN");
@ -710,7 +712,7 @@ function PtzControlPanel({
<FaAngleDown /> <FaAngleDown />
</TooltipButton> </TooltipButton>
<TooltipButton <TooltipButton
label="Move camera right" label={t("ui.live.ptz.move.right.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("MOVE_RIGHT"); sendPtz("MOVE_RIGHT");
@ -729,7 +731,7 @@ function PtzControlPanel({
{ptz?.features?.includes("zoom") && ( {ptz?.features?.includes("zoom") && (
<> <>
<TooltipButton <TooltipButton
label="Zoom in" label={t("ui.live.ptz.zoom.in.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("ZOOM_IN"); sendPtz("ZOOM_IN");
@ -744,7 +746,7 @@ function PtzControlPanel({
<MdZoomIn /> <MdZoomIn />
</TooltipButton> </TooltipButton>
<TooltipButton <TooltipButton
label="Zoom out" label={t("ui.live.ptz.zoom.out.label")}
onMouseDown={(e) => { onMouseDown={(e) => {
e.preventDefault(); e.preventDefault();
sendPtz("ZOOM_OUT"); sendPtz("ZOOM_OUT");
@ -847,7 +849,7 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff} Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
isActive={detectState == "ON"} isActive={detectState == "ON"}
title={`${detectState == "ON" ? "Disable" : "Enable"} Detect`} title={detectState == "ON" ? t("ui.live.detect.disable") : t("ui.live.detect.enable")}
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")} onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
/> />
<CameraFeatureToggle <CameraFeatureToggle
@ -855,7 +857,7 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={recordState == "ON" ? LuVideo : LuVideoOff} Icon={recordState == "ON" ? LuVideo : LuVideoOff}
isActive={recordState == "ON"} isActive={recordState == "ON"}
title={`${recordState == "ON" ? "Disable" : "Enable"} Recording`} title={recordState == "ON" ? t("ui.live.recording.disable") : t("ui.live.recording.enable")}
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")} onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
/> />
<CameraFeatureToggle <CameraFeatureToggle
@ -863,7 +865,7 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography} Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
isActive={snapshotState == "ON"} isActive={snapshotState == "ON"}
title={`${snapshotState == "ON" ? "Disable" : "Enable"} Snapshots`} title={snapshotState == "ON" ? t("ui.live.snapshots.disable") : t("ui.live.snapshots.enable")}
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")} onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
/> />
{audioDetectEnabled && ( {audioDetectEnabled && (
@ -872,7 +874,7 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={audioState == "ON" ? LuEar : LuEarOff} Icon={audioState == "ON" ? LuEar : LuEarOff}
isActive={audioState == "ON"} isActive={audioState == "ON"}
title={`${audioState == "ON" ? "Disable" : "Enable"} Audio Detect`} title={audioState == "ON" ? t("ui.live.audioDetect.disable") : t("ui.live.audioDetect.enable")}
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")} onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
/> />
)} )}
@ -882,7 +884,7 @@ function FrigateCameraFeatures({
variant={fullscreen ? "overlay" : "primary"} variant={fullscreen ? "overlay" : "primary"}
Icon={autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff} Icon={autotrackingState == "ON" ? TbViewfinder : TbViewfinderOff}
isActive={autotrackingState == "ON"} isActive={autotrackingState == "ON"}
title={`${autotrackingState == "ON" ? "Disable" : "Enable"} Autotracking`} title={autotrackingState == "ON" ? t("ui.live.autotracking.disable") : t("ui.live.autotracking.enable")}
onClick={() => onClick={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
} }

View File

@ -32,6 +32,7 @@ import { LivePlayerError } from "@/types/live";
import { FaCompress, FaExpand } from "react-icons/fa"; import { FaCompress, FaExpand } from "react-icons/fa";
import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { t } from "i18next";
type LiveDashboardViewProps = { type LiveDashboardViewProps = {
cameras: CameraConfig[]; cameras: CameraConfig[];
@ -387,7 +388,7 @@ export default function LiveDashboardView({
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{fullscreen ? "Exit Fullscreen" : "Fullscreen"} {fullscreen ? t("ui.exitFullscreen") : t("ui.fullscreen")}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -15,6 +15,7 @@ import { Card } from "@/components/ui/card";
import { HiTrash } from "react-icons/hi"; import { HiTrash } from "react-icons/hi";
import { FaUserEdit } from "react-icons/fa"; import { FaUserEdit } from "react-icons/fa";
import { LuPlus } from "react-icons/lu"; import { LuPlus } from "react-icons/lu";
import { Trans } from "react-i18next";
export default function AuthenticationView() { export default function AuthenticationView() {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -91,7 +92,7 @@ export default function AuthenticationView() {
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0"> <div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
<div className="flex flex-row items-center justify-between gap-2"> <div className="flex flex-row items-center justify-between gap-2">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Users <Trans>ui.settingView.users</Trans>
</Heading> </Heading>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"
@ -102,7 +103,7 @@ export default function AuthenticationView() {
}} }}
> >
<LuPlus className="text-secondary-foreground" /> <LuPlus className="text-secondary-foreground" />
Add User <Trans>ui.settingView.users.addUser</Trans>
</Button> </Button>
</div> </div>
<div className="mt-3 space-y-3"> <div className="mt-3 space-y-3">
@ -123,7 +124,7 @@ export default function AuthenticationView() {
}} }}
> >
<FaUserEdit /> <FaUserEdit />
<div className="hidden md:block">Update Password</div> <div className="hidden md:block"><Trans>ui.settingView.users.updatePassword</Trans></div>
</Button> </Button>
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"
@ -135,7 +136,7 @@ export default function AuthenticationView() {
}} }}
> >
<HiTrash /> <HiTrash />
<div className="hidden md:block">Delete</div> <div className="hidden md:block"><Trans>ui.delete</Trans></div>
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -19,8 +19,10 @@ import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import axios from "axios"; import axios from "axios";
import { t } from "i18next";
import { useCallback, useContext, useEffect, useState } from "react"; import { useCallback, useContext, useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Trans } from "react-i18next";
import { LuExternalLink } from "react-icons/lu"; import { LuExternalLink } from "react-icons/lu";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
@ -196,14 +198,13 @@ export default function NotificationView({
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0"> <div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Notification Settings <Trans>ui.settingView.notification.notificationSettings</Trans>
</Heading> </Heading>
<div className="max-w-6xl"> <div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant"> <div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
<p> <p>
Frigate can natively send push notifications to your device when <Trans>ui.settingView.notification.desc</Trans>
it is running in the browser or installed as a PWA.
</p> </p>
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Link <Link
@ -212,7 +213,7 @@ export default function NotificationView({
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline" className="inline"
> >
Read the Documentation{" "} <Trans>ui.settingView.notification.documentation</Trans>{" "}
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
@ -232,7 +233,7 @@ export default function NotificationView({
<FormControl> <FormControl>
<div className="flex flex-row items-center justify-start gap-2"> <div className="flex flex-row items-center justify-start gap-2">
<Label className="cursor-pointer" htmlFor="auto-live"> <Label className="cursor-pointer" htmlFor="auto-live">
Notifications <Trans>ui.settingView.notification</Trans>
</Label> </Label>
<Switch <Switch
id="auto-live" id="auto-live"
@ -251,17 +252,16 @@ export default function NotificationView({
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel><Trans>ui.settingView.notification.email</Trans></FormLabel>
<FormControl> <FormControl>
<Input <Input
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72" className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72"
placeholder="example@email.com" placeholder={t("ui.settingView.notification.email.placeholder")}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
Entering a valid email is required, as this is used by the <Trans>ui.settingView.notification.email.desc</Trans>
push server in case problems occur.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -274,7 +274,7 @@ export default function NotificationView({
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
Cancel <Trans>ui.cancel</Trans>
</Button> </Button>
<Button <Button
variant="select" variant="select"
@ -286,10 +286,10 @@ export default function NotificationView({
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span>Saving...</span> <span><Trans>ui.saving</Trans></span>
</div> </div>
) : ( ) : (
"Save" <Trans>ui.save</Trans>
)} )}
</Button> </Button>
</div> </div>
@ -336,7 +336,7 @@ export default function NotificationView({
} }
}} }}
> >
{`${registration != null ? "Unregister" : "Register"} for notifications on this device`} {registration != null ? t("ui.settingView.notification.unregisterDevice") : t("ui.settingView.notification.registerDevice")}
</Button> </Button>
</div> </div>
</div> </div>