From 6c8dbc182f9e2fff2fa78c4c28d3e8caca9c3940 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 2 Aug 2026 16:42:08 +0200 Subject: [PATCH] fix: align widget accessibility and locales --- .../widgets/src/anchor-note/component.tsx | 5 +++-- .../src/dns-hole/summary/component.tsx | 19 ++++--------------- .../src/immich/album-carousel/component.tsx | 5 +++-- .../widgets/src/immich/albums-query.spec.ts | 1 + .../src/immich/server-stats/component.tsx | 11 ++++++----- .../widgets/src/media-releases/component.tsx | 7 ++++--- .../execute-automation/component.tsx | 5 +++-- packages/widgets/src/stocks/component.tsx | 10 ++++++---- .../src/tracearr/recent-activity-section.tsx | 5 +++-- .../src/tracearr/violations-section.tsx | 5 +++-- packages/widgets/src/umami/umami-content.tsx | 19 ++++++++++--------- 11 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/widgets/src/anchor-note/component.tsx b/packages/widgets/src/anchor-note/component.tsx index 24e4de866..945a2de55 100644 --- a/packages/widgets/src/anchor-note/component.tsx +++ b/packages/widgets/src/anchor-note/component.tsx @@ -14,7 +14,7 @@ import { clientApi } from "@homarr/api/client"; import { useIntegrationsWithInteractAccess } from "@homarr/auth/client"; import { useTimeAgo } from "@homarr/common"; import type { AnchorNotePermission } from "@homarr/integrations"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client"; import { WidgetEmptyState } from "../common/empty-state"; import type { WidgetComponentProps } from "../definition"; @@ -180,6 +180,7 @@ const AnchorNoteWidgetContent = ({ isAdvanced, }: AnchorNoteWidgetContentProps) => { const t = useScopedI18n("widget.anchorNote"); + const locale = useCurrentIntlLocale(); const { data: note, refetch } = clientApi.widget.anchorNotes.getNote.useQuery({ integrationId, noteId, @@ -327,7 +328,7 @@ const AnchorNoteWidgetContent = ({ )} - {t("createdAt", { date: new Date(note.createdAt).toLocaleDateString() })} + {t("createdAt", { date: new Date(note.createdAt).toLocaleDateString(locale) })} )} diff --git a/packages/widgets/src/dns-hole/summary/component.tsx b/packages/widgets/src/dns-hole/summary/component.tsx index a1ec1f1df..86c03dd7c 100644 --- a/packages/widgets/src/dns-hole/summary/component.tsx +++ b/packages/widgets/src/dns-hole/summary/component.tsx @@ -2,19 +2,7 @@ import { useMemo } from "react"; import type { BoxProps } from "@mantine/core"; -import { - Avatar, - AvatarGroup, - Badge, - Card, - Flex, - Group, - SimpleGrid, - Stack, - Text, - Tooltip, - TooltipFloating, -} from "@mantine/core"; +import { Avatar, AvatarGroup, Badge, Card, Flex, Group, SimpleGrid, Stack, Text, Tooltip } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; import { IconBarrierBlock, IconPercentage, IconSearch, IconWorldWww } from "@tabler/icons-react"; @@ -177,10 +165,11 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => { const value = item.value(data, width <= 64 ? "sm" : "md"); return ( - + { - + ); }; diff --git a/packages/widgets/src/immich/album-carousel/component.tsx b/packages/widgets/src/immich/album-carousel/component.tsx index fa7bd4d0b..61c8c57e2 100644 --- a/packages/widgets/src/immich/album-carousel/component.tsx +++ b/packages/widgets/src/immich/album-carousel/component.tsx @@ -14,7 +14,7 @@ import { } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; -import { useI18n, useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useI18n, useScopedI18n } from "@homarr/translation/client"; import { WidgetEmptyState } from "../../common/empty-state"; import type { WidgetComponentProps } from "../../definition"; @@ -139,6 +139,7 @@ function Carousel({ setPaused, }: CarouselProps) { const t = useScopedI18n("widget.immich-albumCarousel"); + const locale = useCurrentIntlLocale(); const reduceMotion = useReducedMotion(); useEffect(() => { @@ -214,7 +215,7 @@ function Carousel({ )} - {new Date(currentAsset.fileModifiedAt).toLocaleDateString()} + {new Date(currentAsset.fileModifiedAt).toLocaleDateString(locale)} {safeCurrentIndex + 1} / {assets.length} diff --git a/packages/widgets/src/immich/albums-query.spec.ts b/packages/widgets/src/immich/albums-query.spec.ts index 99cef03e9..d04cf905c 100644 --- a/packages/widgets/src/immich/albums-query.spec.ts +++ b/packages/widgets/src/immich/albums-query.spec.ts @@ -20,6 +20,7 @@ vi.mock("@homarr/api/client", () => ({ })); vi.mock("@homarr/translation/client", () => ({ + useCurrentIntlLocale: () => "en", useI18n: () => (key: string) => key, useScopedI18n: () => (key: string) => key, })); diff --git a/packages/widgets/src/immich/server-stats/component.tsx b/packages/widgets/src/immich/server-stats/component.tsx index 0b99c21ec..d127ea463 100644 --- a/packages/widgets/src/immich/server-stats/component.tsx +++ b/packages/widgets/src/immich/server-stats/component.tsx @@ -6,7 +6,7 @@ import { IconDatabase, IconPhoto, IconUsers, IconVideo } from "@tabler/icons-rea import { clientApi } from "@homarr/api/client"; import { formatBytes } from "@homarr/common"; -import { useI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client"; import { WidgetEmptyState } from "../../common/empty-state"; import type { WidgetComponentProps } from "../../definition"; @@ -24,6 +24,7 @@ export default function ImmichServerStatsWidget({ widgetStateRef, }: WidgetComponentProps<"immich-serverStats">) { const t = useI18n(); + const locale = useCurrentIntlLocale(); const input = { integrationId: integrationIds[0] ?? "" }; const albumsInput = { ...input, limit: MAX_ADVANCED_ALBUMS }; const isAdvanced = displayMode === "advanced"; @@ -50,7 +51,7 @@ export default function ImmichServerStatsWidget({ } label={t("widget.immich-serverStats.users")} - value={stats.userCount} + value={stats.userCount.toLocaleString(locale)} dense={statsLayout.dense} /> )} @@ -58,7 +59,7 @@ export default function ImmichServerStatsWidget({ } label={t("widget.immich-serverStats.photos")} - value={stats.photoCount} + value={stats.photoCount.toLocaleString(locale)} dense={statsLayout.dense} /> )} @@ -66,7 +67,7 @@ export default function ImmichServerStatsWidget({ } label={t("widget.immich-serverStats.videos")} - value={stats.videoCount} + value={stats.videoCount.toLocaleString(locale)} dense={statsLayout.dense} /> )} @@ -103,7 +104,7 @@ export default function ImmichServerStatsWidget({ {album.albumName} - {album.assetCount.toLocaleString()} + {album.assetCount.toLocaleString(locale)} diff --git a/packages/widgets/src/media-releases/component.tsx b/packages/widgets/src/media-releases/component.tsx index c04e155de..c2ac70573 100644 --- a/packages/widgets/src/media-releases/component.tsx +++ b/packages/widgets/src/media-releases/component.tsx @@ -12,7 +12,7 @@ import { SimpleGrid, Stack, Text, - TooltipFloating, + Tooltip, UnstyledButton, } from "@mantine/core"; import { IconBook, IconCalendar, IconClock, IconStarFilled } from "@tabler/icons-react"; @@ -88,10 +88,11 @@ const Item = ({ item, options, isAdvanced, width, height }: ItemProps) => { const showSide = isAdvanced || (!isTiny && (options.showType || options.showSource)); return ( - { )} - + ); }; diff --git a/packages/widgets/src/smart-home/execute-automation/component.tsx b/packages/widgets/src/smart-home/execute-automation/component.tsx index 98964991d..87ab84265 100644 --- a/packages/widgets/src/smart-home/execute-automation/component.tsx +++ b/packages/widgets/src/smart-home/execute-automation/component.tsx @@ -19,7 +19,7 @@ import { IconAutomation, IconCheck } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useIntegrationsWithInteractAccess } from "@homarr/auth/client"; import { useRegisterSpotlightContextActions } from "@homarr/spotlight"; -import { useI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../../definition"; @@ -61,6 +61,7 @@ export default function SmartHomeTriggerAutomationWidget({ }, [canInteract, integrationId, isEditMode, isPending, mutateAsync, options.automationId]); const t = useI18n(); + const locale = useCurrentIntlLocale(); useRegisterSpotlightContextActions( `smartHome-automation-${options.automationId}`, [ @@ -150,7 +151,7 @@ export default function SmartHomeTriggerAutomationWidget({ {lastExecutedAt ? t("widget.smartHome-executeAutomation.advanced.lastExecuted", { - time: lastExecutedAt.toLocaleTimeString(), + time: lastExecutedAt.toLocaleTimeString(locale), }) : t("widget.smartHome-executeAutomation.advanced.automationId", { id: options.automationId, diff --git a/packages/widgets/src/stocks/component.tsx b/packages/widgets/src/stocks/component.tsx index d83f409d0..6fb401185 100644 --- a/packages/widgets/src/stocks/component.tsx +++ b/packages/widgets/src/stocks/component.tsx @@ -5,7 +5,7 @@ import { Flex, Stack, Text, Title, useMantineTheme } from "@mantine/core"; import { IconMinus, IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client"; import { WidgetEmptyState } from "../common/empty-state"; import type { WidgetComponentProps } from "../definition"; @@ -73,6 +73,8 @@ export function getStockSummary(priceHistory: number[], previousClose: number): export default function StockPriceWidget({ options, width, height, displayMode }: WidgetComponentProps<"stockPrice">) { const t = useScopedI18n("widget.stockPrice"); + const locale = useCurrentIntlLocale(); + const numberFormatter = new Intl.NumberFormat(locale); const theme = useMantineTheme(); const { data: result } = clientApi.widget.stockPrice.getPriceHistory.useQuery(options); @@ -120,15 +122,15 @@ export default function StockPriceWidget({ options, width, height, displayMode } - {new Intl.NumberFormat().format(round(summary.currentPrice))} + {numberFormatter.format(round(summary.currentPrice))} {layout.showChange && ( - {new Intl.NumberFormat().format(stockValuesChange)} + {numberFormatter.format(stockValuesChange)} {stockValuesChangePercentage === null ? null - : ` (${stockValuesChange > 0 ? "+" : ""}${new Intl.NumberFormat().format(stockValuesChangePercentage)}%)`} + : ` (${stockValuesChange > 0 ? "+" : ""}${numberFormatter.format(stockValuesChangePercentage)}%)`} )} diff --git a/packages/widgets/src/tracearr/recent-activity-section.tsx b/packages/widgets/src/tracearr/recent-activity-section.tsx index 982101350..994ff89a2 100644 --- a/packages/widgets/src/tracearr/recent-activity-section.tsx +++ b/packages/widgets/src/tracearr/recent-activity-section.tsx @@ -1,10 +1,11 @@ import { Avatar, Badge, Group, Paper, Stack, Text } from "@mantine/core"; import type { TracearrHistorySession } from "@homarr/integrations/types"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client"; export function RecentActivityList({ sessions }: { sessions: TracearrHistorySession[] }) { const t = useScopedI18n("widget.tracearr"); + const locale = useCurrentIntlLocale(); return ( @@ -42,7 +43,7 @@ export function RecentActivityList({ sessions }: { sessions: TracearrHistorySess {session.watched ? t("recentActivity.watched") : t("recentActivity.partial")} - {new Date(session.startedAt).toLocaleDateString()} + {new Date(session.startedAt).toLocaleDateString(locale)} diff --git a/packages/widgets/src/tracearr/violations-section.tsx b/packages/widgets/src/tracearr/violations-section.tsx index 6d052dd59..5c96a5c83 100644 --- a/packages/widgets/src/tracearr/violations-section.tsx +++ b/packages/widgets/src/tracearr/violations-section.tsx @@ -2,10 +2,11 @@ import { Avatar, Badge, Group, Paper, Stack, Text } from "@mantine/core"; import { IconAlertTriangle } from "@tabler/icons-react"; import type { TracearrViolation } from "@homarr/integrations/types"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client"; export function ViolationsList({ violations }: { violations: TracearrViolation[] }) { const t = useScopedI18n("widget.tracearr"); + const locale = useCurrentIntlLocale(); return ( @@ -53,7 +54,7 @@ export function ViolationsList({ violations }: { violations: TracearrViolation[] {violation.severity} - {new Date(violation.createdAt).toLocaleDateString()} + {new Date(violation.createdAt).toLocaleDateString(locale)} diff --git a/packages/widgets/src/umami/umami-content.tsx b/packages/widgets/src/umami/umami-content.tsx index 1179fb4f6..510d5749c 100644 --- a/packages/widgets/src/umami/umami-content.tsx +++ b/packages/widgets/src/umami/umami-content.tsx @@ -6,7 +6,7 @@ import { Box, Group, ScrollArea, SimpleGrid, Stack, Text, useMantineColorScheme import { clientApi } from "@homarr/api/client"; import { formatDuration } from "@homarr/common"; import type { UmamiVisitorStats } from "@homarr/integrations/types"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client"; import { UmamiEventsContent } from "./umami-events-content"; import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list"; @@ -44,6 +44,7 @@ export function UmamiContent({ }: UmamiContentProps) { const t = useScopedI18n("widget.umami"); const tCommon = useScopedI18n("common"); + const locale = useCurrentIntlLocale(); const { colorScheme } = useMantineColorScheme(); const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; @@ -217,8 +218,8 @@ export function UmamiContent({ {viewMode === "events" - ? (multiEventTotal ?? 0).toLocaleString() - : (visitorStats.eventCount ?? 0).toLocaleString()} + ? (multiEventTotal ?? 0).toLocaleString(locale) + : (visitorStats.eventCount ?? 0).toLocaleString(locale)} {t("event.label")} @@ -229,7 +230,7 @@ export function UmamiContent({ - {visitorStats.totalVisitors.toLocaleString()} + {visitorStats.totalVisitors.toLocaleString(locale)} {t("visitors")} @@ -239,7 +240,7 @@ export function UmamiContent({ ) : ( - {visitorStats.totalVisitors.toLocaleString()} + {visitorStats.totalVisitors.toLocaleString(locale)} {t("visitors")} @@ -250,18 +251,18 @@ export function UmamiContent({ {showSecondaryStats && ( - ● {activeVisitors.toLocaleString()} {t("active")} + ● {activeVisitors.toLocaleString(locale)} {t("active")} - {visitorStats.totalPageviews.toLocaleString()} {t("pageviews")} + {visitorStats.totalPageviews.toLocaleString(locale)} {t("pageviews")} - {visitorStats.totalVisits.toLocaleString()} {t("visits")} + {visitorStats.totalVisits.toLocaleString(locale)} {t("visits")} {showDetailedStats && ( <> - {visitorStats.bounceRate}% {t("bounceRate")} + {visitorStats.bounceRate.toLocaleString(locale)}% {t("bounceRate")} {formatDuration(visitorStats.avgDuration * 1000)} {t("avgDuration")}