@chipmobilesdk/rn-i18n 0.1.2 → 0.2.0
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 +168 -57
- package/README.md +277 -218
- package/package.json +49 -49
- package/skills/sdk-rn-i18n/SKILL.md +65 -62
- package/src/config/catalog.ts +25 -0
- package/src/config/diagnostics.ts +68 -0
- package/src/config/errors.ts +28 -0
- package/src/config/types.ts +150 -152
- package/src/config/validateConfig.ts +161 -174
- package/src/format/currency.ts +34 -27
- package/src/format/date.ts +32 -26
- package/src/format/invalid.ts +22 -16
- package/src/format/number.ts +30 -25
- package/src/format/units.ts +139 -151
- package/src/i18n.ts +231 -222
- package/src/index.ts +58 -50
- package/src/locale/activeLocale.ts +42 -45
- package/src/locale/deviceLocale.ts +50 -54
- package/src/locale/keyValueStorage.ts +27 -0
- package/src/locale/persistence.ts +59 -37
- package/src/react/I18nProvider.tsx +31 -35
- package/src/react/hooks.ts +66 -61
- package/src/tooling/cli.ts +76 -78
- package/src/tooling/generateKeys.ts +62 -68
- package/src/tooling/index.ts +27 -32
- package/src/tooling/report.ts +109 -117
- package/src/tooling/runner.ts +111 -126
- package/src/tooling/scan.ts +193 -210
- package/src/tooling/sync.ts +193 -198
- package/src/tooling/types.ts +119 -119
- package/src/translation/interpolate.ts +23 -23
- package/src/translation/plurals.ts +62 -69
- package/src/translation/resolver.ts +161 -146
- package/src/translation/runtimeKeys.ts +27 -27
package/src/i18n.ts
CHANGED
|
@@ -1,222 +1,231 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from './translation/runtimeKeys';
|
|
25
|
-
import { formatCurrencyValue } from './format/currency';
|
|
26
|
-
import { formatDateValue } from './format/date';
|
|
27
|
-
import { formatNumberValue } from './format/number';
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
): string;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
source: 'primaryFallback',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const resolverContext: ResolverContext = {
|
|
85
|
-
localesByCode,
|
|
86
|
-
resources: config.resources,
|
|
87
|
-
runtimeEntries,
|
|
88
|
-
missingTranslation,
|
|
89
|
-
warned: new Set(),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
1
|
+
import { createReporter } from './config/diagnostics';
|
|
2
|
+
import type { ResolvedInvalidFormatPolicy } from './format/invalid';
|
|
3
|
+
import type {
|
|
4
|
+
ActiveLocalePreference,
|
|
5
|
+
I18nConfig,
|
|
6
|
+
InvalidFormatPolicy,
|
|
7
|
+
LocaleConfig,
|
|
8
|
+
MissingTranslationPolicy,
|
|
9
|
+
RuntimeTranslationEntry,
|
|
10
|
+
TranslateOptions,
|
|
11
|
+
TranslateVars,
|
|
12
|
+
UnitId,
|
|
13
|
+
} from './config/types';
|
|
14
|
+
import { validateConfig } from './config/validateConfig';
|
|
15
|
+
import { createActiveLocaleStore } from './locale/activeLocale';
|
|
16
|
+
import { defaultDeviceLocaleProvider, matchSupportedLocale } from './locale/deviceLocale';
|
|
17
|
+
import {
|
|
18
|
+
readPersistedLocale,
|
|
19
|
+
readPersistedLocaleSync,
|
|
20
|
+
writePersistedLocale,
|
|
21
|
+
} from './locale/persistence';
|
|
22
|
+
import { translate } from './translation/resolver';
|
|
23
|
+
import type { ResolverContext } from './translation/resolver';
|
|
24
|
+
import { createRuntimeRegistry, registerRuntimeEntries } from './translation/runtimeKeys';
|
|
25
|
+
import { formatCurrencyValue } from './format/currency';
|
|
26
|
+
import { formatDateValue } from './format/date';
|
|
27
|
+
import { formatNumberValue } from './format/number';
|
|
28
|
+
import { KNOWN_UNIT_IDS, convertUnitValue, formatUnitValue } from './format/units';
|
|
29
|
+
import type { UnitFormatOptions } from './format/units';
|
|
30
|
+
|
|
31
|
+
export interface I18n<TKey extends string = string> {
|
|
32
|
+
/** Resolves the initial locale: persisted → device → primary fallback. */
|
|
33
|
+
init(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* `init()` without waiting, before the first render (PERF-08). Resolves the locale
|
|
36
|
+
* and returns true when it can do so synchronously: persistence is off, or the
|
|
37
|
+
* storage adapter has `getItemSync`. Returns false, changing nothing, when the
|
|
38
|
+
* adapter can only read asynchronously; call `init()` then.
|
|
39
|
+
*/
|
|
40
|
+
initSync(): boolean;
|
|
41
|
+
t(key: TKey, vars?: TranslateVars, options?: TranslateOptions): string;
|
|
42
|
+
getLocale(): ActiveLocalePreference;
|
|
43
|
+
/** Switches at runtime without restart and persists best-effort. */
|
|
44
|
+
setLocale(code: string): Promise<void>;
|
|
45
|
+
getSupportedLocales(): readonly LocaleConfig[];
|
|
46
|
+
formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
|
|
47
|
+
formatDate(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
48
|
+
formatCurrency(value: number, currency?: string, options?: Intl.NumberFormatOptions): string;
|
|
49
|
+
formatUnit(value: number, options?: UnitFormatOptions): string;
|
|
50
|
+
convertUnit(value: number, from: UnitId, to: UnitId): number;
|
|
51
|
+
registerRuntimeEntries(entries: readonly RuntimeTranslationEntry[]): void;
|
|
52
|
+
subscribe(listener: () => void): () => void;
|
|
53
|
+
getVersion(): number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_MISSING: MissingTranslationPolicy = { mode: 'key' };
|
|
57
|
+
const DEFAULT_INVALID: InvalidFormatPolicy = { mode: 'placeholder', text: '—' };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates an I18n instance from an app-owned configuration. Validation runs
|
|
61
|
+
* eagerly and throws an I18nConfigError before any localized content can be
|
|
62
|
+
* served (FR-003). Pass the app's generated key type for typed lookups:
|
|
63
|
+
* `createI18n<AppI18nKey>(config)`.
|
|
64
|
+
*/
|
|
65
|
+
export function createI18n<TKey extends string = string>(config: I18nConfig): I18n<TKey> {
|
|
66
|
+
validateConfig(config, KNOWN_UNIT_IDS);
|
|
67
|
+
|
|
68
|
+
const locales: readonly LocaleConfig[] = [...config.locales];
|
|
69
|
+
const localesByCode = new Map(locales.map(locale => [locale.code, locale]));
|
|
70
|
+
const missingTranslation = config.missingTranslation ?? DEFAULT_MISSING;
|
|
71
|
+
const report = createReporter(config.onDiagnostic);
|
|
72
|
+
const invalidFormat: ResolvedInvalidFormatPolicy = {
|
|
73
|
+
...(config.invalidFormat ?? DEFAULT_INVALID),
|
|
74
|
+
report,
|
|
75
|
+
};
|
|
76
|
+
const persistenceEnabled = config.persistLocale !== false && !!config.storage;
|
|
77
|
+
const runtimeEntries = createRuntimeRegistry(config.runtimeEntries);
|
|
78
|
+
|
|
79
|
+
const store = createActiveLocaleStore(
|
|
80
|
+
{ code: config.primaryLocale, source: 'primaryFallback', recovered: false },
|
|
81
|
+
report,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const resolverContext: ResolverContext = {
|
|
85
|
+
localesByCode,
|
|
86
|
+
resources: config.resources,
|
|
87
|
+
runtimeEntries,
|
|
88
|
+
missingTranslation,
|
|
89
|
+
warned: new Set(),
|
|
90
|
+
report,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const activeLocale = (): LocaleConfig => {
|
|
94
|
+
const locale = localesByCode.get(store.get().code);
|
|
95
|
+
if (!locale) {
|
|
96
|
+
// Unreachable after validation; guards against external mutation.
|
|
97
|
+
throw new Error(`[rn-i18n] Active locale "${store.get().code}" is not configured.`);
|
|
98
|
+
}
|
|
99
|
+
return locale;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** persisted → device → primary fallback; shared by `init` and `initSync`. */
|
|
103
|
+
const resolveInitialLocale = (persisted: string | null): void => {
|
|
104
|
+
if (persisted) {
|
|
105
|
+
if (localesByCode.has(persisted)) {
|
|
106
|
+
store.set({ code: persisted, source: 'persisted', recovered: false });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
report({
|
|
110
|
+
code: 'locale_unsupported',
|
|
111
|
+
locale: persisted,
|
|
112
|
+
message: `Persisted locale "${persisted}" is no longer supported; recovering to "${config.primaryLocale}".`,
|
|
113
|
+
});
|
|
114
|
+
store.set({ code: config.primaryLocale, source: 'primaryFallback', recovered: true });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const provider = config.deviceLocale ?? defaultDeviceLocaleProvider;
|
|
118
|
+
const match = matchSupportedLocale(provider(), locales);
|
|
119
|
+
store.set(
|
|
120
|
+
match
|
|
121
|
+
? { code: match.code, source: match.source, recovered: false }
|
|
122
|
+
: { code: config.primaryLocale, source: 'primaryFallback', recovered: false },
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
async init() {
|
|
128
|
+
resolveInitialLocale(
|
|
129
|
+
persistenceEnabled && config.storage
|
|
130
|
+
? await readPersistedLocale(config.storage, report)
|
|
131
|
+
: null,
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
initSync() {
|
|
136
|
+
if (!persistenceEnabled || !config.storage) {
|
|
137
|
+
resolveInitialLocale(null);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const { storage } = config;
|
|
141
|
+
if (typeof storage.getItemSync !== 'function') {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
resolveInitialLocale(
|
|
145
|
+
readPersistedLocaleSync(
|
|
146
|
+
storage as typeof storage & { getItemSync(key: string): string | null },
|
|
147
|
+
report,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
return true;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
t(key, vars, options) {
|
|
154
|
+
return translate(resolverContext, store.get().code, key, vars, options);
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getLocale() {
|
|
158
|
+
return store.get();
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async setLocale(code) {
|
|
162
|
+
if (!localesByCode.has(code)) {
|
|
163
|
+
throw new Error(`[rn-i18n] Cannot switch to unsupported locale "${code}".`);
|
|
164
|
+
}
|
|
165
|
+
store.set({ code, source: 'runtimeSwitch', recovered: false });
|
|
166
|
+
if (persistenceEnabled && config.storage) {
|
|
167
|
+
await writePersistedLocale(config.storage, code, report);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
getSupportedLocales() {
|
|
172
|
+
return locales;
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
formatNumber(value, options) {
|
|
176
|
+
const locale = activeLocale();
|
|
177
|
+
return formatNumberValue(
|
|
178
|
+
value,
|
|
179
|
+
locale.code,
|
|
180
|
+
locale.defaultNumberFormat,
|
|
181
|
+
options,
|
|
182
|
+
invalidFormat,
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
formatDate(value, options) {
|
|
187
|
+
const locale = activeLocale();
|
|
188
|
+
return formatDateValue(value, locale.code, locale.defaultDateFormat, options, invalidFormat);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
formatCurrency(value, currency, options) {
|
|
192
|
+
const locale = activeLocale();
|
|
193
|
+
return formatCurrencyValue(
|
|
194
|
+
value,
|
|
195
|
+
locale.code,
|
|
196
|
+
locale.defaultCurrency,
|
|
197
|
+
currency,
|
|
198
|
+
options,
|
|
199
|
+
invalidFormat,
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
formatUnit(value, options) {
|
|
204
|
+
const locale = activeLocale();
|
|
205
|
+
return formatUnitValue(
|
|
206
|
+
value,
|
|
207
|
+
locale.code,
|
|
208
|
+
locale.defaultUnit,
|
|
209
|
+
locale.defaultNumberFormat,
|
|
210
|
+
options,
|
|
211
|
+
invalidFormat,
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
convertUnit(value, from, to) {
|
|
216
|
+
return convertUnitValue(value, from, to);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
registerRuntimeEntries(entries) {
|
|
220
|
+
registerRuntimeEntries(runtimeEntries, entries);
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
subscribe(listener) {
|
|
224
|
+
return store.subscribe(listener);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
getVersion() {
|
|
228
|
+
return store.getVersion();
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,50 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @chipmobilesdk/rn-i18n — reusable localization for ChipMobileSdk-family
|
|
3
|
-
* React Native apps.
|
|
4
|
-
*
|
|
5
|
-
* This is the single React Native entrypoint. Node-only extraction and
|
|
6
|
-
* synchronization tooling is exposed separately via
|
|
7
|
-
* `@chipmobilesdk/rn-i18n/tooling` and must never be imported from here.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export { createI18n } from './i18n';
|
|
11
|
-
export type { I18n } from './i18n';
|
|
12
|
-
|
|
13
|
-
export { I18nConfigError } from './config/types';
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} from './
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
export {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @chipmobilesdk/rn-i18n — reusable localization for ChipMobileSdk-family
|
|
3
|
+
* React Native apps.
|
|
4
|
+
*
|
|
5
|
+
* This is the single React Native entrypoint. Node-only extraction and
|
|
6
|
+
* synchronization tooling is exposed separately via
|
|
7
|
+
* `@chipmobilesdk/rn-i18n/tooling` and must never be imported from here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { createI18n } from './i18n';
|
|
11
|
+
export type { I18n } from './i18n';
|
|
12
|
+
|
|
13
|
+
export { I18nConfigError } from './config/types';
|
|
14
|
+
export { runtimeEntriesFromCatalog } from './config/catalog';
|
|
15
|
+
export { localeStorageFromKeyValue } from './locale/keyValueStorage';
|
|
16
|
+
export type { I18nErrorCode } from './config/errors';
|
|
17
|
+
export type {
|
|
18
|
+
I18nDiagnostic,
|
|
19
|
+
I18nDiagnosticCode,
|
|
20
|
+
I18nDiagnosticHandler,
|
|
21
|
+
} from './config/diagnostics';
|
|
22
|
+
export type {
|
|
23
|
+
ActiveLocalePreference,
|
|
24
|
+
DeviceLocaleProvider,
|
|
25
|
+
I18nConfig,
|
|
26
|
+
InvalidFormatPolicy,
|
|
27
|
+
LocaleConfig,
|
|
28
|
+
LocaleDirection,
|
|
29
|
+
LocaleSelectionSource,
|
|
30
|
+
LocaleStorageAdapter,
|
|
31
|
+
MissingTranslationPolicy,
|
|
32
|
+
PluralCategory,
|
|
33
|
+
PluralRule,
|
|
34
|
+
RuntimeTranslationEntry,
|
|
35
|
+
TranslateOptions,
|
|
36
|
+
TranslateVars,
|
|
37
|
+
TranslationNamespace,
|
|
38
|
+
TranslationNode,
|
|
39
|
+
TranslationResources,
|
|
40
|
+
UnitDimension,
|
|
41
|
+
UnitId,
|
|
42
|
+
} from './config/types';
|
|
43
|
+
|
|
44
|
+
export { LOCALE_STORAGE_KEY } from './locale/persistence';
|
|
45
|
+
export { defaultDeviceLocaleProvider } from './locale/deviceLocale';
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
SUPPORTED_UNITS,
|
|
49
|
+
UnitConversionError,
|
|
50
|
+
convertUnitValue as convertUnit,
|
|
51
|
+
getUnitSymbol,
|
|
52
|
+
} from './format/units';
|
|
53
|
+
export type { UnitFormatOptions } from './format/units';
|
|
54
|
+
|
|
55
|
+
export { I18nProvider } from './react/I18nProvider';
|
|
56
|
+
export type { I18nProviderProps } from './react/I18nProvider';
|
|
57
|
+
export { useLocale, useTranslation } from './react/hooks';
|
|
58
|
+
export type { UseLocaleResult, UseTranslationResult } from './react/hooks';
|
|
@@ -1,45 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
getVersion: () => version,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
1
|
+
import { createListenerSet } from '@chipmobilesdk/rn-core';
|
|
2
|
+
|
|
3
|
+
import { defaultReporter, describeError, type I18nDiagnosticHandler } from '../config/diagnostics';
|
|
4
|
+
import type { ActiveLocalePreference } from '../config/types';
|
|
5
|
+
|
|
6
|
+
export interface ActiveLocaleStore {
|
|
7
|
+
get(): ActiveLocalePreference;
|
|
8
|
+
set(preference: ActiveLocalePreference): void;
|
|
9
|
+
subscribe(listener: () => void): () => void;
|
|
10
|
+
/** Monotonic change counter consumed by useSyncExternalStore. */
|
|
11
|
+
getVersion(): number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createActiveLocaleStore(
|
|
15
|
+
initial: ActiveLocalePreference,
|
|
16
|
+
report: I18nDiagnosticHandler = defaultReporter,
|
|
17
|
+
): ActiveLocaleStore {
|
|
18
|
+
let preference = initial;
|
|
19
|
+
let version = 0;
|
|
20
|
+
const listeners = createListenerSet<[]>({
|
|
21
|
+
onListenerError: error =>
|
|
22
|
+
report({
|
|
23
|
+
code: 'listener_threw',
|
|
24
|
+
message: `A locale listener threw; the locale change still applied: ${describeError(error)}`,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
get: () => preference,
|
|
30
|
+
set(next) {
|
|
31
|
+
preference = next;
|
|
32
|
+
version += 1;
|
|
33
|
+
// Isolated (CORE-03). One that threw used to skip every listener after
|
|
34
|
+
// it — useSyncExternalStore subscribers included — and made setLocale
|
|
35
|
+
// reject after the switch but before the choice was persisted, so the
|
|
36
|
+
// app showed one locale and stored another.
|
|
37
|
+
listeners.notify();
|
|
38
|
+
},
|
|
39
|
+
subscribe: listener => listeners.add(listener),
|
|
40
|
+
getVersion: () => version,
|
|
41
|
+
};
|
|
42
|
+
}
|