@chipmobilesdk/rn-i18n 0.1.2 → 0.1.3

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.
package/CHANGELOG.md CHANGED
@@ -1,57 +1,70 @@
1
- # Changelog
2
-
3
- All notable changes to `@chipmobilesdk/rn-i18n` are documented here. This project
4
- follows [Semantic Versioning](https://semver.org/).
5
-
6
- ## 0.1.2 — 2026-11-01
7
-
8
- ### Fixed
9
-
10
- - **A throwing locale listener no longer breaks a locale switch.** Listeners
11
- were notified without isolation, so one that threw skipped every listener
12
- after it (React subscribers included) and made `setLocale` reject after the
13
- switch but before the choice was persisted: the app showed one locale and
14
- stored another. Each listener is now isolated and reported with
15
- `console.warn`, and a listener unsubscribed by an earlier one during the
16
- same change is no longer called. No code change is required.
17
-
18
- ## 0.1.0 — 2026-06-13
19
-
20
- Initial release of the reusable mobile i18n and locale library.
21
-
22
- ### Added
23
-
24
- - `createI18n(config)` with eager configuration validation: duplicate locales,
25
- unknown/duplicate/cyclic fallback chains, missing fallback resources, and
26
- missing storage adapter are rejected before any localized content is served.
27
- - Translation lookup with namespaced nested keys, ordered fallback-chain
28
- resolution, `{{name}}` interpolation, package-owned pluralization, and a
29
- deterministic missing-key policy (one console warning per failed lookup path).
30
- - Runtime locale switching without restart, persisted through an app-injected
31
- `LocaleStorageAdapter`, with recovery from unsupported persisted values.
32
- - Device-locale initialization via an injectable provider (default backed by
33
- Hermes `Intl`), with exact-match and single-language-match selection.
34
- - Locale-aware formatting: `formatNumber`, `formatDate`, `formatCurrency` (via
35
- Hermes `Intl`), and package-owned `formatUnit`/`convertUnit` across length,
36
- mass, volume, and temperature dimensions.
37
- - Declared runtime-only translation entries that resolve through the same
38
- pipeline and are protected during synchronization.
39
- - React bindings: `I18nProvider`, `useTranslation`, `useLocale`.
40
- - Node-only extraction/synchronization CLI (`@chipmobilesdk/rn-i18n/tooling`):
41
- regex-pattern source scanning, locale JSON create/add/preserve/remove,
42
- ambiguous-usage and conflict reporting, malformed-file isolation, typed key
43
- contract generation, and deterministic output.
44
-
45
- ### Adoption notes
46
-
47
- - App-owned translations live in the consuming app (commonly
48
- `src/i18n/locales/<locale>.json`), never inside this package.
49
- - Host frameworks (`react`, `react-native`) are peer dependencies; this package
50
- has zero runtime dependencies.
51
- - No native modules, permissions, network calls, analytics, or tracking are
52
- introduced; the locale preference stays on-device via the injected adapter.
53
-
54
- ### Compatibility
55
-
56
- React `19.x`, React Native `0.85+` (Hermes), TypeScript `5.8+`, Node `>=22.11.0`
57
- for tooling. Android and iOS, with no native code.
1
+ # @chipmobilesdk/rn-i18n
2
+
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Packaging only; no code change. The package no longer declares `engines.node`: a
8
+ React Native package does not run on Node, so the field only produced install
9
+ warnings in apps on another Node version. rn-i18n's command-line tooling does run
10
+ on Node; its README's compatibility table states the version it needs. ([#14](https://github.com/operationd8979/chip-mobile-sdk/pull/14))
11
+
12
+ ## 0.1.2 — 2026-11-01
13
+
14
+ ### Fixed
15
+
16
+ - **A throwing locale listener no longer breaks a locale switch.** Listeners
17
+ were notified without isolation, so one that threw skipped every listener
18
+ after it (React subscribers included) and made `setLocale` reject after the
19
+ switch but before the choice was persisted: the app showed one locale and
20
+ stored another. Each listener is now isolated and reported with
21
+ `console.warn`, and a listener unsubscribed by an earlier one during the
22
+ same change is no longer called. No code change is required.
23
+
24
+ ## 0.1.1 — 2026-08-01
25
+
26
+ ### Changed
27
+
28
+ - The package now ships its agent skill (`skills/`), which consuming apps
29
+ copy in with `scripts/sync-sdk-skills.mjs`. No code change.
30
+
31
+ ## 0.1.0 — 2026-06-13
32
+
33
+ Initial release of the reusable mobile i18n and locale library.
34
+
35
+ ### Added
36
+
37
+ - `createI18n(config)` with eager configuration validation: duplicate locales,
38
+ unknown/duplicate/cyclic fallback chains, missing fallback resources, and
39
+ missing storage adapter are rejected before any localized content is served.
40
+ - Translation lookup with namespaced nested keys, ordered fallback-chain
41
+ resolution, `{{name}}` interpolation, package-owned pluralization, and a
42
+ deterministic missing-key policy (one console warning per failed lookup path).
43
+ - Runtime locale switching without restart, persisted through an app-injected
44
+ `LocaleStorageAdapter`, with recovery from unsupported persisted values.
45
+ - Device-locale initialization via an injectable provider (default backed by
46
+ Hermes `Intl`), with exact-match and single-language-match selection.
47
+ - Locale-aware formatting: `formatNumber`, `formatDate`, `formatCurrency` (via
48
+ Hermes `Intl`), and package-owned `formatUnit`/`convertUnit` across length,
49
+ mass, volume, and temperature dimensions.
50
+ - Declared runtime-only translation entries that resolve through the same
51
+ pipeline and are protected during synchronization.
52
+ - React bindings: `I18nProvider`, `useTranslation`, `useLocale`.
53
+ - Node-only extraction/synchronization CLI (`@chipmobilesdk/rn-i18n/tooling`):
54
+ regex-pattern source scanning, locale JSON create/add/preserve/remove,
55
+ ambiguous-usage and conflict reporting, malformed-file isolation, typed key
56
+ contract generation, and deterministic output.
57
+
58
+ ### Adoption notes
59
+
60
+ - App-owned translations live in the consuming app (commonly
61
+ `src/i18n/locales/<locale>.json`), never inside this package.
62
+ - Host frameworks (`react`, `react-native`) are peer dependencies; this package
63
+ has zero runtime dependencies.
64
+ - No native modules, permissions, network calls, analytics, or tracking are
65
+ introduced; the locale preference stays on-device via the injected adapter.
66
+
67
+ ### Compatibility
68
+
69
+ React `19.x`, React Native `0.85+` (Hermes), TypeScript `5.8+`, Node `>=22.11.0`
70
+ for tooling. Android and iOS, with no native code.
package/README.md CHANGED
@@ -1,218 +1,218 @@
1
- # @chipmobilesdk/rn-i18n
2
-
3
- Reusable localization for ChipMobileSdk-family React Native apps: per-locale
4
- configuration, app-owned namespaced translations, typed keys, interpolation,
5
- pluralization, fallback chains, runtime locale switching with app-injected
6
- persistence, device-locale detection, locale-aware number/date/currency/unit
7
- formatting, and a Node CLI that extracts and synchronizes translation keys.
8
-
9
- > **License:** `UNLICENSED`. Published publicly for use by the owner's
10
- > applications; no open-source license is granted.
11
-
12
- - **Zero runtime dependencies.** Formatting uses the `Intl` APIs built into
13
- Hermes; plural rules and unit conversion are package-owned.
14
- - **No native code.** Storage and (optionally) device-locale detection are
15
- injected by the consuming app.
16
- - **Public package.** Published under the `@chipmobilesdk` npm organization.
17
- - **Version**: `0.1.2`
18
- - **Entrypoints**: `@chipmobilesdk/rn-i18n` and the Node-only
19
- `@chipmobilesdk/rn-i18n/tooling`
20
-
21
- ## Installation
22
-
23
- This package ships TypeScript sources and is consumed through the workspace or
24
- the public npm registry:
25
-
26
- ```bash
27
- npm install @chipmobilesdk/rn-i18n
28
- ```
29
-
30
- Host frameworks are peer dependencies:
31
-
32
- ```jsonc
33
- // peerDependencies
34
- "react": ">=19",
35
- "react-native": ">=0.85"
36
- ```
37
-
38
- ## Quick start
39
-
40
- ```ts
41
- import { createI18n } from '@chipmobilesdk/rn-i18n';
42
- import enUS from './locales/en-US.json';
43
- import viVN from './locales/vi-VN.json';
44
- import { storageAdapter } from './storage';
45
- import type { AppI18nKey } from './keys.generated';
46
-
47
- export const i18n = createI18n<AppI18nKey>({
48
- primaryLocale: 'en-US',
49
- locales: [
50
- {
51
- code: 'en-US',
52
- displayName: 'English (US)',
53
- direction: 'ltr',
54
- fallbackChain: [],
55
- defaultCurrency: 'USD',
56
- defaultUnit: 'mile',
57
- defaultDateFormat: { year: 'numeric', month: 'short', day: 'numeric' },
58
- defaultNumberFormat: { maximumFractionDigits: 2 },
59
- },
60
- {
61
- code: 'vi-VN',
62
- displayName: 'Tiếng Việt',
63
- direction: 'ltr',
64
- fallbackChain: ['en-US'],
65
- defaultCurrency: 'VND',
66
- defaultUnit: 'kilometer',
67
- defaultDateFormat: { day: '2-digit', month: '2-digit', year: 'numeric' },
68
- defaultNumberFormat: { maximumFractionDigits: 2 },
69
- },
70
- ],
71
- resources: { 'en-US': enUS, 'vi-VN': viVN },
72
- storage: storageAdapter,
73
- });
74
-
75
- await i18n.init(); // persisted -> device -> primary fallback
76
- ```
77
-
78
- Wrap the app and use the hooks:
79
-
80
- ```tsx
81
- import { I18nProvider, useTranslation, useLocale } from '@chipmobilesdk/rn-i18n';
82
-
83
- <I18nProvider i18n={i18n}>
84
- <App />
85
- </I18nProvider>;
86
-
87
- const { t } = useTranslation<AppI18nKey>();
88
- const { locale, direction, supportedLocales, setLocale } = useLocale();
89
-
90
- t('welcome.message', { name: 'Dung' });
91
- t('cart.items', { count }, { count }); // plural variant by count
92
- ```
93
-
94
- ## Public API
95
-
96
- | Capability | Member |
97
- |------------|--------|
98
- | Setup + validation | `createI18n(config)`, `I18nConfigError` |
99
- | Translation lookup | `i18n.t`, `useTranslation` |
100
- | Locale state + switching | `i18n.getLocale`, `i18n.setLocale`, `useLocale` |
101
- | Locale metadata (RTL/LTR, defaults) | `i18n.getSupportedLocales` |
102
- | Device-locale init | `i18n.init`, `defaultDeviceLocaleProvider` |
103
- | Formatting | `i18n.formatNumber/formatDate/formatCurrency/formatUnit` |
104
- | Unit conversion | `i18n.convertUnit`, `convertUnit`, `getUnitSymbol`, `SUPPORTED_UNITS`, `UnitConversionError` |
105
- | Runtime-only declarations | `i18n.registerRuntimeEntries` |
106
- | Persistence key | `LOCALE_STORAGE_KEY` |
107
- | React | `I18nProvider` |
108
-
109
- Public contract types also include the configuration, locale, translation,
110
- formatting, unit, provider/adapter, and hook result types exported by
111
- `src/index.ts`. Do not deep-import package internals. Node-only extraction and
112
- synchronization APIs must be imported from `@chipmobilesdk/rn-i18n/tooling`.
113
-
114
- ### Storage adapter (required for persistence)
115
-
116
- The package never bundles a storage backend (FR-046/FR-047). Inject one:
117
-
118
- ```ts
119
- import type { LocaleStorageAdapter } from '@chipmobilesdk/rn-i18n';
120
- import AsyncStorage from '@react-native-async-storage/async-storage';
121
-
122
- export const storageAdapter: LocaleStorageAdapter = {
123
- getItem: key => AsyncStorage.getItem(key),
124
- setItem: (key, value) => AsyncStorage.setItem(key, value),
125
- removeItem: key => AsyncStorage.removeItem(key),
126
- };
127
- ```
128
-
129
- An MMKV-backed adapter (wrapping its synchronous API in `Promise.resolve`)
130
- works equally well. If persistence is enabled without an adapter, `createI18n`
131
- throws before any localized content is served.
132
-
133
- ### Device-locale provider (optional)
134
-
135
- The default reads `Intl.DateTimeFormat().resolvedOptions().locale`. Apps that
136
- need richer detection can inject `deviceLocale` (e.g. backed by
137
- `react-native-localize`); that dependency stays in the app, never the package.
138
-
139
- ## Translations
140
-
141
- App-owned JSON, organized by locale and namespace; nested dot-path keys,
142
- `{{name}}` interpolation, and plural groups:
143
-
144
- ```jsonc
145
- {
146
- "welcome": { "message": "Welcome, {{name}}!" },
147
- "cart": { "items": { "one": "{{count}} item", "other": "{{count}} items" } }
148
- }
149
- ```
150
-
151
- Plural categories deliberately come from package-owned rules rather than
152
- depending on `Intl.PluralRules`, which keeps behavior consistent across the
153
- supported Hermes environments. Built-ins cover `en` (one/other), `vi`/`ja`
154
- (other-only), and a default `count === 1 -> one` rule; supply
155
- `LocaleConfig.pluralRule` for richer languages.
156
-
157
- ## Extraction & synchronization CLI
158
-
159
- Node-only, exposed via `@chipmobilesdk/rn-i18n/tooling` and a CLI. Add an app
160
- config (`i18n-sync.config.json`) and a script:
161
-
162
- ```jsonc
163
- {
164
- "sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"],
165
- "patterns": [{ "regex": "[^\\w.]t\\(\\s*'(?<key>[A-Za-z0-9_.-]+)'" }],
166
- "localesDir": "src/i18n/locales",
167
- "locales": ["en-US", "vi-VN", "ja-JP"],
168
- "runtimeEntriesFile": "src/i18n/runtime-keys.ts",
169
- "generateKeysFile": "src/i18n/keys.generated.ts",
170
- "keysTypeName": "AppI18nKey"
171
- }
172
- ```
173
-
174
- ```jsonc
175
- // package.json
176
- "scripts": {
177
- "i18n:sync": "node --experimental-strip-types node_modules/@chipmobilesdk/rn-i18n/src/tooling/cli.ts --config i18n-sync.config.json"
178
- }
179
- ```
180
-
181
- Running it scans the source tree, then per locale: creates missing files, adds
182
- missing keys (with the placeholder policy), preserves existing translations,
183
- removes stale keys, protects declared runtime-only keys, regenerates the typed
184
- key contract, and prints a report. Exit codes: `0` success/warnings, `1`
185
- blocking findings (conflicts, malformed locale files), `2` invalid config.
186
-
187
- Bind `t()` to the generated `AppI18nKey` so `tsc --noEmit` reports invalid or
188
- stale key usage before release.
189
-
190
- ### Runtime-only keys
191
-
192
- Keys resolved only at runtime (declared in `runtimeEntriesFile`) use the same
193
- lookup, fallback, interpolation, and missing-key behavior, and are never
194
- removed by synchronization. Policy `add` writes the default text to missing
195
- locales; `report` only lists them.
196
-
197
- ## Compatibility
198
-
199
- | Surface | Baseline |
200
- |---------|----------|
201
- | React / React Native | `19.x` / `0.85+` (Hermes) |
202
- | TypeScript | `5.8+` |
203
- | Node (tooling) | `>=22.11.0` |
204
- | Platforms | Android, iOS (no native code) |
205
-
206
- ## Validation
207
-
208
- ```bash
209
- npm test # package + demo suites
210
- npm run typecheck:i18n # tsc -p packages/rn-i18n/tsconfig.json --noEmit
211
- npm run pack:i18n # npm pack --workspace @chipmobilesdk/rn-i18n --dry-run
212
- npm run i18n:sync # extraction + synchronization
213
- ```
214
-
215
- The demo integration lives in `src/screens/I18nDemoScreen.tsx` and uses
216
- `src/i18n/` for setup, translations, persistence, and generated keys.
217
-
218
- See [CHANGELOG.md](CHANGELOG.md) for release notes and adoption guidance.
1
+ # @chipmobilesdk/rn-i18n
2
+
3
+ Reusable localization for ChipMobileSdk-family React Native apps: per-locale
4
+ configuration, app-owned namespaced translations, typed keys, interpolation,
5
+ pluralization, fallback chains, runtime locale switching with app-injected
6
+ persistence, device-locale detection, locale-aware number/date/currency/unit
7
+ formatting, and a Node CLI that extracts and synchronizes translation keys.
8
+
9
+ > **License:** `UNLICENSED`. Published publicly for use by the owner's
10
+ > applications; no open-source license is granted.
11
+
12
+ - **Zero runtime dependencies.** Formatting uses the `Intl` APIs built into
13
+ Hermes; plural rules and unit conversion are package-owned.
14
+ - **No native code.** Storage and (optionally) device-locale detection are
15
+ injected by the consuming app.
16
+ - **Public package.** Published under the `@chipmobilesdk` npm organization.
17
+ - **Version**: see [CHANGELOG](CHANGELOG.md)
18
+ - **Entrypoints**: `@chipmobilesdk/rn-i18n` and the Node-only
19
+ `@chipmobilesdk/rn-i18n/tooling`
20
+
21
+ ## Installation
22
+
23
+ This package ships TypeScript sources and is consumed through the workspace or
24
+ the public npm registry:
25
+
26
+ ```bash
27
+ npm install @chipmobilesdk/rn-i18n
28
+ ```
29
+
30
+ Host frameworks are peer dependencies:
31
+
32
+ ```jsonc
33
+ // peerDependencies
34
+ "react": ">=19",
35
+ "react-native": ">=0.85"
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```ts
41
+ import { createI18n } from '@chipmobilesdk/rn-i18n';
42
+ import enUS from './locales/en-US.json';
43
+ import viVN from './locales/vi-VN.json';
44
+ import { storageAdapter } from './storage';
45
+ import type { AppI18nKey } from './keys.generated';
46
+
47
+ export const i18n = createI18n<AppI18nKey>({
48
+ primaryLocale: 'en-US',
49
+ locales: [
50
+ {
51
+ code: 'en-US',
52
+ displayName: 'English (US)',
53
+ direction: 'ltr',
54
+ fallbackChain: [],
55
+ defaultCurrency: 'USD',
56
+ defaultUnit: 'mile',
57
+ defaultDateFormat: { year: 'numeric', month: 'short', day: 'numeric' },
58
+ defaultNumberFormat: { maximumFractionDigits: 2 },
59
+ },
60
+ {
61
+ code: 'vi-VN',
62
+ displayName: 'Tiếng Việt',
63
+ direction: 'ltr',
64
+ fallbackChain: ['en-US'],
65
+ defaultCurrency: 'VND',
66
+ defaultUnit: 'kilometer',
67
+ defaultDateFormat: { day: '2-digit', month: '2-digit', year: 'numeric' },
68
+ defaultNumberFormat: { maximumFractionDigits: 2 },
69
+ },
70
+ ],
71
+ resources: { 'en-US': enUS, 'vi-VN': viVN },
72
+ storage: storageAdapter,
73
+ });
74
+
75
+ await i18n.init(); // persisted -> device -> primary fallback
76
+ ```
77
+
78
+ Wrap the app and use the hooks:
79
+
80
+ ```tsx
81
+ import { I18nProvider, useTranslation, useLocale } from '@chipmobilesdk/rn-i18n';
82
+
83
+ <I18nProvider i18n={i18n}>
84
+ <App />
85
+ </I18nProvider>;
86
+
87
+ const { t } = useTranslation<AppI18nKey>();
88
+ const { locale, direction, supportedLocales, setLocale } = useLocale();
89
+
90
+ t('welcome.message', { name: 'Dung' });
91
+ t('cart.items', { count }, { count }); // plural variant by count
92
+ ```
93
+
94
+ ## Public API
95
+
96
+ | Capability | Member |
97
+ |------------|--------|
98
+ | Setup + validation | `createI18n(config)`, `I18nConfigError` |
99
+ | Translation lookup | `i18n.t`, `useTranslation` |
100
+ | Locale state + switching | `i18n.getLocale`, `i18n.setLocale`, `useLocale` |
101
+ | Locale metadata (RTL/LTR, defaults) | `i18n.getSupportedLocales` |
102
+ | Device-locale init | `i18n.init`, `defaultDeviceLocaleProvider` |
103
+ | Formatting | `i18n.formatNumber/formatDate/formatCurrency/formatUnit` |
104
+ | Unit conversion | `i18n.convertUnit`, `convertUnit`, `getUnitSymbol`, `SUPPORTED_UNITS`, `UnitConversionError` |
105
+ | Runtime-only declarations | `i18n.registerRuntimeEntries` |
106
+ | Persistence key | `LOCALE_STORAGE_KEY` |
107
+ | React | `I18nProvider` |
108
+
109
+ Public contract types also include the configuration, locale, translation,
110
+ formatting, unit, provider/adapter, and hook result types exported by
111
+ `src/index.ts`. Do not deep-import package internals. Node-only extraction and
112
+ synchronization APIs must be imported from `@chipmobilesdk/rn-i18n/tooling`.
113
+
114
+ ### Storage adapter (required for persistence)
115
+
116
+ The package never bundles a storage backend (FR-046/FR-047). Inject one:
117
+
118
+ ```ts
119
+ import type { LocaleStorageAdapter } from '@chipmobilesdk/rn-i18n';
120
+ import AsyncStorage from '@react-native-async-storage/async-storage';
121
+
122
+ export const storageAdapter: LocaleStorageAdapter = {
123
+ getItem: key => AsyncStorage.getItem(key),
124
+ setItem: (key, value) => AsyncStorage.setItem(key, value),
125
+ removeItem: key => AsyncStorage.removeItem(key),
126
+ };
127
+ ```
128
+
129
+ An MMKV-backed adapter (wrapping its synchronous API in `Promise.resolve`)
130
+ works equally well. If persistence is enabled without an adapter, `createI18n`
131
+ throws before any localized content is served.
132
+
133
+ ### Device-locale provider (optional)
134
+
135
+ The default reads `Intl.DateTimeFormat().resolvedOptions().locale`. Apps that
136
+ need richer detection can inject `deviceLocale` (e.g. backed by
137
+ `react-native-localize`); that dependency stays in the app, never the package.
138
+
139
+ ## Translations
140
+
141
+ App-owned JSON, organized by locale and namespace; nested dot-path keys,
142
+ `{{name}}` interpolation, and plural groups:
143
+
144
+ ```jsonc
145
+ {
146
+ "welcome": { "message": "Welcome, {{name}}!" },
147
+ "cart": { "items": { "one": "{{count}} item", "other": "{{count}} items" } }
148
+ }
149
+ ```
150
+
151
+ Plural categories deliberately come from package-owned rules rather than
152
+ depending on `Intl.PluralRules`, which keeps behavior consistent across the
153
+ supported Hermes environments. Built-ins cover `en` (one/other), `vi`/`ja`
154
+ (other-only), and a default `count === 1 -> one` rule; supply
155
+ `LocaleConfig.pluralRule` for richer languages.
156
+
157
+ ## Extraction & synchronization CLI
158
+
159
+ Node-only, exposed via `@chipmobilesdk/rn-i18n/tooling` and a CLI. Add an app
160
+ config (`i18n-sync.config.json`) and a script:
161
+
162
+ ```jsonc
163
+ {
164
+ "sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"],
165
+ "patterns": [{ "regex": "[^\\w.]t\\(\\s*'(?<key>[A-Za-z0-9_.-]+)'" }],
166
+ "localesDir": "src/i18n/locales",
167
+ "locales": ["en-US", "vi-VN", "ja-JP"],
168
+ "runtimeEntriesFile": "src/i18n/runtime-keys.ts",
169
+ "generateKeysFile": "src/i18n/keys.generated.ts",
170
+ "keysTypeName": "AppI18nKey"
171
+ }
172
+ ```
173
+
174
+ ```jsonc
175
+ // package.json
176
+ "scripts": {
177
+ "i18n:sync": "node --experimental-strip-types node_modules/@chipmobilesdk/rn-i18n/src/tooling/cli.ts --config i18n-sync.config.json"
178
+ }
179
+ ```
180
+
181
+ Running it scans the source tree, then per locale: creates missing files, adds
182
+ missing keys (with the placeholder policy), preserves existing translations,
183
+ removes stale keys, protects declared runtime-only keys, regenerates the typed
184
+ key contract, and prints a report. Exit codes: `0` success/warnings, `1`
185
+ blocking findings (conflicts, malformed locale files), `2` invalid config.
186
+
187
+ Bind `t()` to the generated `AppI18nKey` so `tsc --noEmit` reports invalid or
188
+ stale key usage before release.
189
+
190
+ ### Runtime-only keys
191
+
192
+ Keys resolved only at runtime (declared in `runtimeEntriesFile`) use the same
193
+ lookup, fallback, interpolation, and missing-key behavior, and are never
194
+ removed by synchronization. Policy `add` writes the default text to missing
195
+ locales; `report` only lists them.
196
+
197
+ ## Compatibility
198
+
199
+ | Surface | Baseline |
200
+ |---------|----------|
201
+ | React / React Native | `19.x` / `0.85+` (Hermes) |
202
+ | TypeScript | `5.8+` |
203
+ | Node (tooling) | `>=22.11.0` |
204
+ | Platforms | Android, iOS (no native code) |
205
+
206
+ ## Validation
207
+
208
+ ```bash
209
+ npm test # package + demo suites
210
+ npm run typecheck --workspace @chipmobilesdk/rn-i18n # tsc -p packages/rn-i18n/tsconfig.json --noEmit
211
+ npm run pack:check --workspace @chipmobilesdk/rn-i18n # npm pack --workspace @chipmobilesdk/rn-i18n --dry-run
212
+ npm run i18n:sync # extraction + synchronization
213
+ ```
214
+
215
+ The demo integration lives in `src/screens/I18nDemoScreen.tsx` and uses
216
+ `src/i18n/` for setup, translations, persistence, and generated keys.
217
+
218
+ See [CHANGELOG.md](CHANGELOG.md) for release notes and adoption guidance.