diff --git a/src/apps/officer-web/lib/i18n.ts b/src/apps/officer-web/lib/i18n.ts index 1de5c1fd..f32877ad 100644 --- a/src/apps/officer-web/lib/i18n.ts +++ b/src/apps/officer-web/lib/i18n.ts @@ -1,14 +1,32 @@ import type { TranslationKeys } from 'i18n'; import type { InterpolationParams } from 'i18n'; +import { useCallback } from 'react'; import { useTranslation as useTranslationBase } from 'i18n'; import type en from '../locales/en-GB.json'; +import type uiEn from '@/components/locales/en-GB.json'; type AppTranslationKey = TranslationKeys; +type UiTranslationKey = TranslationKeys; -// The `app.` prefix is namespace wiring for the merged catalog (D6) — callers never spell it, they -// pass the same bare key the JSON file uses. +/** + * `t` for this workspace's own catalog, `tUi` for the shared-component one. + * + * Both are here rather than in two imports because "Cancel", "Close" and "Search" recur across the + * shell and genuinely belong to `ui.*` — phase 1 put them there so phases 2 and 3 could reach for them + * instead of inventing a second key for the same word. Anything feature-specific stays in `app.*`: + * per D6 a `ui.*` key may not name a feature. + * + * Both are memoized. An unstable `t` is unusable in a dependency array, and this repo has already lost + * days to exactly that shape — see platform/CLAUDE.md on `useClient()` and the Jellyfin player. + */ export const useTranslation = () => { const { t: baseT, locale } = useTranslationBase(); - const t = (key: AppTranslationKey, params?: InterpolationParams) => baseT(`app.${key}`, params); - return { t, locale }; + + const t = useCallback((key: AppTranslationKey, params?: InterpolationParams) => baseT(`app.${key}`, params), [baseT]); + + const tUi = useCallback((key: UiTranslationKey, params?: InterpolationParams) => baseT(`ui.${key}`, params), [baseT]); + + return { t, tUi, locale }; }; + +export type { AppTranslationKey }; diff --git a/src/workspaces/components/locales/useTranslation.ts b/src/workspaces/components/locales/useTranslation.ts index eca7dd5c..9c49a13c 100644 --- a/src/workspaces/components/locales/useTranslation.ts +++ b/src/workspaces/components/locales/useTranslation.ts @@ -1,4 +1,5 @@ import type { InterpolationParams, TranslationKeys } from 'i18n'; +import { useCallback } from 'react'; import { useTranslation as useTranslationBase } from 'i18n'; import type en from './en-GB.json'; @@ -8,6 +9,8 @@ type UiTranslationKey = TranslationKeys; // pass the same bare key the JSON file uses. `ui.*` may not name a feature (see the naming convention). export const useTranslation = () => { const { t: baseT, locale } = useTranslationBase(); - const t = (key: UiTranslationKey, params?: InterpolationParams) => baseT(`ui.${key}`, params); + // Memoized: an unstable `t` is unusable in a dependency array. See platform/CLAUDE.md on the + // `useClient()` trap that silently disabled the Jellyfin player's playback reports. + const t = useCallback((key: UiTranslationKey, params?: InterpolationParams) => baseT(`ui.${key}`, params), [baseT]); return { t, locale }; }; diff --git a/src/workspaces/officerdev/src/locales/useTranslation.ts b/src/workspaces/officerdev/src/locales/useTranslation.ts index 1f1dd07b..cd16ad69 100644 --- a/src/workspaces/officerdev/src/locales/useTranslation.ts +++ b/src/workspaces/officerdev/src/locales/useTranslation.ts @@ -1,4 +1,5 @@ import type { InterpolationParams, TranslationKeys } from 'i18n'; +import { useCallback } from 'react'; import { useTranslation as useTranslationBase } from 'i18n'; import type en from './en-GB.json'; @@ -8,6 +9,11 @@ type PanelTranslationKey = TranslationKeys; // pass the same bare key the JSON file uses. export const useTranslation = () => { const { t: baseT, locale } = useTranslationBase(); - const t = (key: PanelTranslationKey, params?: InterpolationParams) => baseT(`panel.${key}`, params); + // Memoized: an unstable `t` is unusable in a dependency array. See platform/CLAUDE.md on the + // `useClient()` trap that silently disabled the Jellyfin player's playback reports. + const t = useCallback( + (key: PanelTranslationKey, params?: InterpolationParams) => baseT(`panel.${key}`, params), + [baseT], + ); return { t, locale }; };