@modern-js/main-doc 3.1.5 → 3.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/docs/en/components/international/init-options-desc.mdx +1 -1
- package/docs/en/components/international/install-command.mdx +4 -17
- package/docs/en/components/international/instance-code.mdx +4 -14
- package/docs/en/components/international/introduce.mdx +4 -1
- package/docs/en/configure/app/source/enable-async-pre-entry.mdx +30 -0
- package/docs/en/configure/app/tools/dev-server.mdx +0 -4
- package/docs/en/guides/advanced-features/international/_meta.json +0 -1
- package/docs/en/guides/advanced-features/international/advanced.mdx +48 -109
- package/docs/en/guides/advanced-features/international/api.mdx +125 -290
- package/docs/en/guides/advanced-features/international/best-practices.mdx +203 -48
- package/docs/en/guides/advanced-features/international/configuration.mdx +108 -315
- package/docs/en/guides/advanced-features/international/locale-detection.mdx +62 -208
- package/docs/en/guides/advanced-features/international/quick-start.mdx +41 -55
- package/docs/en/guides/advanced-features/international/resource-loading.mdx +63 -322
- package/docs/en/guides/advanced-features/international/routing.mdx +60 -138
- package/docs/en/guides/advanced-features/international.mdx +19 -27
- package/docs/en/guides/basic-features/alias.mdx +1 -1
- package/docs/en/guides/basic-features/html.mdx +2 -2
- package/docs/en/guides/basic-features/static-assets.mdx +1 -2
- package/docs/en/guides/concept/entries.mdx +2 -2
- package/docs/zh/components/international/init-options-desc.mdx +1 -1
- package/docs/zh/components/international/install-command.mdx +4 -16
- package/docs/zh/components/international/instance-code.mdx +4 -14
- package/docs/zh/components/international/introduce.mdx +5 -2
- package/docs/zh/configure/app/source/enable-async-pre-entry.mdx +77 -0
- package/docs/zh/configure/app/tools/dev-server.mdx +0 -4
- package/docs/zh/guides/advanced-features/bff/function.mdx +2 -2
- package/docs/zh/guides/advanced-features/international/_meta.json +0 -1
- package/docs/zh/guides/advanced-features/international/advanced.mdx +48 -109
- package/docs/zh/guides/advanced-features/international/api.mdx +126 -292
- package/docs/zh/guides/advanced-features/international/best-practices.mdx +204 -49
- package/docs/zh/guides/advanced-features/international/configuration.mdx +105 -318
- package/docs/zh/guides/advanced-features/international/locale-detection.mdx +62 -236
- package/docs/zh/guides/advanced-features/international/quick-start.mdx +40 -54
- package/docs/zh/guides/advanced-features/international/resource-loading.mdx +62 -324
- package/docs/zh/guides/advanced-features/international/routing.mdx +58 -136
- package/docs/zh/guides/advanced-features/international.mdx +19 -26
- package/docs/zh/guides/basic-features/alias.mdx +1 -1
- package/docs/zh/guides/basic-features/html.mdx +2 -2
- package/docs/zh/guides/basic-features/static-assets.mdx +1 -2
- package/docs/zh/guides/concept/entries.mdx +2 -2
- package/package.json +4 -4
- package/docs/en/components/rspackPrecautions.mdx +0 -6
- package/docs/en/guides/advanced-features/international/basic.mdx +0 -417
- package/docs/zh/components/rspackPrecautions.mdx +0 -6
- package/docs/zh/guides/advanced-features/international/basic.mdx +0 -416
|
@@ -6,23 +6,26 @@ title: Best Practices
|
|
|
6
6
|
|
|
7
7
|
## Resource File Organization
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Namespaces are used to split translation files by business module. The default namespace is `translation`. Splitting by module can reduce the initial loading size and load only the namespaces that are actually used.
|
|
10
|
+
|
|
11
|
+
Recommended directory structure:
|
|
10
12
|
|
|
11
13
|
```
|
|
12
14
|
locales/
|
|
13
15
|
├── en/
|
|
14
|
-
│ ├── translation.json
|
|
15
|
-
│ ├── common.json
|
|
16
|
-
│ └── errors.json
|
|
16
|
+
│ ├── translation.json <- Default namespace for common text
|
|
17
|
+
│ ├── common.json <- Common UI text such as buttons and labels
|
|
18
|
+
│ └── errors.json <- Error messages
|
|
17
19
|
└── zh/
|
|
18
20
|
├── translation.json
|
|
19
21
|
├── common.json
|
|
20
22
|
└── errors.json
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
Declare multiple namespaces:
|
|
24
26
|
|
|
25
27
|
```ts
|
|
28
|
+
// src/modern.runtime.ts
|
|
26
29
|
export default defineRuntimeConfig({
|
|
27
30
|
i18n: {
|
|
28
31
|
initOptions: {
|
|
@@ -33,25 +36,178 @@ export default defineRuntimeConfig({
|
|
|
33
36
|
});
|
|
34
37
|
```
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
Use a specific namespace in components:
|
|
37
40
|
|
|
38
41
|
```tsx
|
|
39
42
|
import { useTranslation } from 'react-i18next';
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
// Use a single namespace.
|
|
45
|
+
function MyButton() {
|
|
42
46
|
const { t } = useTranslation('common');
|
|
47
|
+
return <button>{t('submit')}</button>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Use multiple namespaces and access keys with namespace:key.
|
|
51
|
+
function Dashboard() {
|
|
52
|
+
const { t } = useTranslation(['dashboard', 'common']);
|
|
53
|
+
return (
|
|
54
|
+
<header>
|
|
55
|
+
<h1>{t('dashboard:title')}</h1>
|
|
56
|
+
<button>{t('common:button.refresh')}</button>
|
|
57
|
+
</header>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
// keyPrefix can omit repeated prefixes.
|
|
62
|
+
function ButtonGroup() {
|
|
63
|
+
const { t } = useTranslation('common', { keyPrefix: 'button' });
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<button>{t('submit')}</button> {/* common:button.submit */}
|
|
67
|
+
<button>{t('cancel')}</button> {/* common:button.cancel */}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
45
70
|
}
|
|
46
71
|
```
|
|
47
72
|
|
|
48
|
-
##
|
|
73
|
+
## Translation Key Naming
|
|
74
|
+
|
|
75
|
+
The quality of translation key names directly affects maintenance cost. Recommendations:
|
|
76
|
+
|
|
77
|
+
- **Use semantic words** and avoid abbreviations: `button.submit` is better than `btn.sbm`.
|
|
78
|
+
- **Use module-based prefixes**: `dashboard.table.header`, `auth.login.title`.
|
|
79
|
+
- **Do not use complete source-language text as keys**: Keys should be stable identifiers, not the translation content itself.
|
|
80
|
+
- **Use dots for hierarchy**: Match the nested JSON structure.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
// Recommended
|
|
84
|
+
{
|
|
85
|
+
"page": {
|
|
86
|
+
"title": "User Settings",
|
|
87
|
+
"description": "Manage your account information"
|
|
88
|
+
},
|
|
89
|
+
"form": {
|
|
90
|
+
"username": { "label": "Username", "placeholder": "Enter username" },
|
|
91
|
+
"submit": "Save changes"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
t('page.title')
|
|
98
|
+
t('form.username.label')
|
|
99
|
+
t('form.submit', { defaultValue: 'Save' }) // defaultValue prevents showing the key string when the key is missing.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Plurals
|
|
103
|
+
|
|
104
|
+
i18next automatically selects the correct plural form based on the `count` parameter. **Note: since i18next v21, JSON v4 format is used by default**. Plural suffixes changed to CLDR standards such as `_zero`, `_one`, and `_other`; the old `_plural` format is deprecated.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
// locales/en/translation.json (JSON v4 format)
|
|
108
|
+
{
|
|
109
|
+
"item_zero": "No items",
|
|
110
|
+
"item_one": "{{count}} item",
|
|
111
|
+
"item_other": "{{count}} items"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
// locales/zh/translation.json
|
|
117
|
+
{
|
|
118
|
+
"item_zero": "没有条目",
|
|
119
|
+
"item_other": "{{count}} 个条目"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
t('item', { count: 0 }) // "没有条目" / "No items"
|
|
125
|
+
t('item', { count: 1 }) // "没有条目" / "1 item" (Chinese usually has only one form)
|
|
126
|
+
t('item', { count: 5 }) // "5 个条目" / "5 items"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Different languages have different plural rules. English has singular and plural, while Russian has one/few/many forms. i18next automatically matches CLDR rules based on the language code.
|
|
130
|
+
|
|
131
|
+
:::tip
|
|
132
|
+
Use the `_plural` format only if your project uses an old i18next version or explicitly configures `compatibilityJSON: 'v3'`. New projects should use v4 format.
|
|
133
|
+
:::
|
|
134
|
+
|
|
135
|
+
## Nested Keys
|
|
136
|
+
|
|
137
|
+
Nested structures can clearly reflect UI hierarchy and are accessed with dots:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"modal": {
|
|
142
|
+
"confirm": {
|
|
143
|
+
"title": "Confirm deletion",
|
|
144
|
+
"message": "This action cannot be undone. Continue?",
|
|
145
|
+
"actions": {
|
|
146
|
+
"ok": "Confirm",
|
|
147
|
+
"cancel": "Cancel"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
t('modal.confirm.title')
|
|
156
|
+
t('modal.confirm.actions.ok')
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Avoid nesting deeper than 4 levels. Excessive depth makes key names long and hard to maintain.
|
|
160
|
+
|
|
161
|
+
## Formatting Interpolation
|
|
49
162
|
|
|
50
|
-
|
|
163
|
+
Use the `interpolation.format` function to handle number, date, currency, and other formatting consistently, instead of calling `Intl` APIs separately in each component:
|
|
51
164
|
|
|
52
|
-
|
|
165
|
+
```ts
|
|
166
|
+
// src/modern.runtime.ts
|
|
167
|
+
export default defineRuntimeConfig({
|
|
168
|
+
i18n: {
|
|
169
|
+
initOptions: {
|
|
170
|
+
interpolation: {
|
|
171
|
+
escapeValue: false, // React already escapes text. Disable this to avoid double escaping.
|
|
172
|
+
format(value, format, lng) {
|
|
173
|
+
if (format === 'currency') {
|
|
174
|
+
return new Intl.NumberFormat(lng, {
|
|
175
|
+
style: 'currency',
|
|
176
|
+
currency: lng === 'zh' ? 'CNY' : 'USD',
|
|
177
|
+
}).format(Number(value));
|
|
178
|
+
}
|
|
179
|
+
if (format === 'date') {
|
|
180
|
+
return new Intl.DateTimeFormat(lng, { dateStyle: 'medium' }).format(
|
|
181
|
+
value instanceof Date ? value : new Date(value),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Use `, format` in translation files to specify the formatting type:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"price": "Current price: {{value, currency}}",
|
|
197
|
+
"expiry": "Expiry date: {{date, date}}"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
t('price', { value: 99.5 }) // "当前价格:¥99.50" (zh) / "$99.50" (en)
|
|
203
|
+
t('expiry', { date: new Date() }) // "到期日:2025年5月12日" (zh)
|
|
204
|
+
```
|
|
53
205
|
|
|
54
|
-
|
|
206
|
+
## Error Handling
|
|
207
|
+
|
|
208
|
+
### Loading State Handling
|
|
209
|
+
|
|
210
|
+
When using a custom backend, translation resources are loaded asynchronously. Use `isResourcesReady` to handle the loading state:
|
|
55
211
|
|
|
56
212
|
```tsx
|
|
57
213
|
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
@@ -61,75 +217,74 @@ function MyComponent() {
|
|
|
61
217
|
const { isResourcesReady } = useModernI18n();
|
|
62
218
|
const { t } = useTranslation();
|
|
63
219
|
|
|
64
|
-
// Check if resources are loaded and ready
|
|
65
220
|
if (!isResourcesReady) {
|
|
66
|
-
return <div>Loading
|
|
221
|
+
return <div>Loading translations...</div>;
|
|
67
222
|
}
|
|
68
223
|
|
|
69
224
|
return <div>{t('content', { defaultValue: 'Default content' })}</div>;
|
|
70
225
|
}
|
|
71
226
|
```
|
|
72
227
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
For simpler cases, you can also check i18next initialization status:
|
|
228
|
+
Static resources (HTTP/FS backend) load quickly, so loading state handling is usually unnecessary. If you need to check, use `i18n.isInitialized`:
|
|
76
229
|
|
|
77
230
|
```tsx
|
|
78
|
-
|
|
231
|
+
const { t, i18n } = useTranslation();
|
|
232
|
+
if (!i18n.isInitialized) return null;
|
|
233
|
+
```
|
|
79
234
|
|
|
80
|
-
|
|
81
|
-
const { t, i18n } = useTranslation();
|
|
235
|
+
### Missing Translation Keys
|
|
82
236
|
|
|
83
|
-
|
|
84
|
-
if (!i18n.isInitialized) {
|
|
85
|
-
return <div>Loading...</div>;
|
|
86
|
-
}
|
|
237
|
+
Provide fallback text with `defaultValue` to prevent key strings from appearing in the UI:
|
|
87
238
|
|
|
88
|
-
|
|
239
|
+
```tsx
|
|
240
|
+
t('missing.key', { defaultValue: 'Default text' })
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
In development, you can enable `saveMissing` to output missing keys to the console for debugging:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
initOptions: {
|
|
247
|
+
fallbackLng: 'en',
|
|
248
|
+
saveMissing: true, // Recommended only in development.
|
|
89
249
|
}
|
|
90
250
|
```
|
|
91
251
|
|
|
92
|
-
|
|
93
|
-
`isResourcesReady` is more accurate for SDK backend scenarios as it checks if all required resources are actually loaded, not just if the instance is initialized.
|
|
252
|
+
### Network Failures and Resource 404s
|
|
94
253
|
|
|
95
|
-
|
|
254
|
+
When translation file loading fails, i18next falls back to the resources for `fallbackLng`. If those also fail, `t()` returns the key string directly. Recommendations:
|
|
255
|
+
|
|
256
|
+
- In production, make sure the translation files for `fallbackLng`, usually `en`, are complete and stable.
|
|
257
|
+
- When using chained backend, use local files as the fallback to reduce dependency on remote services.
|
|
96
258
|
|
|
97
259
|
## Type Safety
|
|
98
260
|
|
|
99
|
-
|
|
261
|
+
Extend react-i18next type definitions so TypeScript can check whether translation keys exist and provide autocomplete:
|
|
100
262
|
|
|
101
263
|
```ts
|
|
102
264
|
// types/i18n.d.ts
|
|
103
265
|
import 'react-i18next';
|
|
266
|
+
import type translation from '../locales/en/translation.json';
|
|
267
|
+
import type common from '../locales/en/common.json';
|
|
104
268
|
|
|
105
269
|
declare module 'react-i18next' {
|
|
106
270
|
interface CustomTypeOptions {
|
|
107
271
|
defaultNS: 'translation';
|
|
108
272
|
resources: {
|
|
109
|
-
translation:
|
|
110
|
-
|
|
111
|
-
world: string;
|
|
112
|
-
welcome: string;
|
|
113
|
-
};
|
|
114
|
-
common: {
|
|
115
|
-
submit: string;
|
|
116
|
-
cancel: string;
|
|
117
|
-
};
|
|
273
|
+
translation: typeof translation;
|
|
274
|
+
common: typeof common;
|
|
118
275
|
};
|
|
119
276
|
}
|
|
120
277
|
}
|
|
121
278
|
```
|
|
122
279
|
|
|
123
|
-
|
|
280
|
+
Referencing JSON file types directly is less likely to drift from actual translation files than manually written interfaces:
|
|
124
281
|
|
|
125
282
|
```tsx
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const { t } = useTranslation();
|
|
130
|
-
|
|
131
|
-
// TypeScript will check if the key exists
|
|
132
|
-
return <div>{t('hello')}</div>; // ✅ Type safe
|
|
133
|
-
// return <div>{t('invalid')}</div>; // ❌ TypeScript error
|
|
134
|
-
}
|
|
283
|
+
const { t } = useTranslation();
|
|
284
|
+
t('welcome'); // TypeScript autocomplete
|
|
285
|
+
t('nonExistent'); // TypeScript error
|
|
135
286
|
```
|
|
287
|
+
|
|
288
|
+
:::tip Large projects
|
|
289
|
+
When there are many translation files, manually maintaining type files is costly. You can use [i18next-parser](https://github.com/i18next/i18next-parser) or [i18next-resources-for-ts](https://github.com/i18next/i18next-resources-for-ts) to generate TypeScript types from translation files automatically.
|
|
290
|
+
:::
|