65ca7eac5f
Desktop and web each re-implemented the same locale plumbing: the
TranslationOverride<T> partial-catalog type, isRecord (four copies across
the two apps), mergeTranslations, the RTL_LOCALES={'ar'} set with the
documentElement.lang/dir effect, and the endonym table for the language
picker (6 entries on desktop, 17 on web, overlapping and hand-synced).
The generic parts now live once in apps/shared/src/i18n.ts (exported from
the root index and the `@hermes/shared/i18n` subpath). It is generic over
the catalog type — no Translations, no `en` — so translation catalogs stay
per-app (content decision, deliberately not merged here).
Sites (path::symbol → canonical):
apps/desktop/src/i18n/define-locale.ts::TranslationOverride, isRecord,
mergeTranslations → @hermes/shared/i18n; defineLocale is a one-liner
web/src/i18n/define-locale.ts::TranslationOverride, isRecord,
mergeTranslations → @hermes/shared/i18n; defineLocale is a one-liner
apps/desktop/src/i18n/runtime.ts::isRecord → shared isRecord
apps/desktop/src/i18n/context.tsx::isRecord, RTL_LOCALES,
applyDocumentLocale → shared isRecord / applyDocumentLocale
web/src/i18n/context.tsx::RTL_LOCALES + inline lang/dir effect
→ shared applyDocumentLocale
web/src/i18n/context.tsx::LOCALE_META literal (17 names)
→ derived from shared LOCALE_ENDONYMS (same exported shape)
apps/desktop/src/i18n/languages.ts::LOCALE_OPTIONS.name (6 names)
→ LOCALE_ENDONYMS.<id>; englishName/configValue columns stay
The six desktop endonyms were byte-identical to web's before the move.
Tests: apps/shared/src/i18n.test.ts — mergeTranslations keeps untouched
sibling keys under a nested partial override and replaces functions/arrays
wholesale without mutating the base; RTL_LOCALES ⊆ keys(LOCALE_ENDONYMS);
applyDocumentLocale is a no-op without a document. The existing desktop
context.test.tsx RTL/lang assertions keep covering the effect.
Behavior change: none.
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { applyDocumentLocale, LOCALE_ENDONYMS, mergeTranslations, RTL_LOCALES, type TranslationOverride } from './i18n'
|
|
|
|
describe('mergeTranslations', () => {
|
|
it('keeps untouched sibling keys on a nested partial override and replaces functions/arrays wholesale', () => {
|
|
interface Catalog {
|
|
menu: { close: string; count: (n: number) => string; open: string }
|
|
steps: string[]
|
|
}
|
|
|
|
const base: Catalog = {
|
|
menu: { open: 'Open', close: 'Close', count: n => `${n} items` },
|
|
steps: ['one', 'two', 'three']
|
|
}
|
|
|
|
const overrides: TranslationOverride<Catalog> = {
|
|
menu: { close: 'Schließen', count: n => `${n} Einträge` },
|
|
steps: ['eins']
|
|
}
|
|
|
|
const merged = mergeTranslations<Catalog>(base, overrides)
|
|
|
|
expect(merged.menu.open).toBe('Open')
|
|
expect(merged.menu.close).toBe('Schließen')
|
|
expect(merged.menu.count(2)).toBe('2 Einträge')
|
|
expect(merged.steps).toEqual(['eins'])
|
|
expect(base.menu.close).toBe('Close')
|
|
})
|
|
})
|
|
|
|
describe('RTL_LOCALES', () => {
|
|
it('only names locales that have an endonym', () => {
|
|
for (const locale of RTL_LOCALES) {
|
|
expect(Object.keys(LOCALE_ENDONYMS)).toContain(locale)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('applyDocumentLocale', () => {
|
|
it('is a no-op without a document', () => {
|
|
expect(typeof document).toBe('undefined')
|
|
expect(() => applyDocumentLocale('ar')).not.toThrow()
|
|
})
|
|
})
|