From 05a607b538242d59c9450edba9ded1fcf749034f Mon Sep 17 00:00:00 2001 From: ZhaiSoul <842607283@qq.com> Date: Thu, 6 Nov 2025 17:20:04 +0000 Subject: [PATCH] refactor: add formatList --- web/src/utils/lifecycleUtil.ts | 22 ++---------- web/src/utils/stringUtil.ts | 28 +++++++++++++++ web/src/views/settings/CameraSettingsView.tsx | 35 +++++++++++-------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/web/src/utils/lifecycleUtil.ts b/web/src/utils/lifecycleUtil.ts index fa9ce9281..7ed90c5f8 100644 --- a/web/src/utils/lifecycleUtil.ts +++ b/web/src/utils/lifecycleUtil.ts @@ -1,25 +1,7 @@ import { TrackingDetailsSequence } from "@/types/timeline"; import { t } from "i18next"; import { getTranslatedLabel } from "./i18n"; -import { capitalizeFirstLetter } from "./stringUtil"; - -function formatZonesList(zones: string[]): string { - if (zones.length === 0) return ""; - if (zones.length === 1) return zones[0]; - if (zones.length === 2) { - return t("list.two", { - 0: zones[0], - 1: zones[1], - }); - } - - const separatorWithSpace = t("list.separatorWithSpace", { ns: "common" }); - const allButLast = zones.slice(0, -1).join(separatorWithSpace); - return t("list.many", { - items: allButLast, - last: zones[zones.length - 1], - }); -} +import { capitalizeFirstLetter, formatList } from "./stringUtil"; export function getLifecycleItemDescription( lifecycleItem: TrackingDetailsSequence, @@ -42,7 +24,7 @@ export function getLifecycleItemDescription( return t("trackingDetails.lifecycleItemDesc.entered_zone", { ns: "views/explore", label, - zones: formatZonesList( + zones: formatList( lifecycleItem.data.zones_friendly_names ?? lifecycleItem.data.zones, ), }); diff --git a/web/src/utils/stringUtil.ts b/web/src/utils/stringUtil.ts index 0fd34b45b..568ca85d2 100644 --- a/web/src/utils/stringUtil.ts +++ b/web/src/utils/stringUtil.ts @@ -1,3 +1,5 @@ +import { t } from "i18next"; + export const capitalizeFirstLetter = (text: string): string => { return text.charAt(0).toUpperCase() + text.slice(1); }; @@ -45,3 +47,29 @@ export function generateFixedHash(name: string, prefix: string = "id"): string { export function isValidId(name: string): boolean { return /^[a-zA-Z0-9_-]+$/.test(name) && !/^\d+$/.test(name); } + +/** + * Formats a list of strings into a human-readable format with proper localization. + * Handles different cases for empty, single-item, two-item, and multi-item lists. + * + * @param item - The array of strings to format + * @returns A formatted string representation of the list + */ +export function formatList(item: string[]): string { + if (item.length === 0) return ""; + if (item.length === 1) return item[0]; + if (item.length === 2) { + return t("list.two", { + 0: item[0], + 1: item[1], + ns: "common", + }); + } + + const separatorWithSpace = t("list.separatorWithSpace", { ns: "common" }); + const allButLast = item.slice(0, -1).join(separatorWithSpace); + return t("list.many", { + items: allButLast, + last: item[item.length - 1], + }); +} diff --git a/web/src/views/settings/CameraSettingsView.tsx b/web/src/views/settings/CameraSettingsView.tsx index 7e27aaebb..dd384b621 100644 --- a/web/src/views/settings/CameraSettingsView.tsx +++ b/web/src/views/settings/CameraSettingsView.tsx @@ -42,6 +42,7 @@ import { IoMdArrowRoundBack } from "react-icons/io"; import { isDesktop } from "react-device-detect"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { resolveZoneName } from "@/hooks/use-zone-friendly-name"; +import { formatList } from "@/utils/stringUtil"; type CameraSettingsViewProps = { selectedCamera: string; @@ -106,27 +107,27 @@ export default function CameraSettingsView({ const alertsLabels = useMemo(() => { return cameraConfig?.review.alerts.labels - ? cameraConfig.review.alerts.labels - .map((label) => + ? formatList( + cameraConfig.review.alerts.labels.map((label) => getTranslatedLabel( label, cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", ), - ) - .join(", ") + ), + ) : ""; }, [cameraConfig]); const detectionsLabels = useMemo(() => { return cameraConfig?.review.detections.labels - ? cameraConfig.review.detections.labels - .map((label) => + ? formatList( + cameraConfig.review.detections.labels.map((label) => getTranslatedLabel( label, cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", ), - ) - .join(", ") + ), + ) : ""; }, [cameraConfig]); @@ -555,8 +556,10 @@ export default function CameraSettingsView({ "cameraReview.reviewClassification.zoneObjectAlertsTips", { alertsLabels, - zone: watchedAlertsZones.map((zone) => - getZoneName(zone), + zone: formatList( + watchedAlertsZones.map((zone) => + getZoneName(zone), + ), ), cameraName: selectCameraName, }, @@ -669,8 +672,10 @@ export default function CameraSettingsView({ i18nKey="cameraReview.reviewClassification.zoneObjectDetectionsTips.text" values={{ detectionsLabels, - zone: watchedDetectionsZones.map((zone) => - getZoneName(zone), + zone: formatList( + watchedDetectionsZones.map((zone) => + getZoneName(zone), + ), ), cameraName: selectCameraName, }} @@ -681,8 +686,10 @@ export default function CameraSettingsView({ i18nKey="cameraReview.reviewClassification.zoneObjectDetectionsTips.notSelectDetections" values={{ detectionsLabels, - zone: watchedDetectionsZones.map((zone) => - getZoneName(zone), + zone: formatList( + watchedDetectionsZones.map((zone) => + getZoneName(zone), + ), ), cameraName: selectCameraName, }}