From 75d7049b6ded8b33f59db837cb1b0340f0c66dee Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:02:08 -0500 Subject: [PATCH] Fix I18n audio labels (#20508) * ensure i18n audio label keys are translated don't assume they are in the objects namespace * add missing audio labels * Improve handling of label types * simplify * fixes --------- Co-authored-by: Nicolas Mowen --- web/public/locales/en/audio.json | 76 ++++++++++++++++++- web/src/components/card/SearchThumbnail.tsx | 2 +- web/src/types/search.ts | 4 +- web/src/utils/i18n.ts | 22 +++++- web/src/views/explore/ExploreView.tsx | 19 +++-- web/src/views/settings/CameraSettingsView.tsx | 14 +++- 6 files changed, 123 insertions(+), 14 deletions(-) diff --git a/web/public/locales/en/audio.json b/web/public/locales/en/audio.json index de5f5638c..5c197e85b 100644 --- a/web/public/locales/en/audio.json +++ b/web/public/locales/en/audio.json @@ -425,5 +425,79 @@ "television": "Television", "radio": "Radio", "field_recording": "Field Recording", - "scream": "Scream" + "scream": "Scream", + "sodeling": "Sodeling", + "chird": "Chird", + "change_ringing": "Change Ringing", + "shofar": "Shofar", + "liquid": "Liquid", + "splash": "Splash", + "slosh": "Slosh", + "squish": "Squish", + "drip": "Drip", + "pour": "Pour", + "trickle": "Trickle", + "gush": "Gush", + "fill": "Fill", + "spray": "Spray", + "pump": "Pump", + "stir": "Stir", + "boiling": "Boiling", + "sonar": "Sonar", + "arrow": "Arrow", + "whoosh": "Whoosh", + "thump": "Thump", + "thunk": "Thunk", + "electronic_tuner": "Electronic Tuner", + "effects_unit": "Effects Unit", + "chorus_effect": "Chorus Effect", + "basketball_bounce": "Basketball Bounce", + "bang": "Bang", + "slap": "Slap", + "whack": "Whack", + "smash": "Smash", + "breaking": "Breaking", + "bouncing": "Bouncing", + "whip": "Whip", + "flap": "Flap", + "scratch": "Scratch", + "scrape": "Scrape", + "rub": "Rub", + "roll": "Roll", + "crushing": "Crushing", + "crumpling": "Crumpling", + "tearing": "Tearing", + "beep": "Beep", + "ping": "Ping", + "ding": "Ding", + "clang": "Clang", + "squeal": "Squeal", + "creak": "Creak", + "rustle": "Rustle", + "whir": "Whir", + "clatter": "Clatter", + "sizzle": "Sizzle", + "clicking": "Clicking", + "clickety_clack": "Clickety Clack", + "rumble": "Rumble", + "plop": "Plop", + "hum": "Hum", + "zing": "Zing", + "boing": "Boing", + "crunch": "Crunch", + "sine_wave": "Sine Wave", + "harmonic": "Harmonic", + "chirp_tone": "Chirp Tone", + "pulse": "Pulse", + "inside": "Inside", + "outside": "Outside", + "reverberation": "Reverberation", + "echo": "Echo", + "noise": "Noise", + "mains_hum": "Mains Hum", + "distortion": "Distortion", + "sidetone": "Sidetone", + "cacophony": "Cacophony", + "throbbing": "Throbbing", + "vibration": "Vibration" } diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 3876a7710..8ec922ae2 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -150,7 +150,7 @@ export default function SearchThumbnail({ .filter( (item) => item !== undefined && !item.includes("-verified"), ) - .map((text) => getTranslatedLabel(text)) + .map((text) => getTranslatedLabel(text, searchResult.data.type)) .sort() .join(", ") .replaceAll("-verified", "")} diff --git a/web/src/types/search.ts b/web/src/types/search.ts index 2a57385f7..8fb81dfc6 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -29,6 +29,8 @@ export type SearchSortType = | "score_desc" | "relevance"; +export type EventType = "object" | "audio" | "manual"; + export type SearchResult = { id: string; camera: string; @@ -54,7 +56,7 @@ export type SearchResult = { box: number[]; area: number; ratio: number; - type: "object" | "audio" | "manual"; + type: EventType; description?: string; average_estimated_speed: number; velocity_angle: number; diff --git a/web/src/utils/i18n.ts b/web/src/utils/i18n.ts index 8ee059333..1a386ccb3 100644 --- a/web/src/utils/i18n.ts +++ b/web/src/utils/i18n.ts @@ -1,11 +1,29 @@ import i18n, { t } from "i18next"; import { initReactI18next } from "react-i18next"; import HttpBackend from "i18next-http-backend"; +import { EventType } from "@/types/search"; -export const getTranslatedLabel = (label: string) => { +export const getTranslatedLabel = ( + label: string, + type: EventType = "object", +) => { if (!label) return ""; - return t(`${label.replace(/\s+/g, "_").toLowerCase()}`, { ns: "objects" }); + if (type === "manual") return label; + + const normalize = (s: string) => + s + .trim() + .replace(/[-'\s]+/g, "_") + .replace(/__+/g, "_") + .replace(/^_+|_+$/g, "") + .toLowerCase(); + + const key = normalize(label); + + const ns = type === "audio" ? "audio" : "objects"; + + return t(key, { ns }); }; i18n diff --git a/web/src/views/explore/ExploreView.tsx b/web/src/views/explore/ExploreView.tsx index 4472d4ac6..f78f59256 100644 --- a/web/src/views/explore/ExploreView.tsx +++ b/web/src/views/explore/ExploreView.tsx @@ -11,7 +11,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip"; -import { SearchResult } from "@/types/search"; +import { EventType, SearchResult } from "@/types/search"; import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator"; import useImageLoaded from "@/hooks/use-image-loaded"; import ActivityIndicator from "@/components/indicators/activity-indicator"; @@ -110,7 +110,8 @@ export default function ExploreView({ key={label} searchResults={filteredEvents} isValidating={isValidating} - objectType={label} + label={label} + labelType={filteredEvents[0]?.data?.type || "object"} setSearchDetail={setSearchDetail} mutate={mutate} setSimilaritySearch={setSimilaritySearch} @@ -122,7 +123,8 @@ export default function ExploreView({ } type ThumbnailRowType = { - objectType: string; + label: string; + labelType: EventType; searchResults?: SearchResult[]; isValidating: boolean; setSearchDetail: (search: SearchResult | undefined) => void; @@ -132,7 +134,8 @@ type ThumbnailRowType = { }; function ThumbnailRow({ - objectType, + label, + labelType, searchResults, isValidating, setSearchDetail, @@ -153,7 +156,7 @@ function ThumbnailRow({ return (
- {getTranslatedLabel(objectType)} + {getTranslatedLabel(label, labelType)} {searchResults && ( {t("trackedObjectsCount", { @@ -181,7 +184,7 @@ function ThumbnailRow({ ))}
handleSearch(objectType)} + onClick={() => handleSearch(label)} > @@ -192,7 +195,9 @@ function ThumbnailRow({ - {t("exploreMore", { label: getTranslatedLabel(objectType) })} + {t("exploreMore", { + label: getTranslatedLabel(label, labelType), + })} diff --git a/web/src/views/settings/CameraSettingsView.tsx b/web/src/views/settings/CameraSettingsView.tsx index 70bdf1308..f42ec84fe 100644 --- a/web/src/views/settings/CameraSettingsView.tsx +++ b/web/src/views/settings/CameraSettingsView.tsx @@ -100,7 +100,12 @@ export default function CameraSettingsView({ const alertsLabels = useMemo(() => { return cameraConfig?.review.alerts.labels ? cameraConfig.review.alerts.labels - .map((label) => getTranslatedLabel(label)) + .map((label) => + getTranslatedLabel( + label, + cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", + ), + ) .join(", ") : ""; }, [cameraConfig]); @@ -108,7 +113,12 @@ export default function CameraSettingsView({ const detectionsLabels = useMemo(() => { return cameraConfig?.review.detections.labels ? cameraConfig.review.detections.labels - .map((label) => getTranslatedLabel(label)) + .map((label) => + getTranslatedLabel( + label, + cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", + ), + ) .join(", ") : ""; }, [cameraConfig]);