diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json
index aa841c30b5..2ae6297a12 100644
--- a/web/public/locales/en/common.json
+++ b/web/public/locales/en/common.json
@@ -101,7 +101,8 @@
"show": "Show {{item}}",
"ID": "ID",
"none": "None",
- "all": "All"
+ "all": "All",
+ "other": "Other"
},
"list": {
"two": "{{0}} and {{1}}",
diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json
index ada23cdc26..da774e3022 100644
--- a/web/public/locales/en/views/system.json
+++ b/web/public/locales/en/views/system.json
@@ -86,7 +86,14 @@
"otherProcesses": {
"title": "Other Processes",
"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": {
diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx
index a8ed12f241..ffd28a0d06 100644
--- a/web/src/components/card/ClassificationCard.tsx
+++ b/web/src/components/card/ClassificationCard.tsx
@@ -166,7 +166,7 @@ export const ClassificationCard = forwardRef<
{data.name == "unknown"
? t("details.unknown")
- : data.name == "none"
+ : data.name.toLowerCase() == "none"
? t("details.none")
: data.name}
diff --git a/web/src/components/overlay/SetPasswordDialog.tsx b/web/src/components/overlay/SetPasswordDialog.tsx
index c6d861a6ba..7708201aa7 100644
--- a/web/src/components/overlay/SetPasswordDialog.tsx
+++ b/web/src/components/overlay/SetPasswordDialog.tsx
@@ -22,6 +22,7 @@ import { useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import useSWR from "swr";
import { formatSecondsToDuration } from "@/utils/dateUtil";
+import { useDateLocale } from "@/hooks/use-date-locale";
import ActivityIndicator from "../indicators/activity-indicator";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
@@ -48,12 +49,13 @@ export default function SetPasswordDialog({
const { t } = useTranslation(["views/settings", "common"]);
const { getLocaleDocUrl } = useDocDomain();
const isAdmin = useIsAdmin();
+ const dateLocale = useDateLocale();
const { data: config } = useSWR("config");
const refreshSeconds: number | undefined =
config?.auth?.refresh_time ?? undefined;
const refreshTimeLabel = refreshSeconds
- ? formatSecondsToDuration(refreshSeconds)
+ ? formatSecondsToDuration(refreshSeconds, dateLocale)
: t("time.30minutes", { ns: "common" });
// visibility toggles for password fields
diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx
index 80471b8bdf..f2eb111437 100644
--- a/web/src/components/overlay/detail/TrackingDetails.tsx
+++ b/web/src/components/overlay/detail/TrackingDetails.tsx
@@ -266,7 +266,7 @@ export function TrackingDetails({
const label = event.sub_label
? event.sub_label
- : getTranslatedLabel(event.label);
+ : getTranslatedLabel(event.label, event.data.type);
const getZoneColor = useCallback(
(zoneName: string) => {
@@ -998,7 +998,7 @@ function LifecycleIconRow({
{formattedEventTimestamp}
- {((isAdmin && config?.plus?.enabled) || item.data.box) && (
+ {isAdmin && config?.plus?.enabled && item.data.box && (
diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx
index 9500688f57..ed359a0c92 100644
--- a/web/src/components/player/LivePlayer.tsx
+++ b/web/src/components/player/LivePlayer.tsx
@@ -16,7 +16,6 @@ import {
} from "@/types/live";
import { getIconForLabel } from "@/utils/iconUtil";
import Chip from "../indicators/Chip";
-import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { cn } from "@/lib/utils";
import { TbExclamationCircle } from "react-icons/tb";
import { TooltipPortal } from "@radix-ui/react-tooltip";
@@ -26,6 +25,8 @@ import { LuVideoOff } from "react-icons/lu";
import { Trans, useTranslation } from "react-i18next";
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
+import { getTranslatedLabel } from "@/utils/i18n";
+import { formatList } from "@/utils/stringUtil";
type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void;
@@ -367,20 +368,22 @@ export default function LivePlayer({
- {[
- ...new Set([
- ...(objects || []).map(({ label, sub_label }) =>
- label.endsWith("verified")
- ? sub_label
- : label.replaceAll("_", " "),
- ),
- ]),
- ]
- .filter((label) => label?.includes("-verified") == false)
- .map((label) => capitalizeFirstLetter(label))
- .sort()
- .join(", ")
- .replaceAll("-verified", "")}
+ {formatList(
+ [
+ ...new Set([
+ ...(objects || []).map(({ label, sub_label }) =>
+ label.endsWith("verified")
+ ? sub_label
+ : label.replaceAll("_", " "),
+ ),
+ ]),
+ ]
+ .filter((label) => label?.includes("-verified") == false)
+ .map((label) =>
+ getTranslatedLabel(label.replace("-verified", "")),
+ )
+ .sort(),
+ )}
diff --git a/web/src/components/settings/wizard/Step1NameCamera.tsx b/web/src/components/settings/wizard/Step1NameCamera.tsx
index eb0dbe9fe8..741aa4b052 100644
--- a/web/src/components/settings/wizard/Step1NameCamera.tsx
+++ b/web/src/components/settings/wizard/Step1NameCamera.tsx
@@ -417,7 +417,9 @@ export default function Step1NameCamera({
{CAMERA_BRANDS.map((brand) => (
- {brand.label}
+ {brand.label.toLowerCase() === "other"
+ ? t("label.other", { ns: "common" })
+ : brand.label}
))}
diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts
index db6e0b1cb7..b007a9573f 100644
--- a/web/src/utils/dateUtil.ts
+++ b/web/src/utils/dateUtil.ts
@@ -1,5 +1,5 @@
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 i18n from "@/utils/i18n";
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 locale - the date-fns locale to use for formatting
* @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) {
return "Invalid duration";
}
@@ -304,6 +308,7 @@ export const formatSecondsToDuration = (seconds: number): string => {
return formatDuration(duration, {
format: ["hours", "minutes", "seconds"],
delimiter: ", ",
+ locale: locale ?? enUS,
});
};
diff --git a/web/src/utils/lifecycleUtil.ts b/web/src/utils/lifecycleUtil.ts
index 7ed90c5f8f..4e43de9c2f 100644
--- a/web/src/utils/lifecycleUtil.ts
+++ b/web/src/utils/lifecycleUtil.ts
@@ -12,7 +12,10 @@ export function getLifecycleItemDescription(
const label = lifecycleItem.data.sub_label
? capitalizeFirstLetter(rawLabel)
- : getTranslatedLabel(rawLabel);
+ : getTranslatedLabel(
+ rawLabel,
+ lifecycleItem.class_type === "heard" ? "audio" : "object",
+ );
switch (lifecycleItem.class_type) {
case "visible":
diff --git a/web/src/views/system/GeneralMetrics.tsx b/web/src/views/system/GeneralMetrics.tsx
index a05b1b82ac..f8ce648515 100644
--- a/web/src/views/system/GeneralMetrics.tsx
+++ b/web/src/views/system/GeneralMetrics.tsx
@@ -855,7 +855,7 @@ export default function GeneralMetrics({