I18N Miscellaneous Fixes (#21573)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions

* fix: fix classification none tag i18n wrong

* fix: fix set password dialog jwt time i18n wrong

* fix: fix wizard other camera i18n

* fix: fix explore tracking detail audio i18n

* feat: add system processes info i18n

* fix: fix live page label i18n
This commit is contained in:
GuoQing Liu 2026-01-09 05:28:18 +08:00 committed by GitHub
parent 74d14cb8ca
commit f3543cfee2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 49 additions and 26 deletions

View File

@ -101,7 +101,8 @@
"show": "Show {{item}}", "show": "Show {{item}}",
"ID": "ID", "ID": "ID",
"none": "None", "none": "None",
"all": "All" "all": "All",
"other": "Other"
}, },
"list": { "list": {
"two": "{{0}} and {{1}}", "two": "{{0}} and {{1}}",

View File

@ -86,7 +86,14 @@
"otherProcesses": { "otherProcesses": {
"title": "Other Processes", "title": "Other Processes",
"processCpuUsage": "Process CPU Usage", "processCpuUsage": "Process CPU Usage",
"processMemoryUsage": "Process Memory Usage" "processMemoryUsage": "Process Memory Usage",
"series": {
"go2rtc": "go2rtc",
"recording": "recording",
"review_segment": "review segment",
"embeddings": "embeddings",
"audio_detector": "audio detector"
}
} }
}, },
"storage": { "storage": {

View File

@ -166,7 +166,7 @@ export const ClassificationCard = forwardRef<
<div className="break-all smart-capitalize"> <div className="break-all smart-capitalize">
{data.name == "unknown" {data.name == "unknown"
? t("details.unknown") ? t("details.unknown")
: data.name == "none" : data.name.toLowerCase() == "none"
? t("details.none") ? t("details.none")
: data.name} : data.name}
</div> </div>

View File

@ -22,6 +22,7 @@ import { useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
import useSWR from "swr"; import useSWR from "swr";
import { formatSecondsToDuration } from "@/utils/dateUtil"; import { formatSecondsToDuration } from "@/utils/dateUtil";
import { useDateLocale } from "@/hooks/use-date-locale";
import ActivityIndicator from "../indicators/activity-indicator"; import ActivityIndicator from "../indicators/activity-indicator";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -48,12 +49,13 @@ export default function SetPasswordDialog({
const { t } = useTranslation(["views/settings", "common"]); const { t } = useTranslation(["views/settings", "common"]);
const { getLocaleDocUrl } = useDocDomain(); const { getLocaleDocUrl } = useDocDomain();
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
const dateLocale = useDateLocale();
const { data: config } = useSWR("config"); const { data: config } = useSWR("config");
const refreshSeconds: number | undefined = const refreshSeconds: number | undefined =
config?.auth?.refresh_time ?? undefined; config?.auth?.refresh_time ?? undefined;
const refreshTimeLabel = refreshSeconds const refreshTimeLabel = refreshSeconds
? formatSecondsToDuration(refreshSeconds) ? formatSecondsToDuration(refreshSeconds, dateLocale)
: t("time.30minutes", { ns: "common" }); : t("time.30minutes", { ns: "common" });
// visibility toggles for password fields // visibility toggles for password fields

View File

@ -266,7 +266,7 @@ export function TrackingDetails({
const label = event.sub_label const label = event.sub_label
? event.sub_label ? event.sub_label
: getTranslatedLabel(event.label); : getTranslatedLabel(event.label, event.data.type);
const getZoneColor = useCallback( const getZoneColor = useCallback(
(zoneName: string) => { (zoneName: string) => {
@ -998,7 +998,7 @@ function LifecycleIconRow({
<div className="ml-3 flex-shrink-0 px-1 text-right text-xs text-primary-variant"> <div className="ml-3 flex-shrink-0 px-1 text-right text-xs text-primary-variant">
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="whitespace-nowrap">{formattedEventTimestamp}</div> <div className="whitespace-nowrap">{formattedEventTimestamp}</div>
{((isAdmin && config?.plus?.enabled) || item.data.box) && ( {isAdmin && config?.plus?.enabled && item.data.box && (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}> <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<div className="rounded p-1 pr-2" role="button"> <div className="rounded p-1 pr-2" role="button">

View File

@ -16,7 +16,6 @@ import {
} from "@/types/live"; } from "@/types/live";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import Chip from "../indicators/Chip"; import Chip from "../indicators/Chip";
import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { TbExclamationCircle } from "react-icons/tb"; import { TbExclamationCircle } from "react-icons/tb";
import { TooltipPortal } from "@radix-ui/react-tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip";
@ -26,6 +25,8 @@ import { LuVideoOff } from "react-icons/lu";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
import { getTranslatedLabel } from "@/utils/i18n";
import { formatList } from "@/utils/stringUtil";
type LivePlayerProps = { type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void; cameraRef?: (ref: HTMLDivElement | null) => void;
@ -367,20 +368,22 @@ export default function LivePlayer({
</div> </div>
<TooltipPortal> <TooltipPortal>
<TooltipContent className="smart-capitalize"> <TooltipContent className="smart-capitalize">
{[ {formatList(
...new Set([ [
...(objects || []).map(({ label, sub_label }) => ...new Set([
label.endsWith("verified") ...(objects || []).map(({ label, sub_label }) =>
? sub_label label.endsWith("verified")
: label.replaceAll("_", " "), ? sub_label
), : label.replaceAll("_", " "),
]), ),
] ]),
.filter((label) => label?.includes("-verified") == false) ]
.map((label) => capitalizeFirstLetter(label)) .filter((label) => label?.includes("-verified") == false)
.sort() .map((label) =>
.join(", ") getTranslatedLabel(label.replace("-verified", "")),
.replaceAll("-verified", "")} )
.sort(),
)}
</TooltipContent> </TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>

View File

@ -417,7 +417,9 @@ export default function Step1NameCamera({
<SelectContent> <SelectContent>
{CAMERA_BRANDS.map((brand) => ( {CAMERA_BRANDS.map((brand) => (
<SelectItem key={brand.value} value={brand.value}> <SelectItem key={brand.value} value={brand.value}>
{brand.label} {brand.label.toLowerCase() === "other"
? t("label.other", { ns: "common" })
: brand.label}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>

View File

@ -1,5 +1,5 @@
import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns"; import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns";
import { Locale } from "date-fns/locale"; import { enUS, Locale } from "date-fns/locale";
import { formatInTimeZone } from "date-fns-tz"; import { formatInTimeZone } from "date-fns-tz";
import i18n from "@/utils/i18n"; import i18n from "@/utils/i18n";
export const longToDate = (long: number): Date => new Date(long * 1000); export const longToDate = (long: number): Date => new Date(long * 1000);
@ -293,9 +293,13 @@ export const getDurationFromTimestamps = (
/** /**
* *
* @param seconds - number of seconds to convert into hours, minutes and seconds * @param seconds - number of seconds to convert into hours, minutes and seconds
* @param locale - the date-fns locale to use for formatting
* @returns string - formatted duration in hours, minutes and seconds * @returns string - formatted duration in hours, minutes and seconds
*/ */
export const formatSecondsToDuration = (seconds: number): string => { export const formatSecondsToDuration = (
seconds: number,
locale?: Locale,
): string => {
if (isNaN(seconds) || seconds < 0) { if (isNaN(seconds) || seconds < 0) {
return "Invalid duration"; return "Invalid duration";
} }
@ -304,6 +308,7 @@ export const formatSecondsToDuration = (seconds: number): string => {
return formatDuration(duration, { return formatDuration(duration, {
format: ["hours", "minutes", "seconds"], format: ["hours", "minutes", "seconds"],
delimiter: ", ", delimiter: ", ",
locale: locale ?? enUS,
}); });
}; };

View File

@ -12,7 +12,10 @@ export function getLifecycleItemDescription(
const label = lifecycleItem.data.sub_label const label = lifecycleItem.data.sub_label
? capitalizeFirstLetter(rawLabel) ? capitalizeFirstLetter(rawLabel)
: getTranslatedLabel(rawLabel); : getTranslatedLabel(
rawLabel,
lifecycleItem.class_type === "heard" ? "audio" : "object",
);
switch (lifecycleItem.class_type) { switch (lifecycleItem.class_type) {
case "visible": case "visible":

View File

@ -855,7 +855,7 @@ export default function GeneralMetrics({
<ThresholdBarGraph <ThresholdBarGraph
key={series.name} key={series.name}
graphId={`${series.name}-cpu`} graphId={`${series.name}-cpu`}
name={series.name.replaceAll("_", " ")} name={t(`general.otherProcesses.series.${series.name}`)}
unit="%" unit="%"
threshold={DetectorCpuThreshold} threshold={DetectorCpuThreshold}
updateTimes={updateTimes} updateTimes={updateTimes}