mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-27 02:28:58 +03:00
feat: add i18n (translation/localization) (#16877)
* Translation module init * Add more i18n keys * fix: fix string wrong * refactor: use namespace translation file * chore: add more translation key * fix: fix some page name error * refactor: change Trans tag for t function * chore: fix some key not work * chore: fix SearchFilterDialog i18n key error Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * chore: fix en i18n file filter missing some keys * chore: add some i18n keys * chore: add more i18n keys again * feat: add search page i18n * feat: add explore model i18n keys * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/menu/GeneralSettings.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * feat: add more live i18n keys * feat: add more search setting i18n keys * fix: remove some comment * fix: fix some setting page url error * Update web/src/views/settings/SearchSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * fix: add system missing keys * fix: update password update i18n keys * chore: remove outdate translation.json file * fix: fix exploreSettings error * chore: add object setting i18n keys * Update web/src/views/recording/RecordingView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/public/locales/en/components/filter.json Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/components/overlay/ExportDialog.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * feat: add more i18n keys * fix: fix motionDetectionTuner html node * feat: add more page i18n keys * fix: cameraStream i18n keys error * feat: add Player i18n keys * feat: add more toast i18n keys * feat: change explore setting name * feat: add more document title i18n keys * feat: add more search i18n keys * fix: fix accessDenied i18n keys error * chore: add objectType i18n * chore: add inputWithTags i18n * chore: add SearchFilterDialog i18n * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update web/src/views/settings/ObjectSettingsView.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * chore: add some missing i18n keys * chore: remove most import { t } from "i18next"; --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
co-authored by
Josh Hawkins
parent
db541abed4
commit
d34533981f
@@ -44,6 +44,7 @@ import {
|
||||
useNotifications,
|
||||
useNotificationSuspend,
|
||||
} from "@/api/ws";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type LiveContextMenuProps = {
|
||||
className?: string;
|
||||
@@ -85,6 +86,7 @@ export default function LiveContextMenu({
|
||||
config,
|
||||
children,
|
||||
}: LiveContextMenuProps) {
|
||||
const { t } = useTranslation("views/live");
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
// camera enabled
|
||||
@@ -209,7 +211,7 @@ export default function LiveContextMenu({
|
||||
// notifications
|
||||
|
||||
const notificationsEnabledInConfig =
|
||||
config?.cameras[camera].notifications.enabled_in_config;
|
||||
config?.cameras[camera]?.notifications?.enabled_in_config;
|
||||
|
||||
const { payload: notificationState, send: sendNotification } =
|
||||
useNotifications(camera);
|
||||
@@ -234,14 +236,19 @@ export default function LiveContextMenu({
|
||||
};
|
||||
|
||||
const formatSuspendedUntil = (timestamp: string) => {
|
||||
if (timestamp === "0") return "Frigate restarts.";
|
||||
// Some languages require a change in word order
|
||||
if (timestamp === "0") return t("time.untilForRestart", { ns: "common" });
|
||||
|
||||
return formatUnixTimestampToDateTime(Number.parseInt(timestamp), {
|
||||
const time = formatUnixTimestampToDateTime(Number.parseInt(timestamp), {
|
||||
time_style: "medium",
|
||||
date_style: "medium",
|
||||
timezone: config?.ui.timezone,
|
||||
strftime_fmt: `%b %d, ${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`,
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampExcludeSeconds", { ns: "common" }),
|
||||
});
|
||||
return t("time.untilForTime", { ns: "common", time });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -256,7 +263,7 @@ export default function LiveContextMenu({
|
||||
{preferredLiveMode == "jsmpeg" && isRestreamed && (
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<IoIosWarning className="mr-1 size-4 text-danger" />
|
||||
<p className="mr-2 text-xs">Low-bandwidth mode</p>
|
||||
<p className="mr-2 text-xs">{t("lowBandwidthMode")}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -265,7 +272,7 @@ export default function LiveContextMenu({
|
||||
<ContextMenuSeparator className="mb-1" />
|
||||
<div className="p-2 text-sm">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<p>Audio</p>
|
||||
<p>{t("audio")}</p>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<VolumeIcon
|
||||
className="size-5"
|
||||
@@ -292,7 +299,7 @@ export default function LiveContextMenu({
|
||||
onClick={() => sendEnabled(isEnabled ? "OFF" : "ON")}
|
||||
>
|
||||
<div className="text-primary">
|
||||
{isEnabled ? "Disable" : "Enable"} Camera
|
||||
{isEnabled ? t("camera.disable") : t("camera.enable")}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
@@ -302,7 +309,7 @@ export default function LiveContextMenu({
|
||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||
onClick={isEnabled ? muteAll : undefined}
|
||||
>
|
||||
<div className="text-primary">Mute All Cameras</div>
|
||||
<div className="text-primary">{t("muteCameras.enable")}</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem disabled={!isEnabled}>
|
||||
@@ -310,7 +317,7 @@ export default function LiveContextMenu({
|
||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||
onClick={isEnabled ? unmuteAll : undefined}
|
||||
>
|
||||
<div className="text-primary">Unmute All Cameras</div>
|
||||
<div className="text-primary">{t("muteCameras.disable")}</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
@@ -320,7 +327,9 @@ export default function LiveContextMenu({
|
||||
onClick={isEnabled ? toggleStats : undefined}
|
||||
>
|
||||
<div className="text-primary">
|
||||
{statsState ? "Hide" : "Show"} Stream Stats
|
||||
{statsState
|
||||
? t("streamStats.disable")
|
||||
: t("streamStats.enable")}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
@@ -333,7 +342,9 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className="text-primary">Debug View</div>
|
||||
<div className="text-primary">
|
||||
{t("streaming.debugView", { ns: "components/dialog" })}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
{cameraGroup && cameraGroup !== "default" && (
|
||||
@@ -344,7 +355,7 @@ export default function LiveContextMenu({
|
||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||
onClick={isEnabled ? () => setShowSettings(true) : undefined}
|
||||
>
|
||||
<div className="text-primary">Streaming Settings</div>
|
||||
<div className="text-primary">{t("streamingSettings")}</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
@@ -357,7 +368,9 @@ export default function LiveContextMenu({
|
||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||
onClick={isEnabled ? resetPreferredLiveMode : undefined}
|
||||
>
|
||||
<div className="text-primary">Reset</div>
|
||||
<div className="text-primary">
|
||||
{t("button.reset", { ns: "common" })}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
@@ -368,7 +381,7 @@ export default function LiveContextMenu({
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger disabled={!isEnabled}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Notifications</span>
|
||||
<span>{t("notifications")}</span>
|
||||
</div>
|
||||
</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent>
|
||||
@@ -379,25 +392,29 @@ export default function LiveContextMenu({
|
||||
{isSuspended ? (
|
||||
<>
|
||||
<IoIosNotificationsOff className="size-5 text-muted-foreground" />
|
||||
<span>Suspended</span>
|
||||
<span>
|
||||
{t("button.suspended", { ns: "common" })}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IoIosNotifications className="size-5 text-muted-foreground" />
|
||||
<span>Enabled</span>
|
||||
<span>
|
||||
{t("button.enabled", { ns: "common" })}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IoIosNotificationsOff className="size-5 text-danger" />
|
||||
<span>Disabled</span>
|
||||
<span>{t("button.disabled", { ns: "common" })}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isSuspended && (
|
||||
<span className="text-xs text-primary-variant">
|
||||
Until {formatSuspendedUntil(notificationSuspendUntil)}
|
||||
{formatSuspendedUntil(notificationSuspendUntil)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -418,9 +435,11 @@ export default function LiveContextMenu({
|
||||
>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{notificationState === "ON" ? (
|
||||
<span>Unsuspend</span>
|
||||
<span>
|
||||
{t("button.unsuspended", { ns: "common" })}
|
||||
</span>
|
||||
) : (
|
||||
<span>Enable</span>
|
||||
<span>{t("button.enable", { ns: "common" })}</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
@@ -431,7 +450,7 @@ export default function LiveContextMenu({
|
||||
<ContextMenuSeparator />
|
||||
<div className="px-2 py-1.5">
|
||||
<p className="mb-2 text-sm font-medium text-muted-foreground">
|
||||
Suspend for:
|
||||
{t("suspend.forTime")}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<ContextMenuItem
|
||||
@@ -440,7 +459,7 @@ export default function LiveContextMenu({
|
||||
isEnabled ? () => handleSuspend("5") : undefined
|
||||
}
|
||||
>
|
||||
5 minutes
|
||||
{t("time.5minutes", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -450,7 +469,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
10 minutes
|
||||
{t("time.10minutes", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -460,7 +479,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
30 minutes
|
||||
{t("time.30minutes", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -470,7 +489,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
1 hour
|
||||
{t("time.1hour", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -480,7 +499,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
12 hours
|
||||
{t("time.12hours", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -490,7 +509,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
24 hours
|
||||
{t("time.24hours", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={!isEnabled}
|
||||
@@ -500,7 +519,7 @@ export default function LiveContextMenu({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Until restart
|
||||
{t("time.untilRestart", { ns: "common" })}
|
||||
</ContextMenuItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user