i18n: scaffold panel.* and ui.* locale catalogs, wire panelCatalogs into officerdev's barrel
This commit is contained in:
@@ -210,6 +210,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"embla-carousel-react": "^8.5.1",
|
"embla-carousel-react": "^8.5.1",
|
||||||
"hooks": "*",
|
"hooks": "*",
|
||||||
|
"i18n": "*",
|
||||||
"input-otp": "^1.4.1",
|
"input-otp": "^1.4.1",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
@@ -271,6 +272,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hooks": "workspace:*",
|
"hooks": "workspace:*",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"state": "workspace:*",
|
"state": "workspace:*",
|
||||||
"widgets": "workspace:*",
|
"widgets": "workspace:*",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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';
|
||||||
@@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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<typeof en>;
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
};
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"embla-carousel-react": "^8.5.1",
|
"embla-carousel-react": "^8.5.1",
|
||||||
"hooks": "*",
|
"hooks": "*",
|
||||||
|
"i18n": "*",
|
||||||
"input-otp": "^1.4.1",
|
"input-otp": "^1.4.1",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hooks": "workspace:*",
|
"hooks": "workspace:*",
|
||||||
|
"i18n": "workspace:*",
|
||||||
"state": "workspace:*",
|
"state": "workspace:*",
|
||||||
"widgets": "workspace:*"
|
"widgets": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * from './channels';
|
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.
|
// For the shell: a screen naming itself better than its route can — see usePageTitle's precedence.
|
||||||
export { usePageTitleOverride, usePublishPageTitle } from './page-title';
|
export { usePageTitleOverride, usePublishPageTitle } from './page-title';
|
||||||
export type { PageTitleOverride } from './page-title';
|
export type { PageTitleOverride } from './page-title';
|
||||||
@@ -49,13 +51,11 @@ export type {
|
|||||||
CommandHqCostSummary,
|
CommandHqCostSummary,
|
||||||
} from './apps/CommandHQ';
|
} from './apps/CommandHQ';
|
||||||
|
|
||||||
|
|
||||||
// /calendar and /contacts select with a query param rather than a path segment, so what the screens need
|
// /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.
|
// from the app is the param name and the link builder.
|
||||||
export { COLLECTION_PARAM, davCollectionPath } from './apps/Dav/shared';
|
export { COLLECTION_PARAM, davCollectionPath } from './apps/Dav/shared';
|
||||||
export type { DavCollection, CalendarEvent, DavContact } from './apps/Dav/shared';
|
export type { DavCollection, CalendarEvent, DavContact } from './apps/Dav/shared';
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useFilesAPI,
|
useFilesAPI,
|
||||||
useTasks,
|
useTasks,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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';
|
||||||
@@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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<typeof en>;
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user