From 6ac9187c52457bedbbba59630a8ecd494039258d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 03:42:53 +0000 Subject: [PATCH] i18n: scaffold panel.* and ui.* locale catalogs, wire panelCatalogs into officerdev's barrel --- bun.lock | 2 ++ src/workspaces/components/locales/en-GB.json | 1 + src/workspaces/components/locales/index.ts | 8 ++++++++ src/workspaces/components/locales/parity.test.ts | 12 ++++++++++++ src/workspaces/components/locales/pt-PT.json | 1 + src/workspaces/components/locales/useTranslation.ts | 13 +++++++++++++ src/workspaces/components/package.json | 1 + src/workspaces/officerdev/package.json | 1 + src/workspaces/officerdev/src/index.ts | 4 ++-- src/workspaces/officerdev/src/locales/en-GB.json | 1 + src/workspaces/officerdev/src/locales/index.ts | 8 ++++++++ .../officerdev/src/locales/parity.test.ts | 12 ++++++++++++ src/workspaces/officerdev/src/locales/pt-PT.json | 1 + .../officerdev/src/locales/useTranslation.ts | 13 +++++++++++++ 14 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/workspaces/components/locales/en-GB.json create mode 100644 src/workspaces/components/locales/index.ts create mode 100644 src/workspaces/components/locales/parity.test.ts create mode 100644 src/workspaces/components/locales/pt-PT.json create mode 100644 src/workspaces/components/locales/useTranslation.ts create mode 100644 src/workspaces/officerdev/src/locales/en-GB.json create mode 100644 src/workspaces/officerdev/src/locales/index.ts create mode 100644 src/workspaces/officerdev/src/locales/parity.test.ts create mode 100644 src/workspaces/officerdev/src/locales/pt-PT.json create mode 100644 src/workspaces/officerdev/src/locales/useTranslation.ts diff --git a/bun.lock b/bun.lock index f0e590eb..2b732f37 100644 --- a/bun.lock +++ b/bun.lock @@ -210,6 +210,7 @@ "dotenv": "^16.4.7", "embla-carousel-react": "^8.5.1", "hooks": "*", + "i18n": "*", "input-otp": "^1.4.1", "lucide-react": "^0.468.0", "next-themes": "^0.4.4", @@ -271,6 +272,7 @@ "version": "0.0.1", "dependencies": { "hooks": "workspace:*", + "i18n": "workspace:*", "state": "workspace:*", "widgets": "workspace:*", }, diff --git a/src/workspaces/components/locales/en-GB.json b/src/workspaces/components/locales/en-GB.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/workspaces/components/locales/en-GB.json @@ -0,0 +1 @@ +{} diff --git a/src/workspaces/components/locales/index.ts b/src/workspaces/components/locales/index.ts new file mode 100644 index 00000000..d8f20679 --- /dev/null +++ b/src/workspaces/components/locales/index.ts @@ -0,0 +1,8 @@ +import type { TranslationMap } from 'i18n'; +import enGB from './en-GB.json'; +import ptPT from './pt-PT.json'; + +/** Shared components' own catalog, per locale — merged into the app-wide map by `I18nBridge` under `ui.*`. */ +export const uiCatalogs: TranslationMap = { 'en-GB': enGB, 'pt-PT': ptPT }; + +export { useTranslation } from './useTranslation'; diff --git a/src/workspaces/components/locales/parity.test.ts b/src/workspaces/components/locales/parity.test.ts new file mode 100644 index 00000000..cf0a4d31 --- /dev/null +++ b/src/workspaces/components/locales/parity.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, test } from 'bun:test'; +import { findMissingKeys } from 'i18n'; +import en from './en-GB.json'; +import ptPT from './pt-PT.json'; + +// D2: every shipped locale ships a COMPLETE catalog. Phase 0 scaffolds this catalog with no keys yet +// (ui.* strings land in phase 1) — the test still runs so it is load-bearing from day one. +describe('shared components (ui.*) locale catalogs', () => { + test('pt-PT has every key en-GB has', () => { + expect(findMissingKeys(en, { 'pt-PT': ptPT })).toEqual([]); + }); +}); diff --git a/src/workspaces/components/locales/pt-PT.json b/src/workspaces/components/locales/pt-PT.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/workspaces/components/locales/pt-PT.json @@ -0,0 +1 @@ +{} diff --git a/src/workspaces/components/locales/useTranslation.ts b/src/workspaces/components/locales/useTranslation.ts new file mode 100644 index 00000000..eca7dd5c --- /dev/null +++ b/src/workspaces/components/locales/useTranslation.ts @@ -0,0 +1,13 @@ +import type { InterpolationParams, TranslationKeys } from 'i18n'; +import { useTranslation as useTranslationBase } from 'i18n'; +import type en from './en-GB.json'; + +type UiTranslationKey = TranslationKeys; + +// The `ui.` prefix is namespace wiring for the merged catalog (D6) — callers never spell it, they +// 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); + return { t, locale }; +}; diff --git a/src/workspaces/components/package.json b/src/workspaces/components/package.json index 86cba905..d29bed5a 100644 --- a/src/workspaces/components/package.json +++ b/src/workspaces/components/package.json @@ -45,6 +45,7 @@ "dotenv": "^16.4.7", "embla-carousel-react": "^8.5.1", "hooks": "*", + "i18n": "*", "input-otp": "^1.4.1", "lucide-react": "^0.468.0", "next-themes": "^0.4.4", diff --git a/src/workspaces/officerdev/package.json b/src/workspaces/officerdev/package.json index 71019b0f..c4e6b67b 100644 --- a/src/workspaces/officerdev/package.json +++ b/src/workspaces/officerdev/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "hooks": "workspace:*", + "i18n": "workspace:*", "state": "workspace:*", "widgets": "workspace:*" } diff --git a/src/workspaces/officerdev/src/index.ts b/src/workspaces/officerdev/src/index.ts index 1f72c34c..f8246f06 100644 --- a/src/workspaces/officerdev/src/index.ts +++ b/src/workspaces/officerdev/src/index.ts @@ -1,5 +1,7 @@ export * from './hooks'; export * from './channels'; +// The panel apps' own i18n catalog — I18nBridge merges it into the app-wide map under `panel.*`. +export { panelCatalogs } from './locales'; // For the shell: a screen naming itself better than its route can — see usePageTitle's precedence. export { usePageTitleOverride, usePublishPageTitle } from './page-title'; export type { PageTitleOverride } from './page-title'; @@ -49,13 +51,11 @@ export type { CommandHqCostSummary, } from './apps/CommandHQ'; - // /calendar and /contacts select with a query param rather than a path segment, so what the screens need // from the app is the param name and the link builder. export { COLLECTION_PARAM, davCollectionPath } from './apps/Dav/shared'; export type { DavCollection, CalendarEvent, DavContact } from './apps/Dav/shared'; - export { useFilesAPI, useTasks, diff --git a/src/workspaces/officerdev/src/locales/en-GB.json b/src/workspaces/officerdev/src/locales/en-GB.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/workspaces/officerdev/src/locales/en-GB.json @@ -0,0 +1 @@ +{} diff --git a/src/workspaces/officerdev/src/locales/index.ts b/src/workspaces/officerdev/src/locales/index.ts new file mode 100644 index 00000000..3088b54b --- /dev/null +++ b/src/workspaces/officerdev/src/locales/index.ts @@ -0,0 +1,8 @@ +import type { TranslationMap } from 'i18n'; +import enGB from './en-GB.json'; +import ptPT from './pt-PT.json'; + +/** Panel apps' own catalog, per locale — merged into the app-wide map by `I18nBridge` under `panel.*`. */ +export const panelCatalogs: TranslationMap = { 'en-GB': enGB, 'pt-PT': ptPT }; + +export { useTranslation } from './useTranslation'; diff --git a/src/workspaces/officerdev/src/locales/parity.test.ts b/src/workspaces/officerdev/src/locales/parity.test.ts new file mode 100644 index 00000000..5c603fa4 --- /dev/null +++ b/src/workspaces/officerdev/src/locales/parity.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, test } from 'bun:test'; +import { findMissingKeys } from 'i18n'; +import en from './en-GB.json'; +import ptPT from './pt-PT.json'; + +// D2: every shipped locale ships a COMPLETE catalog. Phase 0 scaffolds this catalog with no keys yet +// (no panel is converted in this phase) — the test still runs so it is load-bearing from day one. +describe('officerdev (panel.*) locale catalogs', () => { + test('pt-PT has every key en-GB has', () => { + expect(findMissingKeys(en, { 'pt-PT': ptPT })).toEqual([]); + }); +}); diff --git a/src/workspaces/officerdev/src/locales/pt-PT.json b/src/workspaces/officerdev/src/locales/pt-PT.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/workspaces/officerdev/src/locales/pt-PT.json @@ -0,0 +1 @@ +{} diff --git a/src/workspaces/officerdev/src/locales/useTranslation.ts b/src/workspaces/officerdev/src/locales/useTranslation.ts new file mode 100644 index 00000000..1f1dd07b --- /dev/null +++ b/src/workspaces/officerdev/src/locales/useTranslation.ts @@ -0,0 +1,13 @@ +import type { InterpolationParams, TranslationKeys } from 'i18n'; +import { useTranslation as useTranslationBase } from 'i18n'; +import type en from './en-GB.json'; + +type PanelTranslationKey = TranslationKeys; + +// The `panel.` prefix is namespace wiring for the merged catalog (D6) — callers never spell it, they +// 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); + return { t, locale }; +};