{"_id":"@molecule/app-i18n","_rev":"3-48efecdce8e262b6802bd967bf94d649","name":"@molecule/app-i18n","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@molecule/app-i18n","version":"1.0.0","keywords":["molecule","i18n","internationalization","translations","localization"],"license":"Apache-2.0","_id":"@molecule/app-i18n@1.0.0","maintainers":[{"name":"vialoh","email":"npm@vialoh.me"}],"homepage":"https://github.com/molecule-dev/molecule/tree/main/packages/app/core/i18n","bugs":{"url":"https://github.com/molecule-dev/molecule/issues"},"dist":{"shasum":"b9ee9f990590f00980356d68d6c07b7b59737857","tarball":"https://registry.npmjs.org/@molecule/app-i18n/-/app-i18n-1.0.0.tgz","fileCount":30,"integrity":"sha512-JItgKQPeI5EdXDwHNU94/gJfgIIAt+PnKou6F5m1t87iTJw/WvE8o1FWv/NC/35YadfMEHf+AhgYfD/NB0DjAQ==","signatures":[{"sig":"MEYCIQDQtinPVwgs/8YokzC0v7j2VJ0fdEnY+4k0wladiKI5LwIhAKE7lzppkAFljPKTzIWINT66aB4KN+NPlDTpbj7DZqjn","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73604},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"92623e72a527ca467963169420f4cf07533e4699","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"vialoh","email":"npm@vialoh.me"},"repository":{"url":"git+https://github.com/molecule-dev/molecule.git","type":"git","directory":"packages/app/core/i18n"},"_npmVersion":"11.12.1","description":"Internationalization interface for molecule.dev","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"4.1.10","typescript":"6.0.3","@types/node":"26.1.2","@molecule/app-bond":"1.0.0","@molecule/app-logger":"1.0.0"},"peerDependencies":{"@molecule/app-bond":"^1.0.0","@molecule/app-logger":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/app-i18n_1.0.0_1785796139018_0.06165350891968169","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@molecule/app-i18n","version":"1.0.1","keywords":["molecule","i18n","internationalization","translations","localization"],"license":"Apache-2.0","_id":"@molecule/app-i18n@1.0.1","maintainers":[{"name":"vialoh","email":"npm@vialoh.me"}],"homepage":"https://github.com/molecule-dev/molecule/tree/main/packages/app/core/i18n","bugs":{"url":"https://github.com/molecule-dev/molecule/issues"},"dist":{"shasum":"6ec20a4455b433473fe1a8b4e8493800ca3b67da","tarball":"https://registry.npmjs.org/@molecule/app-i18n/-/app-i18n-1.0.1.tgz","fileCount":31,"integrity":"sha512-hobyGT8OSdiwIWMW/pubKXL9gd1jxt78rBUrCTyXhoeK4/aam5v3TBAK3Q0OcbXFQQgedvDwa2irXEyxKCeqlg==","signatures":[{"sig":"MEQCIAX58fpAbj2FWo/QsGEey0snthtr4eYVcOjjVSdAGUUiAiBp1OEo45FAJi1oS4Poi8nD28vOewaxNwGWOWZpELDe/A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@molecule%2fapp-i18n@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":93830},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"8621216fd4c8c9abe863e4e4f41efd2bc866fb09","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"vialoh","email":"npm@vialoh.me"},"repository":{"url":"git+https://github.com/molecule-dev/molecule.git","type":"git","directory":"packages/app/core/i18n"},"_npmVersion":"12.0.2","description":"Internationalization interface for molecule.dev","directories":{},"_nodeVersion":"22.23.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"4.1.10","typescript":"6.0.3","@types/node":"26.1.2","@molecule/app-bond":"1.0.1","@molecule/app-logger":"1.0.1"},"peerDependencies":{"@molecule/app-bond":"^1.0.1","@molecule/app-logger":"^1.0.1"},"_npmOperationalInternal":{"tmp":"tmp/app-i18n_1.0.1_1785821172769_0.5939310758663763","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"_id":"@molecule/app-i18n@1.0.2","bugs":{"url":"https://github.com/molecule-dev/molecule/issues"},"dist":{"shasum":"29bc06899f32bc3f60b5ed443ff2dc2fe94eb571","tarball":"https://registry.npmjs.org/@molecule/app-i18n/-/app-i18n-1.0.2.tgz","fileCount":31,"integrity":"sha512-y7A29iyX+5vi0H9lSmpenFAkzyAEnQl3Eh4RCqCNCJwD1eXne3YmCOHYEtDO0+N7irhi2VqEmyIFBEw/n5IlMw==","signatures":[{"sig":"MEUCIQCX2vFSl16Im1YcuFPGi1IV3evyCGj6OQ8nzi98qZIvkAIgHwWoDo0wf8ikL5RHLNfDtraXsfJ/zgvaZM1aHOJ2edk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD6x1DjNU/H8iETY3TgmBEHSBaClqmpB7W6QB7a1V74VQIgGC2LjPkr8BM/e0vq9qW6CwxkO57PvHY2zo0XPqPVIs8="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@molecule%2fapp-i18n@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":93799},"main":"dist/index.js","name":"@molecule/app-i18n","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"41bbb7d6c46b04d052a6b333e1b18e76ed007d23","license":"Apache-2.0","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest"},"version":"1.0.2","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:587a90ce-38e4-4a6c-a3f7-f18685248513"}},"homepage":"https://www.molecule.dev/packages/app-i18n","keywords":["molecule","i18n","internationalization","translations","localization"],"repository":{"url":"git+https://github.com/molecule-dev/molecule.git","type":"git","directory":"packages/app/core/i18n"},"_npmVersion":"12.0.2","description":"Internationalization interface for molecule.dev","directories":{},"maintainers":[{"name":"vialoh","email":"npm@vialoh.me"}],"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"4.1.11","typescript":"6.0.3","@types/node":"26.1.2","@molecule/app-bond":"1.0.2","@molecule/app-logger":"1.0.2"},"peerDependencies":{"@molecule/app-bond":"^1.0.1","@molecule/app-logger":"^1.0.1"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/app-i18n_1.0.2_1789909643355_0.5415898399806665"}}},"time":{"created":"2026-08-03T22:28:58.903Z","modified":"2026-09-20T13:07:23.864Z","1.0.0":"2026-08-03T22:28:59.148Z","1.0.1":"2026-08-04T05:26:12.898Z","1.0.2":"2026-09-20T13:07:23.433Z"},"bugs":{"url":"https://github.com/molecule-dev/molecule/issues"},"license":"Apache-2.0","homepage":"https://www.molecule.dev/packages/app-i18n","keywords":["molecule","i18n","internationalization","translations","localization"],"repository":{"url":"git+https://github.com/molecule-dev/molecule.git","type":"git","directory":"packages/app/core/i18n"},"description":"Internationalization interface for molecule.dev","maintainers":[{"name":"vialoh","email":"npm@vialoh.me"}],"readme":"<!--\nAUTO-GENERATED — DO NOT EDIT THIS FILE.\nGenerated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.\nEdits here are overwritten on the next commit (molecule's pre-commit hook regenerates).\nTo change this document, edit the module-level JSDoc in src/index.ts.\nGenerated: 2026-08-04T01:50:59.876Z\n-->\n\n# @molecule/app-i18n\n\n> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.\n> It is written to be read by coding agents as much as by people, and is generated from this\n> package's source — edit `src/index.ts` JSDoc, not this file.\n\nInternationalization (i18n) interface for molecule.dev.\n\nProvides a unified API for translations and localization that works\nwith different i18n libraries (react-i18next, FormatJS, etc.).\n\n## Quick Start\n\n```tsx\nimport { t } from '@molecule/app-i18n'\n// ALWAYS pass a defaultValue — it renders immediately as the English text.\n;<button>{t('settings.save', undefined, { defaultValue: 'Save' })}</button>\n```\n\n## Type\n\n`core`\n\n## Installation\n\n```bash\nnpm install @molecule/app-i18n @molecule/app-bond @molecule/app-logger\n```\n\n## API\n\n### Interfaces\n\n#### `DateFormatOptions`\n\nDate format options.\n\n```typescript\ninterface DateFormatOptions {\n  /**\n   * Date style.\n   */\n  dateStyle?: 'full' | 'long' | 'medium' | 'short'\n\n  /**\n   * Time style.\n   */\n  timeStyle?: 'full' | 'long' | 'medium' | 'short'\n\n  /**\n   * Custom format string (implementation-specific).\n   */\n  format?: string\n\n  /**\n   * Relative time.\n   */\n  relative?: boolean\n}\n```\n\n#### `I18nProvider`\n\ni18n provider interface.\n\nAll i18n providers must implement this interface.\n\n```typescript\ninterface I18nProvider {\n  /**\n   * Gets the current locale.\n   */\n  getLocale(): string\n\n  /**\n   * Sets the current locale.\n   *\n   * **Fleet contract:** every conformant provider (the core simple provider,\n   * `@molecule/api-i18n-simple`, `@molecule/app-i18n-i18next`, and\n   * `@molecule/app-i18n-react-i18next`) MUST throw `Error('Locale \"<code>\"\n   * not found')` when `locale` is not registered — via the constructor's\n   * `initialLocales`/`locales` config, `addLocale()`, or `addTranslations()`\n   * (all three register a locale). It must NOT silently degrade to\n   * fallback-locale text while `getLocale()` reports the unregistered code —\n   * that divergence makes a misconfigured locale switch indistinguishable\n   * from a working one until a user notices the wrong language on screen.\n   */\n  setLocale(locale: string): Promise<void>\n\n  /**\n   * Gets all available locales.\n   */\n  getLocales(): LocaleConfig[]\n\n  /**\n   * Adds a locale.\n   */\n  addLocale(config: LocaleConfig): void\n\n  /**\n   * Removes a locale by code, notifying subscribers so language pickers\n   * built on `onLocaleChange` re-render their list. If the removed locale\n   * is currently active, the caller is responsible for switching to a\n   * fallback (e.g. `'en'`) BEFORE calling this — the provider will not\n   * auto-fall-back on its own.\n   *\n   * Returns `true` if the locale was registered and removed, `false`\n   * otherwise.\n   */\n  removeLocale(code: string): boolean\n\n  /**\n   * Adds translations to a locale. Auto-creates the locale if it doesn't exist.\n   *\n   * **Fleet contract:** merges are DEEP, not a shallow spread — registering\n   * two calls (e.g. two modules) that share a top-level namespace key merges\n   * their subtrees instead of the second call clobbering the first's nested\n   * translations wholesale. `@molecule/api-i18n-simple` implements the same\n   * contract on the API side.\n   */\n  addTranslations(locale: string, translations: Translations, namespace?: string): void\n\n  /**\n   * Translates a key with optional interpolation values and pluralization.\n   *\n   * **Fleet plural contract (matches i18next's own key resolution order):**\n   * when `options.count` is provided, the plural-suffixed key\n   * (`` `${key}_${pluralForm}` ``, e.g. `item_one`/`item_few`/…, falling back\n   * to `` `${key}_other` ``) is looked up FIRST and wins over the base `key`\n   * if BOTH are registered. Only when no plural-suffixed key exists at all\n   * does resolution fall back to the base key. A catalog that ships both\n   * `item` and `item_one`/`item_other` therefore pluralizes identically\n   * whichever provider is bonded.\n   *\n   * @returns The translated string, or the default value / key if not found.\n   */\n  t(\n    key: string,\n    values?: InterpolationValues,\n    options?: { defaultValue?: string; count?: number },\n  ): string\n\n  /**\n   * Checks if a translation key exists.\n   *\n   * **Fleet contract:** follows the SAME locale-resolution chain as `t()` —\n   * the active locale, then the English fallback — so `exists(key) === true`\n   * whenever `t(key)` would render real translated text (not the raw key or\n   * an inline `defaultValue`). Do not narrow this to \"only the active\n   * locale's own catalog\"; that made `exists()` return `false` for keys `t()`\n   * happily rendered via the English fallback, and the answer differed by\n   * provider.\n   *\n   * @returns `true` if the key has a translation.\n   */\n  exists(key: string): boolean\n\n  /**\n   * Formats a number according to the current locale.\n   *\n   * @returns The locale-formatted number string.\n   */\n  formatNumber(value: number, options?: NumberFormatOptions): string\n\n  /**\n   * Formats a date according to the current locale.\n   *\n   * @returns The locale-formatted date string.\n   */\n  formatDate(value: Date | number | string, options?: DateFormatOptions): string\n\n  /**\n   * Formats a relative time (e.g. \"2 hours ago\").\n   *\n   * @returns The locale-formatted relative time string.\n   */\n  formatRelativeTime(value: Date | number, options?: { unit?: Intl.RelativeTimeFormatUnit }): string\n\n  /**\n   * Formats a list (e.g. \"A, B, and C\").\n   *\n   * @returns The locale-formatted list string.\n   */\n  formatList(values: string[], options?: { type?: 'conjunction' | 'disjunction' | 'unit' }): string\n\n  /**\n   * Subscribes to locale changes.\n   *\n   * @returns An unsubscribe function.\n   */\n  onLocaleChange(listener: (locale: string) => void): () => void\n\n  /**\n   * Gets the text direction for the current locale.\n   *\n   * @returns `'ltr'` or `'rtl'`.\n   */\n  getDirection(): 'ltr' | 'rtl'\n\n  /**\n   * Checks if a translation key exists (alias for exists).\n   */\n  hasKey?(key: string): boolean\n\n  /**\n   * Checks if the provider is ready.\n   */\n  isReady?(): boolean\n\n  /**\n   * Registers a callback for when the provider is ready.\n   */\n  onReady?(callback: () => void): () => void\n\n  /**\n   * Registers a lazily-loaded content module for automatic reload on locale changes.\n   * All registered content is reloaded during `setLocale()` before listeners fire,\n   * ensuring content is available on the first re-render with no flash.\n   *\n   * Idempotent: registering the same module name twice is a no-op.\n   */\n  registerContent?(module: string, loader: (locale: string) => Promise<void>): void\n}\n```\n\n#### `LocaleConfig`\n\nConfiguration for a supported locale (code, display name, text direction, translations or lazy loader).\n\n```typescript\ninterface LocaleConfig {\n  /**\n   * Locale code (e.g., 'en-US', 'fr-FR').\n   */\n  code: string\n\n  /**\n   * Display name (e.g., 'English (US)', 'Francais').\n   */\n  name: string\n\n  /**\n   * Native display name.\n   */\n  nativeName?: string\n\n  /**\n   * Text direction.\n   */\n  direction?: 'ltr' | 'rtl'\n\n  /**\n   * Translations for this locale.\n   */\n  translations?: Translations\n\n  /**\n   * Lazy loader for translations. Called on first setLocale() to this locale.\n   * When provided, translations can be omitted and will be loaded on demand.\n   */\n  loader?: () => Promise<Translations>\n}\n```\n\n#### `NumberFormatOptions`\n\nNumber format options.\n\n```typescript\ninterface NumberFormatOptions {\n  /**\n   * Number style.\n   */\n  style?: 'decimal' | 'currency' | 'percent' | 'unit'\n\n  /**\n   * Currency code (for currency style).\n   */\n  currency?: string\n\n  /**\n   * Minimum fraction digits.\n   */\n  minimumFractionDigits?: number\n\n  /**\n   * Maximum fraction digits.\n   */\n  maximumFractionDigits?: number\n\n  /**\n   * Use grouping separators.\n   */\n  useGrouping?: boolean\n}\n```\n\n#### `PluralRule`\n\nPlural form resolution rule mapping a count to a translation category (zero, one, two, few, many, other).\n\n```typescript\ninterface PluralRule {\n  /**\n   * Zero count text.\n   */\n  zero?: string\n\n  /**\n   * One count text.\n   */\n  one?: string\n\n  /**\n   * Two count text.\n   */\n  two?: string\n\n  /**\n   * Few count text (for some languages).\n   */\n  few?: string\n\n  /**\n   * Many count text.\n   */\n  many?: string\n\n  /**\n   * Other count text (default).\n   */\n  other: string\n}\n```\n\n#### `Translations`\n\nTranslation key/value map.\n\n```typescript\ninterface Translations {\n  [key: string]: string | Translations\n}\n```\n\n### Types\n\n#### `InterpolationValues`\n\nKey-value map of interpolation variables passed to a translation string (e.g. `{ name: 'World' }`).\n\n```typescript\ntype InterpolationValues = Record<string, string | number | boolean | Date>\n```\n\n#### `TranslateFunction`\n\nStandalone translate function signature matching `I18nProvider.t()`.\n\n```typescript\ntype TranslateFunction = (\n  key: string,\n  values?: InterpolationValues,\n  options?: { defaultValue?: string; count?: number },\n) => string\n```\n\n#### `TranslateOptions`\n\nOptions for the translate function (default value, pluralization count).\n\n```typescript\ntype TranslateOptions = { defaultValue?: string; count?: number }\n```\n\n### Classes\n\n#### `I18nError`\n\nAn error that stores an i18n key instead of a pre-translated string.\n\n**Always throw this instead of `new Error(t('key'))`.**\nThe plain-Error form translates at throw-time, permanently freezing the message\nin whatever locale was active — switching languages later has no effect.\n`I18nError` preserves the key so `useI18nError` (from `@molecule/app-react`)\ncan re-translate at render time, making displayed errors update automatically\nwhen the locale changes.\n\nThe `fallback` argument (English text) becomes `error.message` for non-React\nconsumers such as loggers and unit tests.\n\n### Functions\n\n#### `addLocale(config)`\n\nRegisters a locale configuration (code, name, translations or loader).\n\n```typescript\nfunction addLocale(config: LocaleConfig): void\n```\n\n- `config` — The locale configuration to add.\n\n**Returns:** Nothing.\n\n#### `addTranslations(locale, translations, namespace)`\n\nAdds translations to a locale, optionally under a namespace prefix.\nAuto-creates the locale if it doesn't already exist.\n\n```typescript\nfunction addTranslations(locale: string, translations: Translations, namespace?: string): void\n```\n\n- `locale` — The locale code to add translations to.\n- `translations` — The translation key-value map.\n- `namespace` — Optional namespace prefix for the translation keys.\n\n**Returns:** Nothing.\n\n#### `createSimpleI18nProvider(initialLocale, initialLocales)`\n\nCreates an in-memory i18n provider with translation lookup, interpolation,\npluralization, `Intl`-based formatting, lazy locale loading, and\nlocale change subscription.\n\n```typescript\nfunction createSimpleI18nProvider(\n  initialLocale?: string,\n  initialLocales?: LocaleConfig[],\n): I18nProvider\n```\n\n- `initialLocale` — The initial active locale code (defaults to `'en'`).\n- `initialLocales` — Pre-registered locale configurations with optional translations.\n\n**Returns:** A fully functional `I18nProvider` instance.\n\n#### `deepMerge(target, source)`\n\nRecursively merges source translation entries into a COPY of the target\nobject, preserving existing keys and overwriting only the leaves that\nconflict. Used by `addTranslations()` so registering two modules that\nshare a top-level namespace key merges their subtrees instead of one\nmodule's translations clobbering the other's — matching the deep-merge\ncontract documented on `I18nProvider.addTranslations`.\n\n```typescript\nfunction deepMerge(target: Translations, source: Translations): Translations\n```\n\n- `target` — The base translations object (not mutated).\n- `source` — The translations to merge on top.\n\n**Returns:** A new object with `source` deep-merged over `target`.\n\n#### `formatDate(value, options)`\n\nFormats a date according to the current locale.\n\n```typescript\nfunction formatDate(value: string | number | Date, options?: DateFormatOptions): string\n```\n\n- `value` — The date to format (Date object, timestamp, or date string).\n- `options` — Date formatting options (dateStyle, timeStyle, relative).\n\n**Returns:** The locale-formatted date string.\n\n#### `formatNumber(value, options)`\n\nFormats a number according to the current locale.\n\n```typescript\nfunction formatNumber(value: number, options?: NumberFormatOptions): string\n```\n\n- `value` — The number to format.\n- `options` — Number formatting options (style, currency, fraction digits).\n\n**Returns:** The locale-formatted number string.\n\n#### `formatRelativeTime(value, options)`\n\nFormats a relative time (e.g. \"2 hours ago\", \"in 3 days\").\n\n```typescript\nfunction formatRelativeTime(\n  value: number | Date,\n  options?: { unit?: Intl.RelativeTimeFormatUnit },\n): string\n```\n\n- `value` — The date or timestamp to express relative to now.\n- `options` — Optional settings; `unit` forces the difference to be expressed in that unit.\n\n**Returns:** The locale-formatted relative time string.\n\n#### `getLocale()`\n\nReturns the current locale code (e.g. `'en'`, `'fr'`).\n\n```typescript\nfunction getLocale(): string\n```\n\n**Returns:** The active locale code string.\n\n#### `getNestedValue(obj, key)`\n\nRetrieves a translation string from a nested translations object using\ndot-notation keys. First checks for a flat key match (e.g. `'auth.login.email'`\nas a direct property), then traverses nested objects.\n\n```typescript\nfunction getNestedValue(obj: Translations, key: string): string | undefined\n```\n\n- `obj` — The translations object to search.\n- `key` — The dot-notation key (e.g. `'auth.login.title'`).\n\n**Returns:** The translation string, or `undefined` if not found.\n\n#### `getPluralForm(count, locale)`\n\nReturns the CLDR plural category for a given count and locale\nusing the `Intl.PluralRules` API.\n\n```typescript\nfunction getPluralForm(count: number, locale: string): string\n```\n\n- `count` — The numeric count to determine the plural form for.\n- `locale` — The BCP 47 locale string (e.g. `'en'`, `'fr'`, `'ar'`).\n\n**Returns:** The plural category: `'zero'`, `'one'`, `'two'`, `'few'`, `'many'`, or `'other'`.\n\n#### `getProvider()`\n\nRetrieves the bonded i18n provider. If none is bonded,\nautomatically creates a simple in-memory provider.\n\n```typescript\nfunction getProvider(): I18nProvider\n```\n\n**Returns:** The active i18n provider instance.\n\n#### `interpolate(text, values)`\n\nReplaces `{{key}}` placeholders in a string with values from the\nprovided map. Dates are formatted with `toLocaleDateString()`;\nother values are converted via `String()`.\n\n```typescript\nfunction interpolate(text: string, values: InterpolationValues): string\n```\n\n- `text` — The template string containing `\\{\\{key\\}\\}` placeholders.\n- `values` — A map of placeholder names to their replacement values.\n\n**Returns:** The interpolated string with all matched placeholders replaced.\n\n#### `onLocaleChange(listener)`\n\nSubscribes to locale changes. The listener fires whenever `setLocale()` is called.\n\n```typescript\nfunction onLocaleChange(listener: (locale: string) => void): () => void\n```\n\n- `listener` — Callback invoked with the new locale code.\n\n**Returns:** An unsubscribe function.\n\n#### `registerContent(module, loader)`\n\nRegisters a lazily-loaded content module for automatic reload on locale changes.\nAll registered content is reloaded during `setLocale()` before listeners fire.\n\n```typescript\nfunction registerContent(module: string, loader: (locale: string) => Promise<void>): void\n```\n\n- `module` — Unique content module identifier (e.g. `'privacyPolicy'`).\n- `loader` — Function that loads and merges translations for a given locale.\n\n#### `registerLocaleModule(moduleExports)`\n\nRegisters all locale exports from a locale bond module.\nHandles `zhTW` → `zh-TW` camelCase-to-code mapping automatically.\n\n```typescript\nfunction registerLocaleModule(moduleExports: Record<string, unknown>): void\n```\n\n- `moduleExports` — The `import * as locales` object from a locale bond package.\n\n#### `setLocale(locale)`\n\nSets the active locale and loads its translations if a loader is configured.\n\n```typescript\nfunction setLocale(locale: string): Promise<void>\n```\n\n- `locale` — The locale code to activate (e.g. `'en'`, `'fr'`).\n\n**Returns:** A promise that resolves when the locale is activated and translations are loaded.\n\n#### `setProvider(provider)`\n\nRegisters an i18n provider as the active singleton.\n\n```typescript\nfunction setProvider(provider: I18nProvider): void\n```\n\n- `provider` — The i18n provider implementation to bond.\n\n#### `t(key, values, options)`\n\nTranslates a key using the bonded i18n provider, with optional\ninterpolation values and pluralization.\n\n```typescript\nfunction t(\n  key: string,\n  values?: InterpolationValues,\n  options?: { defaultValue?: string; count?: number },\n): string\n```\n\n- `key` — Dot-delimited translation key (e.g. `'auth.login.title'`).\n- `values` — Interpolation values to substitute into the translation.\n- `options` — Translation options including `defaultValue` and `count` for pluralization.\n- `options.defaultValue` — Fallback string if the key is not found.\n- `options.count` — Count value for pluralization.\n\n**Returns:** The translated string, or the `defaultValue` / key if not found.\n\n### Constants\n\n#### `simpleProvider`\n\nDefault in-memory i18n provider instance with `'en'` locale.\nUsed as a fallback when no bond package provides a provider.\n\n```typescript\nconst simpleProvider: I18nProvider\n```\n\n## Available Providers\n\n| Provider      | Package                            |\n| ------------- | ---------------------------------- |\n| i18next       | `@molecule/app-i18n-i18next`       |\n| react-i18next | `@molecule/app-i18n-react-i18next` |\n\n## Injection Notes\n\n### Requirements\n\nPeer dependencies:\n\n- `@molecule/app-bond` ^1.0.1\n- `@molecule/app-logger` ^1.0.1\n\n### Runtime Dependencies\n\n- `@molecule/app-bond`\n- `@molecule/app-logger`\n\nEvery user-visible string must go through `t()` — never hardcode UI text. And\nALWAYS supply `{ defaultValue: 'English text' }`: a bare `t('settings.save')`\nrenders the raw KEY string (\"settings.save\") in the UI until a translation for\nthat key loads, so the default is what the user actually sees. Translation keys\nare optional polish layered on top; the `defaultValue` is the real copy. Add\nkeys to the app's `locales/en/ui.ts` (and per-locale `{code}/ui.ts`) — not in\nfeature packages.\n\nInterpolation tokens use **DOUBLE braces** `{{var}}`, never single `{var}` — a\nsingle brace is NOT interpolated and renders LITERALLY (you see \"{name}\" on the\npage). This applies to BOTH the `defaultValue` and any matching locale-file\nentry: `t('hero', { tagline }, { defaultValue: 'Eat well — {{tagline}}' })` and\n`heroTitle: 'Eat well — {{tagline}}'`. The locale entry takes priority over the\n`defaultValue`, so a single-brace locale entry shows `{tagline}` even when the\ndefault is correct — the #1 i18n footgun.\n\n**Fleet provider contract** (every `I18nProvider` — this package's own\n`createSimpleI18nProvider`, `@molecule/api-i18n-simple`,\n`@molecule/app-i18n-i18next`, `@molecule/app-i18n-react-i18next` — must\nbehave identically; see the JSDoc on each `I18nProvider` method for the\nnormative text):\n\n- `setLocale()` THROWS for an unregistered locale — never silently\n  degrades to fallback text while `getLocale()` reports the bad code.\n- `t()` prefers the plural-suffixed key (`key_one`/`key_other`/…) over the\n  base `key` whenever `options.count` is provided (matches i18next).\n- `addTranslations()` deep-merges nested translation objects.\n- `exists(key)` follows the same locale-fallback chain as `t()` (active\n  locale, then English), so it agrees with whether `t(key)` renders real\n  translated text.\n\n## Translations\n\nTranslation strings are provided by `@molecule/app-locales-i18n`.\n","readmeFilename":"README.md"}