@molecule/app-i18n-react-i18next 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +507 -0
  2. package/package.json +7 -6
package/README.md ADDED
@@ -0,0 +1,507 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:51:01.633Z
7
+ -->
8
+
9
+ # @molecule/app-i18n-react-i18next
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ react-i18next provider for molecule.dev.
16
+
17
+ Implements the I18nProvider interface using i18next and react-i18next.
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import { setProvider } from '@molecule/app-i18n'
23
+ import { createReactI18nextProvider } from '@molecule/app-i18n-react-i18next'
24
+
25
+ const provider = createReactI18nextProvider({
26
+ defaultLocale: 'en',
27
+ locales: [
28
+ { code: 'en', name: 'English', translations: { ... } },
29
+ { code: 'fr', name: 'French', translations: { ... } },
30
+ ],
31
+ })
32
+
33
+ setProvider(provider)
34
+ ```
35
+
36
+ ## Type
37
+
38
+ `provider`
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install @molecule/app-i18n-react-i18next @molecule/app-i18n @molecule/app-i18n-i18next i18next react react-i18next
44
+ npm install -D @types/react
45
+ ```
46
+
47
+ ## API
48
+
49
+ ### Interfaces
50
+
51
+ #### `DateFormatOptions`
52
+
53
+ Date format options.
54
+
55
+ ```typescript
56
+ interface DateFormatOptions {
57
+ /**
58
+ * Date style.
59
+ */
60
+ dateStyle?: 'full' | 'long' | 'medium' | 'short'
61
+ /**
62
+ * Time style.
63
+ */
64
+ timeStyle?: 'full' | 'long' | 'medium' | 'short'
65
+ /**
66
+ * Custom format string (implementation-specific).
67
+ */
68
+ format?: string
69
+ /**
70
+ * Relative time.
71
+ */
72
+ relative?: boolean
73
+ }
74
+ ```
75
+
76
+ #### `I18nextProviderConfig`
77
+
78
+ Configuration options for the i18next provider.
79
+
80
+ ```typescript
81
+ interface I18nextProviderConfig {
82
+ /**
83
+ * Default locale code.
84
+ */
85
+ defaultLocale?: string
86
+ /**
87
+ * Fallback locale code.
88
+ */
89
+ fallbackLocale?: string
90
+ /**
91
+ * Available locales with translations.
92
+ */
93
+ locales?: LocaleConfig[]
94
+ /**
95
+ * Enable language detection.
96
+ */
97
+ detection?: boolean
98
+ /**
99
+ * Language detection options.
100
+ */
101
+ detectionOptions?: {
102
+ order?: (
103
+ 'querystring' | 'cookie' | 'localStorage' | 'sessionStorage' | 'navigator' | 'htmlTag'
104
+ )[]
105
+ lookupQuerystring?: string
106
+ lookupCookie?: string
107
+ lookupLocalStorage?: string
108
+ lookupSessionStorage?: string
109
+ caches?: ('localStorage' | 'cookie')[]
110
+ }
111
+ /**
112
+ * Debug mode.
113
+ */
114
+ debug?: boolean
115
+ /**
116
+ * Custom i18next initialization options.
117
+ */
118
+ i18nextOptions?: Partial<InitOptions>
119
+ /**
120
+ * i18next plugins to apply before initialization.
121
+ *
122
+ * Each plugin is passed to `i18n.use()` before `i18n.init()`.
123
+ * Useful for framework integrations (e.g. react-i18next's `initReactI18next`).
124
+ */
125
+ plugins?: unknown[]
126
+ }
127
+ ```
128
+
129
+ #### `I18nProvider`
130
+
131
+ i18n provider interface.
132
+
133
+ All i18n providers must implement this interface.
134
+
135
+ ```typescript
136
+ interface I18nProvider {
137
+ /**
138
+ * Gets the current locale.
139
+ */
140
+ getLocale(): string
141
+ /**
142
+ * Sets the current locale.
143
+ *
144
+ * **Fleet contract:** every conformant provider (the core simple provider,
145
+ * `@molecule/api-i18n-simple`, `@molecule/app-i18n-i18next`, and
146
+ * `@molecule/app-i18n-react-i18next`) MUST throw `Error('Locale "<code>"
147
+ * not found')` when `locale` is not registered — via the constructor's
148
+ * `initialLocales`/`locales` config, `addLocale()`, or `addTranslations()`
149
+ * (all three register a locale). It must NOT silently degrade to
150
+ * fallback-locale text while `getLocale()` reports the unregistered code —
151
+ * that divergence makes a misconfigured locale switch indistinguishable
152
+ * from a working one until a user notices the wrong language on screen.
153
+ */
154
+ setLocale(locale: string): Promise<void>
155
+ /**
156
+ * Gets all available locales.
157
+ */
158
+ getLocales(): LocaleConfig[]
159
+ /**
160
+ * Adds a locale.
161
+ */
162
+ addLocale(config: LocaleConfig): void
163
+ /**
164
+ * Removes a locale by code, notifying subscribers so language pickers
165
+ * built on `onLocaleChange` re-render their list. If the removed locale
166
+ * is currently active, the caller is responsible for switching to a
167
+ * fallback (e.g. `'en'`) BEFORE calling this — the provider will not
168
+ * auto-fall-back on its own.
169
+ *
170
+ * Returns `true` if the locale was registered and removed, `false`
171
+ * otherwise.
172
+ */
173
+ removeLocale(code: string): boolean
174
+ /**
175
+ * Adds translations to a locale. Auto-creates the locale if it doesn't exist.
176
+ *
177
+ * **Fleet contract:** merges are DEEP, not a shallow spread — registering
178
+ * two calls (e.g. two modules) that share a top-level namespace key merges
179
+ * their subtrees instead of the second call clobbering the first's nested
180
+ * translations wholesale. `@molecule/api-i18n-simple` implements the same
181
+ * contract on the API side.
182
+ */
183
+ addTranslations(locale: string, translations: Translations, namespace?: string): void
184
+ /**
185
+ * Translates a key with optional interpolation values and pluralization.
186
+ *
187
+ * **Fleet plural contract (matches i18next's own key resolution order):**
188
+ * when `options.count` is provided, the plural-suffixed key
189
+ * (`` `${key}_${pluralForm}` ``, e.g. `item_one`/`item_few`/…, falling back
190
+ * to `` `${key}_other` ``) is looked up FIRST and wins over the base `key`
191
+ * if BOTH are registered. Only when no plural-suffixed key exists at all
192
+ * does resolution fall back to the base key. A catalog that ships both
193
+ * `item` and `item_one`/`item_other` therefore pluralizes identically
194
+ * whichever provider is bonded.
195
+ *
196
+ * @returns The translated string, or the default value / key if not found.
197
+ */
198
+ t(
199
+ key: string,
200
+ values?: InterpolationValues,
201
+ options?: {
202
+ defaultValue?: string
203
+ count?: number
204
+ },
205
+ ): string
206
+ /**
207
+ * Checks if a translation key exists.
208
+ *
209
+ * **Fleet contract:** follows the SAME locale-resolution chain as `t()` —
210
+ * the active locale, then the English fallback — so `exists(key) === true`
211
+ * whenever `t(key)` would render real translated text (not the raw key or
212
+ * an inline `defaultValue`). Do not narrow this to "only the active
213
+ * locale's own catalog"; that made `exists()` return `false` for keys `t()`
214
+ * happily rendered via the English fallback, and the answer differed by
215
+ * provider.
216
+ *
217
+ * @returns `true` if the key has a translation.
218
+ */
219
+ exists(key: string): boolean
220
+ /**
221
+ * Formats a number according to the current locale.
222
+ *
223
+ * @returns The locale-formatted number string.
224
+ */
225
+ formatNumber(value: number, options?: NumberFormatOptions): string
226
+ /**
227
+ * Formats a date according to the current locale.
228
+ *
229
+ * @returns The locale-formatted date string.
230
+ */
231
+ formatDate(value: Date | number | string, options?: DateFormatOptions): string
232
+ /**
233
+ * Formats a relative time (e.g. "2 hours ago").
234
+ *
235
+ * @returns The locale-formatted relative time string.
236
+ */
237
+ formatRelativeTime(
238
+ value: Date | number,
239
+ options?: {
240
+ unit?: Intl.RelativeTimeFormatUnit
241
+ },
242
+ ): string
243
+ /**
244
+ * Formats a list (e.g. "A, B, and C").
245
+ *
246
+ * @returns The locale-formatted list string.
247
+ */
248
+ formatList(
249
+ values: string[],
250
+ options?: {
251
+ type?: 'conjunction' | 'disjunction' | 'unit'
252
+ },
253
+ ): string
254
+ /**
255
+ * Subscribes to locale changes.
256
+ *
257
+ * @returns An unsubscribe function.
258
+ */
259
+ onLocaleChange(listener: (locale: string) => void): () => void
260
+ /**
261
+ * Gets the text direction for the current locale.
262
+ *
263
+ * @returns `'ltr'` or `'rtl'`.
264
+ */
265
+ getDirection(): 'ltr' | 'rtl'
266
+ /**
267
+ * Checks if a translation key exists (alias for exists).
268
+ */
269
+ hasKey?(key: string): boolean
270
+ /**
271
+ * Checks if the provider is ready.
272
+ */
273
+ isReady?(): boolean
274
+ /**
275
+ * Registers a callback for when the provider is ready.
276
+ */
277
+ onReady?(callback: () => void): () => void
278
+ /**
279
+ * Registers a lazily-loaded content module for automatic reload on locale changes.
280
+ * All registered content is reloaded during `setLocale()` before listeners fire,
281
+ * ensuring content is available on the first re-render with no flash.
282
+ *
283
+ * Idempotent: registering the same module name twice is a no-op.
284
+ */
285
+ registerContent?(module: string, loader: (locale: string) => Promise<void>): void
286
+ }
287
+ ```
288
+
289
+ #### `LocaleConfig`
290
+
291
+ Configuration for a supported locale (code, display name, text direction, translations or lazy loader).
292
+
293
+ ```typescript
294
+ interface LocaleConfig {
295
+ /**
296
+ * Locale code (e.g., 'en-US', 'fr-FR').
297
+ */
298
+ code: string
299
+ /**
300
+ * Display name (e.g., 'English (US)', 'Francais').
301
+ */
302
+ name: string
303
+ /**
304
+ * Native display name.
305
+ */
306
+ nativeName?: string
307
+ /**
308
+ * Text direction.
309
+ */
310
+ direction?: 'ltr' | 'rtl'
311
+ /**
312
+ * Translations for this locale.
313
+ */
314
+ translations?: Translations
315
+ /**
316
+ * Lazy loader for translations. Called on first setLocale() to this locale.
317
+ * When provided, translations can be omitted and will be loaded on demand.
318
+ */
319
+ loader?: () => Promise<Translations>
320
+ }
321
+ ```
322
+
323
+ #### `NumberFormatOptions`
324
+
325
+ Number format options.
326
+
327
+ ```typescript
328
+ interface NumberFormatOptions {
329
+ /**
330
+ * Number style.
331
+ */
332
+ style?: 'decimal' | 'currency' | 'percent' | 'unit'
333
+ /**
334
+ * Currency code (for currency style).
335
+ */
336
+ currency?: string
337
+ /**
338
+ * Minimum fraction digits.
339
+ */
340
+ minimumFractionDigits?: number
341
+ /**
342
+ * Maximum fraction digits.
343
+ */
344
+ maximumFractionDigits?: number
345
+ /**
346
+ * Use grouping separators.
347
+ */
348
+ useGrouping?: boolean
349
+ }
350
+ ```
351
+
352
+ #### `Translations`
353
+
354
+ Translation key/value map.
355
+
356
+ ```typescript
357
+ interface Translations {
358
+ [key: string]: string | Translations
359
+ }
360
+ ```
361
+
362
+ ### Types
363
+
364
+ #### `InterpolationValues`
365
+
366
+ Key-value map of interpolation variables passed to a translation string (e.g. `{ name: 'World' }`).
367
+
368
+ ```typescript
369
+ type InterpolationValues = Record<string, string | number | boolean | Date>
370
+ ```
371
+
372
+ #### `ReactI18nextProviderConfig`
373
+
374
+ Configuration options for the react-i18next provider.
375
+
376
+ Identical to I18nextProviderConfig — the React-specific setup
377
+ (initReactI18next plugin, Suspense) is handled automatically.
378
+
379
+ ```typescript
380
+ type ReactI18nextProviderConfig = I18nextProviderConfig
381
+ ```
382
+
383
+ ### Functions
384
+
385
+ #### `createReactI18nextProvider(config)`
386
+
387
+ Creates a React-specific i18n provider that wraps the base i18next provider with the
388
+ `react-i18next` plugin. Enables `useSuspense` by default for React Suspense integration.
389
+
390
+ ```typescript
391
+ function createReactI18nextProvider(
392
+ config?: I18nextProviderConfig,
393
+ ): I18nProvider & { i18n: i18n; initialize: () => Promise<void> }
394
+ ```
395
+
396
+ - `config` — Same as `I18nextProviderConfig` plus optional React-specific overrides.
397
+
398
+ **Returns:** An `I18nProvider` with the `react-i18next` plugin pre-registered.
399
+
400
+ #### `useI18n()`
401
+
402
+ React hook that provides translation, locale switching, and formatting functions
403
+ using `react-i18next` under the hood. Wraps `useTranslation()` into the molecule i18n interface.
404
+
405
+ ```typescript
406
+ function useI18n(): {
407
+ t: (key: string, values?: InterpolationValues) => string
408
+ locale: string
409
+ setLocale: (locale: string) => Promise<unknown>
410
+ formatNumber: (value: number, options?: NumberFormatOptions) => string
411
+ formatDate: (value: Date | number | string, options?: DateFormatOptions) => string
412
+ }
413
+ ```
414
+
415
+ **Returns:** An object with `t` (translate), `locale`, `setLocale`, `formatNumber`, and `formatDate`.
416
+
417
+ ### Constants
418
+
419
+ #### `I18nextProvider`
420
+
421
+ ```typescript
422
+ const I18nextProvider: React.FunctionComponent<I18nextProviderProps>
423
+ ```
424
+
425
+ #### `localeConfigToResources`
426
+
427
+ Converts an array of molecule `LocaleConfig` objects to the i18next resource bundle format.
428
+ Each locale's translations are placed under a `translation` namespace keyed by locale code.
429
+
430
+ ```typescript
431
+ const localeConfigToResources: (
432
+ locales: LocaleConfig[],
433
+ ) => Record<string, { translation: Translations }>
434
+ ```
435
+
436
+ #### `provider`
437
+
438
+ Default provider instance.
439
+
440
+ ```typescript
441
+ const provider: I18nProvider & { i18n: i18n; initialize: () => Promise<void> }
442
+ ```
443
+
444
+ #### `Trans`
445
+
446
+ ```typescript
447
+ const Trans: TransLegacy
448
+ ```
449
+
450
+ #### `useTranslation`
451
+
452
+ ```typescript
453
+ const useTranslation: UseTranslationLegacy
454
+ ```
455
+
456
+ ## Core Interface
457
+
458
+ Implements `@molecule/app-i18n` interface.
459
+
460
+ ## Bond Wiring
461
+
462
+ Setup function to register this provider with the core interface:
463
+
464
+ ```typescript
465
+ import { setProvider } from '@molecule/app-i18n'
466
+ import { provider } from '@molecule/app-i18n-react-i18next'
467
+
468
+ export function setupI18nReactI18next(): void {
469
+ setProvider(provider)
470
+ }
471
+ ```
472
+
473
+ ## Injection Notes
474
+
475
+ ### Requirements
476
+
477
+ Peer dependencies:
478
+
479
+ - `@molecule/app-i18n` ^1.0.1
480
+ - `react` ^18.0.0 || ^19.0.0
481
+
482
+ ### Runtime Dependencies
483
+
484
+ - `@molecule/app-i18n`
485
+ - `@molecule/app-i18n-i18next`
486
+ - `i18next`
487
+ - `react`
488
+ - `react-i18next`
489
+
490
+ **Startup locale vs. `detection`:** with `detection: true` (the default),
491
+ `defaultLocale` is only the FALLBACK — the actual startup locale is
492
+ whatever the browser detector resolves (querystring, then `navigator`, by
493
+ default). Apps that want a pinned startup locale must pass
494
+ `detection: false` (or an explicit `i18nextOptions.lng`).
495
+
496
+ **`setLocale()` contract:** `createReactI18nextProvider()` is a thin
497
+ wrapper over `@molecule/app-i18n-i18next`'s `createI18nextProvider()`, so
498
+ its `setLocale()` THROWS for an unregistered locale — see that package's
499
+ remarks for the fleet-wide contract. The separate `useI18n()` hook below,
500
+ however, calls `react-i18next`'s raw `i18n.changeLanguage()` directly and
501
+ is NOT an `I18nProvider` — it does not throw for an unregistered locale.
502
+
503
+ **React Suspense:** the provider sets `react.useSuspense: true` by default,
504
+ so components using `useTranslation()`/`Trans` may SUSPEND while i18next
505
+ initializes — wrap the app (or the i18n-using subtree) in a
506
+ `<Suspense fallback={…}>` boundary, or opt out with
507
+ `i18nextOptions: { react: { useSuspense: false } }`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-i18n-react-i18next",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "react-i18next provider for molecule.dev",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "README.md"
21
22
  ],
22
23
  "keywords": [
23
24
  "molecule",
@@ -28,7 +29,7 @@
28
29
  ],
29
30
  "license": "Apache-2.0",
30
31
  "dependencies": {
31
- "@molecule/app-i18n-i18next": "1.0.0",
32
+ "@molecule/app-i18n-i18next": "1.0.2",
32
33
  "i18next": "26.3.6",
33
34
  "react-i18next": "17.0.11"
34
35
  },
@@ -37,10 +38,10 @@
37
38
  "@types/react": "19.2.17",
38
39
  "react": "19.2.8",
39
40
  "typescript": "6.0.3",
40
- "vitest": "4.1.10"
41
+ "vitest": "4.1.11"
41
42
  },
42
43
  "peerDependencies": {
43
- "@molecule/app-i18n": "^1.0.0",
44
+ "@molecule/app-i18n": "^1.0.1",
44
45
  "react": "^18.0.0 || ^19.0.0"
45
46
  },
46
47
  "repository": {
@@ -48,7 +49,7 @@
48
49
  "url": "https://github.com/molecule-dev/molecule.git",
49
50
  "directory": "packages/app/bonds/i18n/react-i18next"
50
51
  },
51
- "homepage": "https://github.com/molecule-dev/molecule/tree/main/packages/app/bonds/i18n/react-i18next",
52
+ "homepage": "https://www.molecule.dev/packages/app-i18n-react-i18next",
52
53
  "bugs": "https://github.com/molecule-dev/molecule/issues",
53
54
  "publishConfig": {
54
55
  "access": "public"