diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 87f32a251..aa841c30b 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -72,7 +72,10 @@ "formattedTimestampFilename": { "12hour": "MM-dd-yy-h-mm-ss-a", "24hour": "MM-dd-yy-HH-mm-ss" - } + }, + "inProgress": "In progress", + "invalidStartTime": "Invalid start time", + "invalidEndTime": "Invalid end time" }, "unit": { "speed": { @@ -144,7 +147,8 @@ "unselect": "Unselect", "export": "Export", "deleteNow": "Delete Now", - "next": "Next" + "next": "Next", + "continue": "Continue" }, "menu": { "system": "System", @@ -237,6 +241,7 @@ "export": "Export", "uiPlayground": "UI Playground", "faceLibrary": "Face Library", + "classification": "Classification", "user": { "title": "User", "account": "Account", diff --git a/web/public/locales/en/views/events.json b/web/public/locales/en/views/events.json index 6e242c1e3..d3cf78658 100644 --- a/web/public/locales/en/views/events.json +++ b/web/public/locales/en/views/events.json @@ -24,8 +24,8 @@ "label": "Detail", "noDataFound": "No detail data to review", "aria": "Toggle detail view", - "trackedObject_one": "object", - "trackedObject_other": "objects", + "trackedObject_one": "{{count}} object", + "trackedObject_other": "{{count}} objects", "noObjectDetailData": "No object detail data available.", "settings": "Detail View Settings", "alwaysExpandActive": { diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index afc81eaa6..3d6985cfc 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -35,7 +35,7 @@ "snapshot": "snapshot", "thumbnail": "thumbnail", "video": "video", - "object_lifecycle": "object lifecycle" + "tracking_details": "tracking details" }, "trackingDetails": { "title": "Tracking Details", diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index d6df53987..76274ec3f 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -454,6 +454,24 @@ export function GeneralFilterContent({ onClose, }: GeneralFilterContentProps) { const { t } = useTranslation(["components/filter", "views/events"]); + const { data: config } = useSWR("config", { + revalidateOnFocus: false, + }); + const allAudioListenLabels = useMemo(() => { + if (!config) { + return []; + } + + const labels = new Set(); + Object.values(config.cameras).forEach((camera) => { + if (camera?.audio?.enabled) { + camera.audio.listen.forEach((label) => { + labels.add(label); + }); + } + }); + return [...labels].sort(); + }, [config]); return ( <>
@@ -504,7 +522,10 @@ export function GeneralFilterContent({ {allLabels.map((item) => ( { if (isChecked) { diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 199209c7c..58098743b 100755 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -81,6 +81,43 @@ export default function InputWithTags({ revalidateOnFocus: false, }); + const allAudioListenLabels = useMemo>(() => { + if (!config) { + return new Set(); + } + + const labels = new Set(); + Object.values(config.cameras).forEach((camera) => { + if (camera?.audio?.enabled) { + camera.audio.listen.forEach((label) => { + labels.add(label); + }); + } + }); + return labels; + }, [config]); + + const translatedAudioLabelMap = useMemo>(() => { + const map = new Map(); + if (!config) return map; + + allAudioListenLabels.forEach((label) => { + // getTranslatedLabel likely depends on i18n internally; including `lang` + // in deps ensures this map is rebuilt when language changes + map.set(label, getTranslatedLabel(label, "audio")); + }); + return map; + }, [allAudioListenLabels, config]); + + function resolveLabel(value: string) { + const mapped = translatedAudioLabelMap.get(value); + if (mapped) return mapped; + return getTranslatedLabel( + value, + allAudioListenLabels.has(value) ? "audio" : "object", + ); + } + const [inputValue, setInputValue] = useState(search || ""); const [currentFilterType, setCurrentFilterType] = useState( null, @@ -421,7 +458,8 @@ export default function InputWithTags({ ? t("button.yes", { ns: "common" }) : t("button.no", { ns: "common" }); } else if (filterType === "labels") { - return getTranslatedLabel(String(filterValues)); + const value = String(filterValues); + return resolveLabel(value); } else if (filterType === "search_type") { return t("filter.searchType." + String(filterValues)); } else { @@ -828,7 +866,7 @@ export default function InputWithTags({ > {t("filter.label." + filterType)}:{" "} {filterType === "labels" ? ( - getTranslatedLabel(value) + resolveLabel(value) ) : filterType === "cameras" ? ( ) : filterType === "zones" ? ( diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 7ff49d4d5..ae60ed1c6 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -1155,7 +1155,7 @@ function ObjectDetailsTab({
{getIconForLabel(search.label, "size-4 text-primary")} - {getTranslatedLabel(search.label)} + {getTranslatedLabel(search.label, search.data.type)} {search.sub_label && ` (${search.sub_label})`} {isAdmin && search.end_time && ( @@ -1394,7 +1394,9 @@ function ObjectDetailsTab({ {state == "submitted" && (
- {t("explore.plus.review.state.submitted")} + {t("explore.plus.review.state.submitted", { + ns: "components/dialog", + })}
)}
diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index 8e80506bd..d133aa05a 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -349,7 +349,7 @@ function ReviewGroup({ ? fetchedEvents.length : (review.data.objects ?? []).length; - return `${objectCount} ${t("detail.trackedObject", { count: objectCount })}`; + return `${t("detail.trackedObject", { count: objectCount })}`; }, [review, t, fetchedEvents]); const reviewDuration = useMemo( @@ -478,7 +478,7 @@ function ReviewGroup({
{getIconForLabel(audioLabel, "size-3 text-white")}
- {getTranslatedLabel(audioLabel)} + {getTranslatedLabel(audioLabel, "audio")} ))} @@ -513,7 +513,8 @@ function EventList({ const isSelected = selectedObjectIds.includes(event.id); - const label = event.sub_label || getTranslatedLabel(event.label); + const label = + event.sub_label || getTranslatedLabel(event.label, event.data.type); const handleObjectSelect = (event: Event | undefined) => { if (event) { diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index 4427b59ac..db6e0b1cb 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -244,12 +244,12 @@ export const getDurationFromTimestamps = ( abbreviated: boolean = false, ): string => { if (isNaN(start_time)) { - return "Invalid start time"; + return i18n.t("time.invalidStartTime", { ns: "common" }); } - let duration = "In Progress"; + let duration = i18n.t("time.inProgress", { ns: "common" }); if (end_time !== null) { if (isNaN(end_time)) { - return "Invalid end time"; + return i18n.t("time.invalidEndTime", { ns: "common" }); } const start = fromUnixTime(start_time); const end = fromUnixTime(end_time); diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index a934e1cb9..5b4d5328c 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -649,7 +649,7 @@ export function RecordingView({ value="detail" aria-label="Detail Stream" > -
Detail
+
{t("detail.label")}
) : (