react-native-i18njs 0.0.2 → 0.0.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/LICENSE +15 -0
- package/README.md +36 -5
- package/dist/index.d.mts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +125 -73
- package/dist/index.mjs +120 -74
- package/package.json +12 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present, wangws
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- 🛡️ **极致类型安全**:完全 TypeScript 编写,提供从 Key 到插值参数的完整类型推导。
|
|
16
16
|
- 📱 **自动跟随系统**:基于 `react-native-localize`,自动检测并响应设备语言变更。
|
|
17
17
|
- ⚡ **高性能**:基于 `i18n-js` 核心,轻量高效,无多余运行时开销。
|
|
18
|
-
- 🔌 **灵活 API**:同时支持 Hook (`useI18n`)、高阶组件 (`
|
|
18
|
+
- 🔌 **灵活 API**:同时支持 Hook (`useI18n`)、高阶组件 (`withI18n`) 和全局函数 (`t`)。
|
|
19
19
|
- 📝 **富文本支持**:`Trans` 组件轻松处理嵌套样式和组件插值。
|
|
20
20
|
- 🌍 **格式化内置**:开箱即用的数字、货币、日期格式化支持。
|
|
21
21
|
|
|
@@ -129,13 +129,13 @@ const message = t('errors.network_timeout');
|
|
|
129
129
|
|
|
130
130
|
#### 进阶:监听语言变化
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
如果你需要在组件外监听语言变更(例如同步更新全局状态),可以使用顶层 `subscribe` 函数:
|
|
133
133
|
|
|
134
134
|
```ts
|
|
135
|
-
import
|
|
135
|
+
import { subscribe } from 'react-native-i18njs';
|
|
136
136
|
|
|
137
137
|
// 订阅语言变更
|
|
138
|
-
const unsubscribe =
|
|
138
|
+
const unsubscribe = subscribe((locale) => {
|
|
139
139
|
console.log('Language changed to:', locale);
|
|
140
140
|
// 更新 API 默认 Header 或其他全局状态
|
|
141
141
|
});
|
|
@@ -144,6 +144,27 @@ const unsubscribe = i18n.subscribe((locale) => {
|
|
|
144
144
|
// unsubscribe();
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
#### 进阶:重置为跟随系统
|
|
148
|
+
|
|
149
|
+
用户手动调用 `setLocale` 后会锁定语言,不再自动跟随系统。如果需要恢复跟随系统语言:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { resetToSystem } from 'react-native-i18njs';
|
|
153
|
+
|
|
154
|
+
// 撤销用户锁定,重新跟随系统语言
|
|
155
|
+
resetToSystem();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 进阶:RTL 检测
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { isRTL } from 'react-native-i18njs';
|
|
162
|
+
|
|
163
|
+
if (isRTL()) {
|
|
164
|
+
// 当前为从右到左语言(如阿拉伯语、希伯来语)
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
147
168
|
#### 实战示例:Axios 拦截器
|
|
148
169
|
|
|
149
170
|
```ts
|
|
@@ -192,7 +213,7 @@ async function loadFrench() {
|
|
|
192
213
|
|
|
193
214
|
### 4. 格式化工具
|
|
194
215
|
|
|
195
|
-
利用 `Intl`
|
|
216
|
+
利用 `Intl` 标准进行格式化,在组件中通过 Hook 使用:
|
|
196
217
|
|
|
197
218
|
```ts
|
|
198
219
|
const { formatNumber, formatCurrency, formatDate } = useI18n();
|
|
@@ -207,6 +228,16 @@ formatCurrency(99.99, 'USD'); // "$99.99"
|
|
|
207
228
|
formatDate(new Date(), { dateStyle: 'full' }); // "Tuesday, October 10, 2023"
|
|
208
229
|
```
|
|
209
230
|
|
|
231
|
+
在非组件环境中,也可以直接使用顶层导出:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { formatNumber, formatCurrency, formatDate } from 'react-native-i18njs';
|
|
235
|
+
|
|
236
|
+
formatNumber(1234.56);
|
|
237
|
+
formatCurrency(99.99, 'USD');
|
|
238
|
+
formatDate(new Date());
|
|
239
|
+
```
|
|
240
|
+
|
|
210
241
|
## ⚙️ 配置选项 (I18nOptions)
|
|
211
242
|
|
|
212
243
|
`initI18n` 接受的第二个参数对象:
|
package/dist/index.d.mts
CHANGED
|
@@ -35,6 +35,8 @@ interface I18nEngine {
|
|
|
35
35
|
formatDate(date: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
36
36
|
subscribe(listener: Listener): () => void;
|
|
37
37
|
isRTL(): boolean;
|
|
38
|
+
/** 重置为跟随系统语言,撤销 setLocale 的用户锁定 */
|
|
39
|
+
resetToSystem(): void;
|
|
38
40
|
ready(): Promise<void>;
|
|
39
41
|
isReady(): boolean;
|
|
40
42
|
}
|
|
@@ -55,12 +57,18 @@ declare class DefaultI18nEngine implements I18nEngine {
|
|
|
55
57
|
private version;
|
|
56
58
|
private readyPromise;
|
|
57
59
|
private resolveReady?;
|
|
60
|
+
/** Intl 格式化器缓存,key = locale + JSON(options) */
|
|
61
|
+
private numberFormatCache;
|
|
62
|
+
private dateFormatCache;
|
|
63
|
+
/** locale chain 缓存,locale 或 fallbackLocales 变化时失效 */
|
|
64
|
+
private localeChainCache;
|
|
58
65
|
constructor();
|
|
59
66
|
init(translations: Translations, options?: I18nOptions): void;
|
|
60
67
|
loadTranslations(translations: Translations): void;
|
|
61
68
|
updateLocale(): void;
|
|
62
69
|
setLocale(locale: string): void;
|
|
63
70
|
getLocale(): string;
|
|
71
|
+
resetToSystem(): void;
|
|
64
72
|
t(scope: Scope, options?: TranslateOptions): string;
|
|
65
73
|
formatNumber(n: number, options?: Intl.NumberFormatOptions): string;
|
|
66
74
|
formatCurrency(n: number, currency: string, options?: Intl.NumberFormatOptions): string;
|
|
@@ -69,11 +77,16 @@ declare class DefaultI18nEngine implements I18nEngine {
|
|
|
69
77
|
isRTL(): boolean;
|
|
70
78
|
ready(): Promise<void>;
|
|
71
79
|
isReady(): boolean;
|
|
80
|
+
private invalidateCaches;
|
|
81
|
+
private getCachedNumberFormat;
|
|
82
|
+
private getCachedDateFormat;
|
|
72
83
|
private handleRTL;
|
|
73
84
|
private notifyListeners;
|
|
74
85
|
private setLocaleFromSystem;
|
|
75
86
|
private applyLocale;
|
|
76
87
|
private translateAtLocale;
|
|
88
|
+
/** 将 locale 及其自动降级(如 en-US → en)追加到 chain 中 */
|
|
89
|
+
private pushWithDegradation;
|
|
77
90
|
private getLocaleChain;
|
|
78
91
|
private hasTranslation;
|
|
79
92
|
private normalizeTranslateResult;
|
|
@@ -126,7 +139,14 @@ declare const loadTranslations: (translations: Translations) => void;
|
|
|
126
139
|
declare const setLocale: (locale: string) => void;
|
|
127
140
|
declare const getLocale: () => string;
|
|
128
141
|
declare const t: (scope: Scope, options?: TranslateOptions) => string;
|
|
142
|
+
declare const formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;
|
|
143
|
+
declare const formatCurrency: (n: number, currency: string, options?: Intl.NumberFormatOptions) => string;
|
|
144
|
+
declare const formatDate: (date: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
145
|
+
declare const subscribe: (listener: Listener) => (() => void);
|
|
146
|
+
declare const isRTL: () => boolean;
|
|
147
|
+
/** 重置为跟随系统语言,撤销 setLocale 的用户锁定 */
|
|
148
|
+
declare const resetToSystem: () => void;
|
|
129
149
|
declare const readyI18n: () => Promise<void>;
|
|
130
150
|
declare const isI18nReady: () => boolean;
|
|
131
151
|
|
|
132
|
-
export { I18nContext, type I18nContextType, type I18nOptions, I18nProvider, type I18nProviderProps, type Path, Trans, type Translations, i18nService as default, getLocale, initI18n, isI18nReady, loadTranslations, readyI18n, setLocale, t, useI18n, withI18n };
|
|
152
|
+
export { I18nContext, type I18nContextType, type I18nOptions, I18nProvider, type I18nProviderProps, type Listener, type Path, Trans, type Translations, i18nService as default, formatCurrency, formatDate, formatNumber, getLocale, initI18n, isI18nReady, isRTL, loadTranslations, readyI18n, resetToSystem, setLocale, subscribe, t, useI18n, withI18n };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ interface I18nEngine {
|
|
|
35
35
|
formatDate(date: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
36
36
|
subscribe(listener: Listener): () => void;
|
|
37
37
|
isRTL(): boolean;
|
|
38
|
+
/** 重置为跟随系统语言,撤销 setLocale 的用户锁定 */
|
|
39
|
+
resetToSystem(): void;
|
|
38
40
|
ready(): Promise<void>;
|
|
39
41
|
isReady(): boolean;
|
|
40
42
|
}
|
|
@@ -55,12 +57,18 @@ declare class DefaultI18nEngine implements I18nEngine {
|
|
|
55
57
|
private version;
|
|
56
58
|
private readyPromise;
|
|
57
59
|
private resolveReady?;
|
|
60
|
+
/** Intl 格式化器缓存,key = locale + JSON(options) */
|
|
61
|
+
private numberFormatCache;
|
|
62
|
+
private dateFormatCache;
|
|
63
|
+
/** locale chain 缓存,locale 或 fallbackLocales 变化时失效 */
|
|
64
|
+
private localeChainCache;
|
|
58
65
|
constructor();
|
|
59
66
|
init(translations: Translations, options?: I18nOptions): void;
|
|
60
67
|
loadTranslations(translations: Translations): void;
|
|
61
68
|
updateLocale(): void;
|
|
62
69
|
setLocale(locale: string): void;
|
|
63
70
|
getLocale(): string;
|
|
71
|
+
resetToSystem(): void;
|
|
64
72
|
t(scope: Scope, options?: TranslateOptions): string;
|
|
65
73
|
formatNumber(n: number, options?: Intl.NumberFormatOptions): string;
|
|
66
74
|
formatCurrency(n: number, currency: string, options?: Intl.NumberFormatOptions): string;
|
|
@@ -69,11 +77,16 @@ declare class DefaultI18nEngine implements I18nEngine {
|
|
|
69
77
|
isRTL(): boolean;
|
|
70
78
|
ready(): Promise<void>;
|
|
71
79
|
isReady(): boolean;
|
|
80
|
+
private invalidateCaches;
|
|
81
|
+
private getCachedNumberFormat;
|
|
82
|
+
private getCachedDateFormat;
|
|
72
83
|
private handleRTL;
|
|
73
84
|
private notifyListeners;
|
|
74
85
|
private setLocaleFromSystem;
|
|
75
86
|
private applyLocale;
|
|
76
87
|
private translateAtLocale;
|
|
88
|
+
/** 将 locale 及其自动降级(如 en-US → en)追加到 chain 中 */
|
|
89
|
+
private pushWithDegradation;
|
|
77
90
|
private getLocaleChain;
|
|
78
91
|
private hasTranslation;
|
|
79
92
|
private normalizeTranslateResult;
|
|
@@ -126,7 +139,14 @@ declare const loadTranslations: (translations: Translations) => void;
|
|
|
126
139
|
declare const setLocale: (locale: string) => void;
|
|
127
140
|
declare const getLocale: () => string;
|
|
128
141
|
declare const t: (scope: Scope, options?: TranslateOptions) => string;
|
|
142
|
+
declare const formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;
|
|
143
|
+
declare const formatCurrency: (n: number, currency: string, options?: Intl.NumberFormatOptions) => string;
|
|
144
|
+
declare const formatDate: (date: Date | number, options?: Intl.DateTimeFormatOptions) => string;
|
|
145
|
+
declare const subscribe: (listener: Listener) => (() => void);
|
|
146
|
+
declare const isRTL: () => boolean;
|
|
147
|
+
/** 重置为跟随系统语言,撤销 setLocale 的用户锁定 */
|
|
148
|
+
declare const resetToSystem: () => void;
|
|
129
149
|
declare const readyI18n: () => Promise<void>;
|
|
130
150
|
declare const isI18nReady: () => boolean;
|
|
131
151
|
|
|
132
|
-
export { I18nContext, type I18nContextType, type I18nOptions, I18nProvider, type I18nProviderProps, type Path, Trans, type Translations, i18nService as default, getLocale, initI18n, isI18nReady, loadTranslations, readyI18n, setLocale, t, useI18n, withI18n };
|
|
152
|
+
export { I18nContext, type I18nContextType, type I18nOptions, I18nProvider, type I18nProviderProps, type Listener, type Path, Trans, type Translations, i18nService as default, formatCurrency, formatDate, formatNumber, getLocale, initI18n, isI18nReady, isRTL, loadTranslations, readyI18n, resetToSystem, setLocale, subscribe, t, useI18n, withI18n };
|
package/dist/index.js
CHANGED
|
@@ -73,6 +73,12 @@ var DefaultI18nEngine = class {
|
|
|
73
73
|
__publicField(this, "version");
|
|
74
74
|
__publicField(this, "readyPromise");
|
|
75
75
|
__publicField(this, "resolveReady");
|
|
76
|
+
// --- 性能缓存 ---
|
|
77
|
+
/** Intl 格式化器缓存,key = locale + JSON(options) */
|
|
78
|
+
__publicField(this, "numberFormatCache", /* @__PURE__ */ new Map());
|
|
79
|
+
__publicField(this, "dateFormatCache", /* @__PURE__ */ new Map());
|
|
80
|
+
/** locale chain 缓存,locale 或 fallbackLocales 变化时失效 */
|
|
81
|
+
__publicField(this, "localeChainCache", /* @__PURE__ */ new Map());
|
|
76
82
|
this.i18n = new i18nJs.I18n();
|
|
77
83
|
this.listeners = /* @__PURE__ */ new Set();
|
|
78
84
|
this.i18n.enableFallback = false;
|
|
@@ -107,6 +113,7 @@ var DefaultI18nEngine = class {
|
|
|
107
113
|
this.localeSource = "system";
|
|
108
114
|
this.missingBehavior = missingBehavior;
|
|
109
115
|
this.onMissingKey = onMissingKey;
|
|
116
|
+
this.invalidateCaches();
|
|
110
117
|
if (this.followSystem) this.updateLocale();
|
|
111
118
|
this.version += 1;
|
|
112
119
|
this.notifyListeners({ type: "translations", version: this.version });
|
|
@@ -142,6 +149,10 @@ var DefaultI18nEngine = class {
|
|
|
142
149
|
getLocale() {
|
|
143
150
|
return this.i18n.locale;
|
|
144
151
|
}
|
|
152
|
+
resetToSystem() {
|
|
153
|
+
this.localeSource = "system";
|
|
154
|
+
this.updateLocale();
|
|
155
|
+
}
|
|
145
156
|
t(scope, options) {
|
|
146
157
|
var _a, _b, _c;
|
|
147
158
|
if (typeof scope !== "string") {
|
|
@@ -175,7 +186,7 @@ var DefaultI18nEngine = class {
|
|
|
175
186
|
return String(n);
|
|
176
187
|
}
|
|
177
188
|
try {
|
|
178
|
-
return
|
|
189
|
+
return this.getCachedNumberFormat(this.i18n.locale, options).format(n);
|
|
179
190
|
} catch {
|
|
180
191
|
return String(n);
|
|
181
192
|
}
|
|
@@ -185,7 +196,7 @@ var DefaultI18nEngine = class {
|
|
|
185
196
|
return `${n} ${currency}`;
|
|
186
197
|
}
|
|
187
198
|
try {
|
|
188
|
-
return
|
|
199
|
+
return this.getCachedNumberFormat(this.i18n.locale, {
|
|
189
200
|
...options,
|
|
190
201
|
style: "currency",
|
|
191
202
|
currency
|
|
@@ -201,7 +212,7 @@ var DefaultI18nEngine = class {
|
|
|
201
212
|
return d.toISOString();
|
|
202
213
|
}
|
|
203
214
|
try {
|
|
204
|
-
return
|
|
215
|
+
return this.getCachedDateFormat(this.i18n.locale, options).format(d);
|
|
205
216
|
} catch {
|
|
206
217
|
return d.toISOString();
|
|
207
218
|
}
|
|
@@ -222,15 +233,40 @@ var DefaultI18nEngine = class {
|
|
|
222
233
|
isReady() {
|
|
223
234
|
return this.initialized;
|
|
224
235
|
}
|
|
236
|
+
// ─── 缓存管理 ────────────────────────────────────────────
|
|
237
|
+
invalidateCaches() {
|
|
238
|
+
this.numberFormatCache.clear();
|
|
239
|
+
this.dateFormatCache.clear();
|
|
240
|
+
this.localeChainCache.clear();
|
|
241
|
+
}
|
|
242
|
+
getCachedNumberFormat(locale, options) {
|
|
243
|
+
const cacheKey = locale + (options ? JSON.stringify(options) : "");
|
|
244
|
+
let fmt = this.numberFormatCache.get(cacheKey);
|
|
245
|
+
if (!fmt) {
|
|
246
|
+
fmt = new Intl.NumberFormat(locale, options);
|
|
247
|
+
this.numberFormatCache.set(cacheKey, fmt);
|
|
248
|
+
}
|
|
249
|
+
return fmt;
|
|
250
|
+
}
|
|
251
|
+
getCachedDateFormat(locale, options) {
|
|
252
|
+
const cacheKey = locale + (options ? JSON.stringify(options) : "");
|
|
253
|
+
let fmt = this.dateFormatCache.get(cacheKey);
|
|
254
|
+
if (!fmt) {
|
|
255
|
+
fmt = new Intl.DateTimeFormat(locale, options);
|
|
256
|
+
this.dateFormatCache.set(cacheKey, fmt);
|
|
257
|
+
}
|
|
258
|
+
return fmt;
|
|
259
|
+
}
|
|
260
|
+
// ─── 私有方法 ────────────────────────────────────────────
|
|
225
261
|
handleRTL(locale) {
|
|
226
262
|
var _a, _b;
|
|
227
|
-
const
|
|
263
|
+
const isRTL2 = this.isRTLLocale(locale);
|
|
228
264
|
if (typeof ((_a = reactNative.I18nManager) == null ? void 0 : _a.allowRTL) !== "function" || typeof ((_b = reactNative.I18nManager) == null ? void 0 : _b.forceRTL) !== "function") {
|
|
229
265
|
return;
|
|
230
266
|
}
|
|
231
|
-
if (reactNative.I18nManager.isRTL !==
|
|
232
|
-
reactNative.I18nManager.allowRTL(
|
|
233
|
-
reactNative.I18nManager.forceRTL(
|
|
267
|
+
if (reactNative.I18nManager.isRTL !== isRTL2) {
|
|
268
|
+
reactNative.I18nManager.allowRTL(isRTL2);
|
|
269
|
+
reactNative.I18nManager.forceRTL(isRTL2);
|
|
234
270
|
}
|
|
235
271
|
}
|
|
236
272
|
notifyListeners(change) {
|
|
@@ -245,6 +281,7 @@ var DefaultI18nEngine = class {
|
|
|
245
281
|
const normalizedLocale = this.normalizeLocaleTag(locale);
|
|
246
282
|
if (this.i18n.locale !== normalizedLocale) {
|
|
247
283
|
this.i18n.locale = normalizedLocale;
|
|
284
|
+
this.invalidateCaches();
|
|
248
285
|
this.handleRTL(normalizedLocale);
|
|
249
286
|
this.version += 1;
|
|
250
287
|
this.notifyListeners({ type: "locale", version: this.version });
|
|
@@ -260,39 +297,36 @@ var DefaultI18nEngine = class {
|
|
|
260
297
|
this.i18n.locale = prevLocale;
|
|
261
298
|
}
|
|
262
299
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
chain.push(
|
|
267
|
-
const parts =
|
|
300
|
+
/** 将 locale 及其自动降级(如 en-US → en)追加到 chain 中 */
|
|
301
|
+
pushWithDegradation(chain, locale) {
|
|
302
|
+
const normalized = this.normalizeLocaleTag(locale);
|
|
303
|
+
chain.push(normalized);
|
|
304
|
+
const parts = normalized.split("-").filter(Boolean);
|
|
268
305
|
for (let i = parts.length - 1; i >= 1; i -= 1) {
|
|
269
306
|
chain.push(parts.slice(0, i).join("-"));
|
|
270
307
|
}
|
|
308
|
+
}
|
|
309
|
+
getLocaleChain(locale) {
|
|
310
|
+
const cached = this.localeChainCache.get(locale);
|
|
311
|
+
if (cached) return cached;
|
|
312
|
+
const chain = [];
|
|
313
|
+
this.pushWithDegradation(chain, locale);
|
|
314
|
+
const normalizedLocale = this.normalizeLocaleTag(locale);
|
|
271
315
|
const extra = this.fallbackLocales ? typeof this.fallbackLocales === "function" ? this.fallbackLocales(normalizedLocale) : this.fallbackLocales : [];
|
|
272
316
|
for (const l of extra) {
|
|
273
|
-
|
|
274
|
-
chain.push(normalizedFallback);
|
|
275
|
-
const fallbackParts = normalizedFallback.split("-").filter(Boolean);
|
|
276
|
-
for (let i = fallbackParts.length - 1; i >= 1; i -= 1) {
|
|
277
|
-
chain.push(fallbackParts.slice(0, i).join("-"));
|
|
278
|
-
}
|
|
317
|
+
this.pushWithDegradation(chain, l);
|
|
279
318
|
}
|
|
280
319
|
if (this.i18n.defaultLocale) {
|
|
281
|
-
|
|
282
|
-
chain.push(normalizedDefault);
|
|
283
|
-
const defaultParts = normalizedDefault.split("-").filter(Boolean);
|
|
284
|
-
for (let i = defaultParts.length - 1; i >= 1; i -= 1) {
|
|
285
|
-
chain.push(defaultParts.slice(0, i).join("-"));
|
|
286
|
-
}
|
|
320
|
+
this.pushWithDegradation(chain, this.i18n.defaultLocale);
|
|
287
321
|
}
|
|
288
322
|
const seen = /* @__PURE__ */ new Set();
|
|
289
|
-
|
|
290
|
-
if (!l) return false;
|
|
291
|
-
|
|
292
|
-
if (seen.has(normalized)) return false;
|
|
293
|
-
seen.add(normalized);
|
|
323
|
+
const result = chain.filter((l) => {
|
|
324
|
+
if (!l || seen.has(l)) return false;
|
|
325
|
+
seen.add(l);
|
|
294
326
|
return true;
|
|
295
327
|
});
|
|
328
|
+
this.localeChainCache.set(locale, result);
|
|
329
|
+
return result;
|
|
296
330
|
}
|
|
297
331
|
hasTranslation(locale, key) {
|
|
298
332
|
var _a;
|
|
@@ -327,7 +361,7 @@ var DefaultI18nEngine = class {
|
|
|
327
361
|
}
|
|
328
362
|
isRTLLocale(locale) {
|
|
329
363
|
var _a;
|
|
330
|
-
const normalized =
|
|
364
|
+
const normalized = this.normalizeLocaleTag(locale);
|
|
331
365
|
const parts = normalized.split("-").filter(Boolean);
|
|
332
366
|
const languageCode = (_a = parts[0]) == null ? void 0 : _a.toLowerCase();
|
|
333
367
|
if (!languageCode) return false;
|
|
@@ -427,13 +461,13 @@ function useI18n() {
|
|
|
427
461
|
}, [context]);
|
|
428
462
|
}
|
|
429
463
|
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
464
|
+
var TAG_REGEX = /<(\/?)([A-Za-z][\w-]*)(?:\s[^>]*?)?(\/?)>/g;
|
|
430
465
|
var parseString = (input) => {
|
|
431
466
|
var _a;
|
|
432
467
|
const root = { type: "tag", name: "root", children: [] };
|
|
433
468
|
const stack = [root];
|
|
434
|
-
const tagRegex = /<(\/?)([A-Za-z][\w-]*)(?:\s[^>]*?)?(\/?)>/g;
|
|
435
469
|
let lastIndex = 0;
|
|
436
|
-
for (const match of input.matchAll(
|
|
470
|
+
for (const match of input.matchAll(TAG_REGEX)) {
|
|
437
471
|
const fullMatch = match[0];
|
|
438
472
|
const isClosing = match[1] === "/";
|
|
439
473
|
const tagName = match[2];
|
|
@@ -463,51 +497,57 @@ var parseString = (input) => {
|
|
|
463
497
|
}
|
|
464
498
|
return root.children;
|
|
465
499
|
};
|
|
500
|
+
var renderAST = (nodes, keyPrefix, components, placeholderToElement) => {
|
|
501
|
+
return nodes.map((node, index) => {
|
|
502
|
+
const key = `${keyPrefix}-${index}`;
|
|
503
|
+
if (node.type === "text") {
|
|
504
|
+
const placeholders = Object.keys(placeholderToElement);
|
|
505
|
+
if (placeholders.length === 0) return node.content;
|
|
506
|
+
const pattern = new RegExp(`(${placeholders.map(escapeRegExp).join("|")})`, "g");
|
|
507
|
+
const parts = node.content.split(pattern);
|
|
508
|
+
if (parts.length === 1) return node.content;
|
|
509
|
+
return /* @__PURE__ */ React__default.default.createElement(React.Fragment, { key }, parts.map((part, i) => {
|
|
510
|
+
if (placeholderToElement[part]) {
|
|
511
|
+
return React.cloneElement(placeholderToElement[part], { key: `${key}-${i}` });
|
|
512
|
+
}
|
|
513
|
+
return part;
|
|
514
|
+
}));
|
|
515
|
+
} else if (node.type === "tag") {
|
|
516
|
+
const Component = components == null ? void 0 : components[node.name];
|
|
517
|
+
const children = renderAST(node.children, key, components, placeholderToElement);
|
|
518
|
+
if (React.isValidElement(Component)) {
|
|
519
|
+
return React.cloneElement(Component, { key }, children.length > 0 ? children : void 0);
|
|
520
|
+
}
|
|
521
|
+
return /* @__PURE__ */ React__default.default.createElement(React.Fragment, { key }, children);
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
});
|
|
525
|
+
};
|
|
466
526
|
var Trans = ({ i18nKey, values, components, ...props }) => {
|
|
467
527
|
const { t: t2 } = useI18n();
|
|
468
|
-
const stringValues = {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
const translatedText = t2(i18nKey, stringValues);
|
|
483
|
-
const ast = parseString(translatedText);
|
|
484
|
-
const renderAST = (nodes, keyPrefix) => {
|
|
485
|
-
return nodes.map((node, index) => {
|
|
486
|
-
const key = `${keyPrefix}-${index}`;
|
|
487
|
-
if (node.type === "text") {
|
|
488
|
-
const placeholders = Object.keys(placeholderToElement);
|
|
489
|
-
if (placeholders.length === 0) return node.content;
|
|
490
|
-
const pattern = new RegExp(`(${placeholders.map(escapeRegExp).join("|")})`, "g");
|
|
491
|
-
const parts = node.content.split(pattern);
|
|
492
|
-
if (parts.length === 1) return node.content;
|
|
493
|
-
return /* @__PURE__ */ React__default.default.createElement(React.Fragment, { key }, parts.map((part, i) => {
|
|
494
|
-
if (placeholderToElement[part]) {
|
|
495
|
-
return React.cloneElement(placeholderToElement[part], { key: `${key}-${i}` });
|
|
496
|
-
}
|
|
497
|
-
return part;
|
|
498
|
-
}));
|
|
499
|
-
} else if (node.type === "tag") {
|
|
500
|
-
const Component = components == null ? void 0 : components[node.name];
|
|
501
|
-
const children2 = renderAST(node.children, key);
|
|
502
|
-
if (React.isValidElement(Component)) {
|
|
503
|
-
return React.cloneElement(Component, { key }, children2.length > 0 ? children2 : void 0);
|
|
528
|
+
const { stringValues, placeholderToElement } = React.useMemo(() => {
|
|
529
|
+
const sv = {};
|
|
530
|
+
const pte = {};
|
|
531
|
+
if (values) {
|
|
532
|
+
for (const key of Object.keys(values)) {
|
|
533
|
+
const value = values[key];
|
|
534
|
+
if (React.isValidElement(value)) {
|
|
535
|
+
const placeholder = `__ELEMENT_${key}__`;
|
|
536
|
+
pte[placeholder] = value;
|
|
537
|
+
sv[key] = placeholder;
|
|
538
|
+
} else {
|
|
539
|
+
sv[key] = value == null ? "" : String(value);
|
|
504
540
|
}
|
|
505
|
-
return /* @__PURE__ */ React__default.default.createElement(React.Fragment, { key }, children2);
|
|
506
541
|
}
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
const
|
|
542
|
+
}
|
|
543
|
+
return { stringValues: sv, placeholderToElement: pte };
|
|
544
|
+
}, [values]);
|
|
545
|
+
const translatedText = t2(i18nKey, stringValues);
|
|
546
|
+
const ast = React.useMemo(() => parseString(translatedText), [translatedText]);
|
|
547
|
+
const children = React.useMemo(
|
|
548
|
+
() => renderAST(ast, "trans", components, placeholderToElement),
|
|
549
|
+
[ast, components, placeholderToElement]
|
|
550
|
+
);
|
|
511
551
|
return /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { ...props }, children);
|
|
512
552
|
};
|
|
513
553
|
|
|
@@ -517,6 +557,12 @@ var loadTranslations = (translations) => i18nService.loadTranslations(translatio
|
|
|
517
557
|
var setLocale = (locale) => i18nService.setLocale(locale);
|
|
518
558
|
var getLocale = () => i18nService.getLocale();
|
|
519
559
|
var t = (scope, options) => i18nService.t(scope, options);
|
|
560
|
+
var formatNumber = (n, options) => i18nService.formatNumber(n, options);
|
|
561
|
+
var formatCurrency = (n, currency, options) => i18nService.formatCurrency(n, currency, options);
|
|
562
|
+
var formatDate = (date, options) => i18nService.formatDate(date, options);
|
|
563
|
+
var subscribe = (listener) => i18nService.subscribe(listener);
|
|
564
|
+
var isRTL = () => i18nService.isRTL();
|
|
565
|
+
var resetToSystem = () => i18nService.resetToSystem();
|
|
520
566
|
var readyI18n = () => i18nService.ready();
|
|
521
567
|
var isI18nReady = () => i18nService.isReady();
|
|
522
568
|
var index_default = i18nService;
|
|
@@ -525,12 +571,18 @@ exports.I18nContext = I18nContext;
|
|
|
525
571
|
exports.I18nProvider = I18nProvider;
|
|
526
572
|
exports.Trans = Trans;
|
|
527
573
|
exports.default = index_default;
|
|
574
|
+
exports.formatCurrency = formatCurrency;
|
|
575
|
+
exports.formatDate = formatDate;
|
|
576
|
+
exports.formatNumber = formatNumber;
|
|
528
577
|
exports.getLocale = getLocale;
|
|
529
578
|
exports.initI18n = initI18n;
|
|
530
579
|
exports.isI18nReady = isI18nReady;
|
|
580
|
+
exports.isRTL = isRTL;
|
|
531
581
|
exports.loadTranslations = loadTranslations;
|
|
532
582
|
exports.readyI18n = readyI18n;
|
|
583
|
+
exports.resetToSystem = resetToSystem;
|
|
533
584
|
exports.setLocale = setLocale;
|
|
585
|
+
exports.subscribe = subscribe;
|
|
534
586
|
exports.t = t;
|
|
535
587
|
exports.useI18n = useI18n;
|
|
536
588
|
exports.withI18n = withI18n;
|
package/dist/index.mjs
CHANGED
|
@@ -46,6 +46,12 @@ var DefaultI18nEngine = class {
|
|
|
46
46
|
__publicField(this, "version");
|
|
47
47
|
__publicField(this, "readyPromise");
|
|
48
48
|
__publicField(this, "resolveReady");
|
|
49
|
+
// --- 性能缓存 ---
|
|
50
|
+
/** Intl 格式化器缓存,key = locale + JSON(options) */
|
|
51
|
+
__publicField(this, "numberFormatCache", /* @__PURE__ */ new Map());
|
|
52
|
+
__publicField(this, "dateFormatCache", /* @__PURE__ */ new Map());
|
|
53
|
+
/** locale chain 缓存,locale 或 fallbackLocales 变化时失效 */
|
|
54
|
+
__publicField(this, "localeChainCache", /* @__PURE__ */ new Map());
|
|
49
55
|
this.i18n = new I18n();
|
|
50
56
|
this.listeners = /* @__PURE__ */ new Set();
|
|
51
57
|
this.i18n.enableFallback = false;
|
|
@@ -80,6 +86,7 @@ var DefaultI18nEngine = class {
|
|
|
80
86
|
this.localeSource = "system";
|
|
81
87
|
this.missingBehavior = missingBehavior;
|
|
82
88
|
this.onMissingKey = onMissingKey;
|
|
89
|
+
this.invalidateCaches();
|
|
83
90
|
if (this.followSystem) this.updateLocale();
|
|
84
91
|
this.version += 1;
|
|
85
92
|
this.notifyListeners({ type: "translations", version: this.version });
|
|
@@ -115,6 +122,10 @@ var DefaultI18nEngine = class {
|
|
|
115
122
|
getLocale() {
|
|
116
123
|
return this.i18n.locale;
|
|
117
124
|
}
|
|
125
|
+
resetToSystem() {
|
|
126
|
+
this.localeSource = "system";
|
|
127
|
+
this.updateLocale();
|
|
128
|
+
}
|
|
118
129
|
t(scope, options) {
|
|
119
130
|
var _a, _b, _c;
|
|
120
131
|
if (typeof scope !== "string") {
|
|
@@ -148,7 +159,7 @@ var DefaultI18nEngine = class {
|
|
|
148
159
|
return String(n);
|
|
149
160
|
}
|
|
150
161
|
try {
|
|
151
|
-
return
|
|
162
|
+
return this.getCachedNumberFormat(this.i18n.locale, options).format(n);
|
|
152
163
|
} catch {
|
|
153
164
|
return String(n);
|
|
154
165
|
}
|
|
@@ -158,7 +169,7 @@ var DefaultI18nEngine = class {
|
|
|
158
169
|
return `${n} ${currency}`;
|
|
159
170
|
}
|
|
160
171
|
try {
|
|
161
|
-
return
|
|
172
|
+
return this.getCachedNumberFormat(this.i18n.locale, {
|
|
162
173
|
...options,
|
|
163
174
|
style: "currency",
|
|
164
175
|
currency
|
|
@@ -174,7 +185,7 @@ var DefaultI18nEngine = class {
|
|
|
174
185
|
return d.toISOString();
|
|
175
186
|
}
|
|
176
187
|
try {
|
|
177
|
-
return
|
|
188
|
+
return this.getCachedDateFormat(this.i18n.locale, options).format(d);
|
|
178
189
|
} catch {
|
|
179
190
|
return d.toISOString();
|
|
180
191
|
}
|
|
@@ -195,15 +206,40 @@ var DefaultI18nEngine = class {
|
|
|
195
206
|
isReady() {
|
|
196
207
|
return this.initialized;
|
|
197
208
|
}
|
|
209
|
+
// ─── 缓存管理 ────────────────────────────────────────────
|
|
210
|
+
invalidateCaches() {
|
|
211
|
+
this.numberFormatCache.clear();
|
|
212
|
+
this.dateFormatCache.clear();
|
|
213
|
+
this.localeChainCache.clear();
|
|
214
|
+
}
|
|
215
|
+
getCachedNumberFormat(locale, options) {
|
|
216
|
+
const cacheKey = locale + (options ? JSON.stringify(options) : "");
|
|
217
|
+
let fmt = this.numberFormatCache.get(cacheKey);
|
|
218
|
+
if (!fmt) {
|
|
219
|
+
fmt = new Intl.NumberFormat(locale, options);
|
|
220
|
+
this.numberFormatCache.set(cacheKey, fmt);
|
|
221
|
+
}
|
|
222
|
+
return fmt;
|
|
223
|
+
}
|
|
224
|
+
getCachedDateFormat(locale, options) {
|
|
225
|
+
const cacheKey = locale + (options ? JSON.stringify(options) : "");
|
|
226
|
+
let fmt = this.dateFormatCache.get(cacheKey);
|
|
227
|
+
if (!fmt) {
|
|
228
|
+
fmt = new Intl.DateTimeFormat(locale, options);
|
|
229
|
+
this.dateFormatCache.set(cacheKey, fmt);
|
|
230
|
+
}
|
|
231
|
+
return fmt;
|
|
232
|
+
}
|
|
233
|
+
// ─── 私有方法 ────────────────────────────────────────────
|
|
198
234
|
handleRTL(locale) {
|
|
199
235
|
var _a, _b;
|
|
200
|
-
const
|
|
236
|
+
const isRTL2 = this.isRTLLocale(locale);
|
|
201
237
|
if (typeof ((_a = I18nManager) == null ? void 0 : _a.allowRTL) !== "function" || typeof ((_b = I18nManager) == null ? void 0 : _b.forceRTL) !== "function") {
|
|
202
238
|
return;
|
|
203
239
|
}
|
|
204
|
-
if (I18nManager.isRTL !==
|
|
205
|
-
I18nManager.allowRTL(
|
|
206
|
-
I18nManager.forceRTL(
|
|
240
|
+
if (I18nManager.isRTL !== isRTL2) {
|
|
241
|
+
I18nManager.allowRTL(isRTL2);
|
|
242
|
+
I18nManager.forceRTL(isRTL2);
|
|
207
243
|
}
|
|
208
244
|
}
|
|
209
245
|
notifyListeners(change) {
|
|
@@ -218,6 +254,7 @@ var DefaultI18nEngine = class {
|
|
|
218
254
|
const normalizedLocale = this.normalizeLocaleTag(locale);
|
|
219
255
|
if (this.i18n.locale !== normalizedLocale) {
|
|
220
256
|
this.i18n.locale = normalizedLocale;
|
|
257
|
+
this.invalidateCaches();
|
|
221
258
|
this.handleRTL(normalizedLocale);
|
|
222
259
|
this.version += 1;
|
|
223
260
|
this.notifyListeners({ type: "locale", version: this.version });
|
|
@@ -233,39 +270,36 @@ var DefaultI18nEngine = class {
|
|
|
233
270
|
this.i18n.locale = prevLocale;
|
|
234
271
|
}
|
|
235
272
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
chain.push(
|
|
240
|
-
const parts =
|
|
273
|
+
/** 将 locale 及其自动降级(如 en-US → en)追加到 chain 中 */
|
|
274
|
+
pushWithDegradation(chain, locale) {
|
|
275
|
+
const normalized = this.normalizeLocaleTag(locale);
|
|
276
|
+
chain.push(normalized);
|
|
277
|
+
const parts = normalized.split("-").filter(Boolean);
|
|
241
278
|
for (let i = parts.length - 1; i >= 1; i -= 1) {
|
|
242
279
|
chain.push(parts.slice(0, i).join("-"));
|
|
243
280
|
}
|
|
281
|
+
}
|
|
282
|
+
getLocaleChain(locale) {
|
|
283
|
+
const cached = this.localeChainCache.get(locale);
|
|
284
|
+
if (cached) return cached;
|
|
285
|
+
const chain = [];
|
|
286
|
+
this.pushWithDegradation(chain, locale);
|
|
287
|
+
const normalizedLocale = this.normalizeLocaleTag(locale);
|
|
244
288
|
const extra = this.fallbackLocales ? typeof this.fallbackLocales === "function" ? this.fallbackLocales(normalizedLocale) : this.fallbackLocales : [];
|
|
245
289
|
for (const l of extra) {
|
|
246
|
-
|
|
247
|
-
chain.push(normalizedFallback);
|
|
248
|
-
const fallbackParts = normalizedFallback.split("-").filter(Boolean);
|
|
249
|
-
for (let i = fallbackParts.length - 1; i >= 1; i -= 1) {
|
|
250
|
-
chain.push(fallbackParts.slice(0, i).join("-"));
|
|
251
|
-
}
|
|
290
|
+
this.pushWithDegradation(chain, l);
|
|
252
291
|
}
|
|
253
292
|
if (this.i18n.defaultLocale) {
|
|
254
|
-
|
|
255
|
-
chain.push(normalizedDefault);
|
|
256
|
-
const defaultParts = normalizedDefault.split("-").filter(Boolean);
|
|
257
|
-
for (let i = defaultParts.length - 1; i >= 1; i -= 1) {
|
|
258
|
-
chain.push(defaultParts.slice(0, i).join("-"));
|
|
259
|
-
}
|
|
293
|
+
this.pushWithDegradation(chain, this.i18n.defaultLocale);
|
|
260
294
|
}
|
|
261
295
|
const seen = /* @__PURE__ */ new Set();
|
|
262
|
-
|
|
263
|
-
if (!l) return false;
|
|
264
|
-
|
|
265
|
-
if (seen.has(normalized)) return false;
|
|
266
|
-
seen.add(normalized);
|
|
296
|
+
const result = chain.filter((l) => {
|
|
297
|
+
if (!l || seen.has(l)) return false;
|
|
298
|
+
seen.add(l);
|
|
267
299
|
return true;
|
|
268
300
|
});
|
|
301
|
+
this.localeChainCache.set(locale, result);
|
|
302
|
+
return result;
|
|
269
303
|
}
|
|
270
304
|
hasTranslation(locale, key) {
|
|
271
305
|
var _a;
|
|
@@ -300,7 +334,7 @@ var DefaultI18nEngine = class {
|
|
|
300
334
|
}
|
|
301
335
|
isRTLLocale(locale) {
|
|
302
336
|
var _a;
|
|
303
|
-
const normalized =
|
|
337
|
+
const normalized = this.normalizeLocaleTag(locale);
|
|
304
338
|
const parts = normalized.split("-").filter(Boolean);
|
|
305
339
|
const languageCode = (_a = parts[0]) == null ? void 0 : _a.toLowerCase();
|
|
306
340
|
if (!languageCode) return false;
|
|
@@ -400,13 +434,13 @@ function useI18n() {
|
|
|
400
434
|
}, [context]);
|
|
401
435
|
}
|
|
402
436
|
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
437
|
+
var TAG_REGEX = /<(\/?)([A-Za-z][\w-]*)(?:\s[^>]*?)?(\/?)>/g;
|
|
403
438
|
var parseString = (input) => {
|
|
404
439
|
var _a;
|
|
405
440
|
const root = { type: "tag", name: "root", children: [] };
|
|
406
441
|
const stack = [root];
|
|
407
|
-
const tagRegex = /<(\/?)([A-Za-z][\w-]*)(?:\s[^>]*?)?(\/?)>/g;
|
|
408
442
|
let lastIndex = 0;
|
|
409
|
-
for (const match of input.matchAll(
|
|
443
|
+
for (const match of input.matchAll(TAG_REGEX)) {
|
|
410
444
|
const fullMatch = match[0];
|
|
411
445
|
const isClosing = match[1] === "/";
|
|
412
446
|
const tagName = match[2];
|
|
@@ -436,51 +470,57 @@ var parseString = (input) => {
|
|
|
436
470
|
}
|
|
437
471
|
return root.children;
|
|
438
472
|
};
|
|
473
|
+
var renderAST = (nodes, keyPrefix, components, placeholderToElement) => {
|
|
474
|
+
return nodes.map((node, index) => {
|
|
475
|
+
const key = `${keyPrefix}-${index}`;
|
|
476
|
+
if (node.type === "text") {
|
|
477
|
+
const placeholders = Object.keys(placeholderToElement);
|
|
478
|
+
if (placeholders.length === 0) return node.content;
|
|
479
|
+
const pattern = new RegExp(`(${placeholders.map(escapeRegExp).join("|")})`, "g");
|
|
480
|
+
const parts = node.content.split(pattern);
|
|
481
|
+
if (parts.length === 1) return node.content;
|
|
482
|
+
return /* @__PURE__ */ React.createElement(Fragment, { key }, parts.map((part, i) => {
|
|
483
|
+
if (placeholderToElement[part]) {
|
|
484
|
+
return cloneElement(placeholderToElement[part], { key: `${key}-${i}` });
|
|
485
|
+
}
|
|
486
|
+
return part;
|
|
487
|
+
}));
|
|
488
|
+
} else if (node.type === "tag") {
|
|
489
|
+
const Component = components == null ? void 0 : components[node.name];
|
|
490
|
+
const children = renderAST(node.children, key, components, placeholderToElement);
|
|
491
|
+
if (isValidElement(Component)) {
|
|
492
|
+
return cloneElement(Component, { key }, children.length > 0 ? children : void 0);
|
|
493
|
+
}
|
|
494
|
+
return /* @__PURE__ */ React.createElement(Fragment, { key }, children);
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
});
|
|
498
|
+
};
|
|
439
499
|
var Trans = ({ i18nKey, values, components, ...props }) => {
|
|
440
500
|
const { t: t2 } = useI18n();
|
|
441
|
-
const stringValues = {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
const translatedText = t2(i18nKey, stringValues);
|
|
456
|
-
const ast = parseString(translatedText);
|
|
457
|
-
const renderAST = (nodes, keyPrefix) => {
|
|
458
|
-
return nodes.map((node, index) => {
|
|
459
|
-
const key = `${keyPrefix}-${index}`;
|
|
460
|
-
if (node.type === "text") {
|
|
461
|
-
const placeholders = Object.keys(placeholderToElement);
|
|
462
|
-
if (placeholders.length === 0) return node.content;
|
|
463
|
-
const pattern = new RegExp(`(${placeholders.map(escapeRegExp).join("|")})`, "g");
|
|
464
|
-
const parts = node.content.split(pattern);
|
|
465
|
-
if (parts.length === 1) return node.content;
|
|
466
|
-
return /* @__PURE__ */ React.createElement(Fragment, { key }, parts.map((part, i) => {
|
|
467
|
-
if (placeholderToElement[part]) {
|
|
468
|
-
return cloneElement(placeholderToElement[part], { key: `${key}-${i}` });
|
|
469
|
-
}
|
|
470
|
-
return part;
|
|
471
|
-
}));
|
|
472
|
-
} else if (node.type === "tag") {
|
|
473
|
-
const Component = components == null ? void 0 : components[node.name];
|
|
474
|
-
const children2 = renderAST(node.children, key);
|
|
475
|
-
if (isValidElement(Component)) {
|
|
476
|
-
return cloneElement(Component, { key }, children2.length > 0 ? children2 : void 0);
|
|
501
|
+
const { stringValues, placeholderToElement } = useMemo(() => {
|
|
502
|
+
const sv = {};
|
|
503
|
+
const pte = {};
|
|
504
|
+
if (values) {
|
|
505
|
+
for (const key of Object.keys(values)) {
|
|
506
|
+
const value = values[key];
|
|
507
|
+
if (isValidElement(value)) {
|
|
508
|
+
const placeholder = `__ELEMENT_${key}__`;
|
|
509
|
+
pte[placeholder] = value;
|
|
510
|
+
sv[key] = placeholder;
|
|
511
|
+
} else {
|
|
512
|
+
sv[key] = value == null ? "" : String(value);
|
|
477
513
|
}
|
|
478
|
-
return /* @__PURE__ */ React.createElement(Fragment, { key }, children2);
|
|
479
514
|
}
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
const
|
|
515
|
+
}
|
|
516
|
+
return { stringValues: sv, placeholderToElement: pte };
|
|
517
|
+
}, [values]);
|
|
518
|
+
const translatedText = t2(i18nKey, stringValues);
|
|
519
|
+
const ast = useMemo(() => parseString(translatedText), [translatedText]);
|
|
520
|
+
const children = useMemo(
|
|
521
|
+
() => renderAST(ast, "trans", components, placeholderToElement),
|
|
522
|
+
[ast, components, placeholderToElement]
|
|
523
|
+
);
|
|
484
524
|
return /* @__PURE__ */ React.createElement(Text, { ...props }, children);
|
|
485
525
|
};
|
|
486
526
|
|
|
@@ -490,8 +530,14 @@ var loadTranslations = (translations) => i18nService.loadTranslations(translatio
|
|
|
490
530
|
var setLocale = (locale) => i18nService.setLocale(locale);
|
|
491
531
|
var getLocale = () => i18nService.getLocale();
|
|
492
532
|
var t = (scope, options) => i18nService.t(scope, options);
|
|
533
|
+
var formatNumber = (n, options) => i18nService.formatNumber(n, options);
|
|
534
|
+
var formatCurrency = (n, currency, options) => i18nService.formatCurrency(n, currency, options);
|
|
535
|
+
var formatDate = (date, options) => i18nService.formatDate(date, options);
|
|
536
|
+
var subscribe = (listener) => i18nService.subscribe(listener);
|
|
537
|
+
var isRTL = () => i18nService.isRTL();
|
|
538
|
+
var resetToSystem = () => i18nService.resetToSystem();
|
|
493
539
|
var readyI18n = () => i18nService.ready();
|
|
494
540
|
var isI18nReady = () => i18nService.isReady();
|
|
495
541
|
var index_default = i18nService;
|
|
496
542
|
|
|
497
|
-
export { I18nContext, I18nProvider, Trans, index_default as default, getLocale, initI18n, isI18nReady, loadTranslations, readyI18n, setLocale, t, useI18n, withI18n };
|
|
543
|
+
export { I18nContext, I18nProvider, Trans, index_default as default, formatCurrency, formatDate, formatNumber, getLocale, initI18n, isI18nReady, isRTL, loadTranslations, readyI18n, resetToSystem, setLocale, subscribe, t, useI18n, withI18n };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-i18njs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "一个轻量级、类型安全、零心智负担的 React Native 国际化解决方案。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "ISC",
|
|
10
10
|
"author": "wangws",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"i18n",
|
|
14
|
+
"internationalization",
|
|
15
|
+
"localization",
|
|
16
|
+
"l10n",
|
|
17
|
+
"translation",
|
|
18
|
+
"locale",
|
|
19
|
+
"react",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
11
22
|
"sideEffects": false,
|
|
12
23
|
"main": "dist/index.js",
|
|
13
24
|
"module": "dist/index.mjs",
|