@lakutata/i18n 3.0.0 → 3.1.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/README.md +82 -79
- package/README.zh-CN.md +307 -0
- package/dist/cjs/i18n/I18n.d.ts +1 -1
- package/dist/cjs/i18n/I18n.js +1 -1
- package/dist/esm/i18n/I18n.js +1 -1
- package/dist/types/i18n/I18n.d.ts +1 -1
- package/package.json +26 -4
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# @lakutata/i18n
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
English | [中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
The internationalization component of the lakutata framework. It holds the application's messages for each language,
|
|
6
|
+
by key, written in [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) and
|
|
7
|
+
formatted for their language by the built-in `Intl` of Node.js (numbers, currencies, dates, plurals, selects), with no
|
|
8
|
+
third-party library. It also provides the middleware that chooses the language of each request (so that the request's
|
|
9
|
+
`Translator`, validation errors and exceptions are in that language), the translation of the Commander CLI help, and
|
|
10
|
+
tools that check the translations. The umbrella package `lakutata` re-exports it as `lakutata/com/i18n`.
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
@@ -13,25 +15,25 @@ umbrella package `lakutata` re-exports it as `lakutata/com/i18n`.
|
|
|
13
15
|
npm install lakutata
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
The component needs nothing
|
|
17
|
-
(`lakutata/com/entrypoint`, the optional peer dependency `@lakutata/entrypoint`),
|
|
18
|
+
The component needs nothing else. `I18nMiddleware` and `commanderTranslator` use the entrypoint component
|
|
19
|
+
(`lakutata/com/entrypoint`, the optional peer dependency `@lakutata/entrypoint`), which `lakutata` includes.
|
|
18
20
|
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
21
23
|
### The component and its messages
|
|
22
24
|
|
|
23
|
-
Register the component with `buildI18nOptions()` in the `components
|
|
24
|
-
middleware and `commanderTranslator` use), and give it its messages:
|
|
25
|
+
Register the component with `buildI18nOptions()` in the application's `components`, under the name `i18n` (the name
|
|
26
|
+
the middleware and `commanderTranslator` use), and give it its messages:
|
|
25
27
|
|
|
26
|
-
- `directory`: one or
|
|
27
|
-
|
|
28
|
-
its tag whose files and subdirectories prefix their keys (`zh-TW/order.json` gives `order.*`,
|
|
28
|
+
- `directory`: one or more directories (a path, or an application alias such as `@locales`), read when the component
|
|
29
|
+
is created. A language is either a JSON file named after its tag (`en.json`, `zh-CN.json`), or a directory named
|
|
30
|
+
after its tag, whose files and subdirectories prefix their keys (`zh-TW/order.json` gives `order.*`,
|
|
29
31
|
`zh-TW/admin/user.json` gives `admin.user.*`). Both layouts can be mixed.
|
|
30
|
-
- `messages`: the messages by language in the code (object literals or imported JSON), bundled with it
|
|
31
|
-
the directories
|
|
32
|
+
- `messages`: the messages by language, in the code (object literals or imported JSON), so they are bundled with it.
|
|
33
|
+
They are added after the directories and replace the same keys.
|
|
32
34
|
|
|
33
|
-
The nesting of the JSON
|
|
34
|
-
|
|
35
|
+
The nesting of the JSON gives the keys: `{"order": {"count": "..."}}` holds the key `order.count`. No part of a key can
|
|
36
|
+
contain `.`.
|
|
35
37
|
|
|
36
38
|
```typescript
|
|
37
39
|
import {Application} from 'lakutata'
|
|
@@ -55,7 +57,7 @@ Application
|
|
|
55
57
|
fallback: {'zh-TW': 'zh-CN'},
|
|
56
58
|
//The currency of {price, number, currency} when the message and its arguments give none
|
|
57
59
|
currency: 'CNY',
|
|
58
|
-
//The time zone of
|
|
60
|
+
//The time zone of dates and times (the process's by default)
|
|
59
61
|
timeZone: 'Asia/Shanghai'
|
|
60
62
|
})
|
|
61
63
|
}
|
|
@@ -71,34 +73,34 @@ Application
|
|
|
71
73
|
|
|
72
74
|
| Option | Default | Description |
|
|
73
75
|
|---|---|---|
|
|
74
|
-
| `directory` | none | The directories
|
|
75
|
-
| `messages` | none | The messages by language given in the code
|
|
76
|
+
| `directory` | none | The message directories (aliases accepted); later ones replace the same keys |
|
|
77
|
+
| `messages` | none | The messages by language, given in the code and added after the directories |
|
|
76
78
|
| `locales` | the default language, then the languages of the messages (alphabetical) | The supported languages, which `negotiate()` chooses from |
|
|
77
79
|
| `defaultLocale` | `'en'` | The default language |
|
|
78
|
-
| `fallback` | none | The languages
|
|
79
|
-
| `missing` | `'throw'`, `'key'` when `NODE_ENV=production` | `'throw'` throws on a missing message, argument or currency; `'key'`
|
|
80
|
+
| `fallback` | none | The languages to look in next when a language lacks a message (`{'zh-TW': 'zh-CN'}`, or a list) |
|
|
81
|
+
| `missing` | `'throw'`, `'key'` when `NODE_ENV=production` | `'throw'` throws on a missing message, argument or currency; `'key'` returns the key, writes a missing argument as `{name}` and a currency argument without a currency as a plain number, emitting an `I18nWarning` once for each (per key and language, or per message and argument) |
|
|
80
82
|
| `currency` | none | The ISO 4217 currency of `{price, number, currency}` when neither the style nor a `currency` argument gives one (without it, such a message throws, or gives a plain number with `missing: 'key'`) |
|
|
81
|
-
| `timeZone` | the process's | The IANA time zone of
|
|
83
|
+
| `timeZone` | the process's | The IANA time zone of dates and times |
|
|
82
84
|
| `validationMessages` | `true` | Whether the built-in validation messages of the languages are registered |
|
|
83
85
|
|
|
84
|
-
**Fallbacks.** A message missing in a language is looked up in its `fallback` languages (and theirs), then in its
|
|
85
|
-
specific tags (`zh-Hant-TW
|
|
86
|
-
|
|
87
|
-
`I18nMissingKeyException`, or
|
|
86
|
+
**Fallbacks.** A message missing in a language is looked up in its `fallback` languages (and in theirs), then in its
|
|
87
|
+
less specific tags (for `zh-Hant-TW`: `zh-Hant`, then `zh`), then in the default language. It is always formatted in the
|
|
88
|
+
requested language: an English message used for a German request writes `1.000`. A key found nowhere throws
|
|
89
|
+
`I18nMissingKeyException`, or returns the key itself with `missing: 'key'`.
|
|
88
90
|
|
|
89
|
-
**
|
|
90
|
-
`has(key, locale)`, `format(message, params, locale)`, and `translator(locale)`, a translator
|
|
91
|
+
**Outside a request** (in a schedule or a singleton component), the component provides `t(key, params, locale)`,
|
|
92
|
+
`has(key, locale)`, `format(message, params, locale)`, and `translator(locale)`, a translator for one language. None of
|
|
91
93
|
them negotiates the language: an unsupported language is looked up as given, then falls back.
|
|
92
94
|
|
|
93
95
|
**At run time**, `addMessages(locale, messages)` adds or replaces messages. A new language becomes supported
|
|
94
|
-
(`getLocales()`, `negotiate()`) unless the `locales` option lists the languages
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
(`getLocales()`, `negotiate()`) unless the `locales` option lists the languages; its validation messages are registered
|
|
97
|
+
as at creation in any case. The promise that `addMessages()` returns resolves once they are; `t()` gives the new
|
|
98
|
+
messages as soon as `addMessages()` returns.
|
|
97
99
|
|
|
98
100
|
### The translator of a request
|
|
99
101
|
|
|
100
|
-
With `I18nMiddleware` in the `middlewares
|
|
101
|
-
|
|
102
|
+
With `I18nMiddleware` in the entrypoint's `middlewares`, each request has a language, and its objects (the controller,
|
|
103
|
+
the `@Scoped` providers, the access control rules) inject the same `Translator` for that language:
|
|
102
104
|
|
|
103
105
|
```typescript
|
|
104
106
|
import {Application} from 'lakutata'
|
|
@@ -142,30 +144,30 @@ Application.run(() => ({
|
|
|
142
144
|
}))
|
|
143
145
|
```
|
|
144
146
|
|
|
145
|
-
A translator (`Translator`, and the `ITranslator`
|
|
146
|
-
`format(message, params)`, `number(value, options)` (`Intl.NumberFormat`) and `date(value, options)`
|
|
147
|
-
(`Intl.DateTimeFormat`, in the `timeZone` option).
|
|
147
|
+
A translator (`Translator`, and the `ITranslator` returned by `translator(locale)`) has `locale`, `t(key, params)`,
|
|
148
|
+
`has(key)`, `format(message, params)`, `number(value, options)` (`Intl.NumberFormat`) and `date(value, options)`
|
|
149
|
+
(`Intl.DateTimeFormat`, in the `timeZone` option). Without the middleware, a request's `Translator` throws
|
|
148
150
|
`I18nTranslatorException`.
|
|
149
151
|
|
|
150
152
|
### The language of each request
|
|
151
153
|
|
|
152
|
-
The middleware reads the languages a request asks for, preferred first,
|
|
153
|
-
|
|
154
|
+
The middleware reads the languages a request asks for, preferred first, negotiates them with the supported languages
|
|
155
|
+
(the default language when none matches) and sets `context.locale`:
|
|
154
156
|
|
|
155
157
|
| Transport | Languages read, preferred first |
|
|
156
158
|
|---|---|
|
|
157
159
|
| HTTP (Express, Fastify) | The query parameter `?lang=`, the cookie `lang`, then `Accept-Language` (by quality) |
|
|
158
|
-
| Socket.IO | The query parameter `lang`, the cookie `lang` and `Accept-Language` of the connection
|
|
159
|
-
| HTTP service adapter, JSON-RPC adapter (HTTP, WebSocket) | The query parameter `lang`, the cookie `lang` and `Accept-Language` of the HTTP request (for WebSocket, of the request
|
|
160
|
+
| Socket.IO | The query parameter `lang`, the cookie `lang` and `Accept-Language` of the connection handshake |
|
|
161
|
+
| HTTP service adapter, JSON-RPC adapter (HTTP, WebSocket) | The query parameter `lang`, the cookie `lang` and `Accept-Language` of the HTTP request (for WebSocket, of the request that opened the connection) |
|
|
160
162
|
| Command line, Electron IPC, JSON-RPC over stdio | The variable named by `environmentVariable` (none by default), `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG`, then the process's language |
|
|
161
163
|
| Schedules | None: the default language |
|
|
162
164
|
|
|
163
|
-
A query or cookie value
|
|
164
|
-
|
|
165
|
-
the order of the supported languages).
|
|
165
|
+
A query or cookie value that is not a language tag is skipped. Negotiation tries each requested language in turn: the
|
|
166
|
+
same tag, then its language alone (`en-GB` gives `en`), then another tag of the same language (`pt-PT` gives `pt-BR`,
|
|
167
|
+
in the order of the supported languages).
|
|
166
168
|
|
|
167
|
-
A subclass
|
|
168
|
-
`environmentVariable`, or the languages read (`requested(context)`):
|
|
169
|
+
A subclass can change the component (`@Inject('...') protected readonly i18n`), the `queryParameter` and `cookie`
|
|
170
|
+
names, `environmentVariable`, or the languages read (`requested(context)`):
|
|
169
171
|
|
|
170
172
|
```typescript
|
|
171
173
|
import {type BaseContext} from 'lakutata/com/entrypoint'
|
|
@@ -188,20 +190,20 @@ export class LanguageMiddleware extends I18nMiddleware {
|
|
|
188
190
|
|
|
189
191
|
| Syntax | Example |
|
|
190
192
|
|---|---|
|
|
191
|
-
| Argument | `Hello, {name}`: a number is formatted
|
|
193
|
+
| Argument | `Hello, {name}`: a number is formatted for the language, a `Date` as a date, anything else as a string |
|
|
192
194
|
| Number | `{n, number}`, `{n, number, integer}`, `{n, number, percent}` |
|
|
193
|
-
| Currency | `{price, number, currency}` (the `currency` argument, else the `currency` option), `{price, number, currency/EUR}` |
|
|
195
|
+
| Currency | `{price, number, currency}` (the `currency` argument, or else the `currency` option), `{price, number, currency/EUR}` |
|
|
194
196
|
| Date, time | `{at, date, short}`, `{at, time, long}`; the styles `short`, `medium` (the default), `long`, `full`; a `Date`, a timestamp, a date string or an object with `toDate()` |
|
|
195
|
-
| Plural | `{count, plural, =0 {none} one {# item} other {# items}}`: the `=n` options first, then the plural rules of the language; `#` is the number; `offset:1` supported |
|
|
197
|
+
| Plural | `{count, plural, =0 {none} one {# item} other {# items}}`: the `=n` options first, then the plural rules of the language; `#` is the number; `offset:1` is supported |
|
|
196
198
|
| Ordinal | `{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}` |
|
|
197
199
|
| Select | `{role, select, admin {Administrator} other {User}}` |
|
|
198
200
|
| Quoting | `''` writes an apostrophe; `'{literal}'` writes braces (`'#'` writes `#` in a plural) |
|
|
199
201
|
|
|
200
202
|
`plural`, `selectordinal` and `select` need an `other` option. A message is parsed the first time it is used; a syntax
|
|
201
|
-
error throws `I18nMessageSyntaxException
|
|
203
|
+
error throws `I18nMessageSyntaxException`, which names its language, key and position.
|
|
202
204
|
|
|
203
|
-
The application can declare its keys and their arguments, so that a wrong key or a missing argument fails
|
|
204
|
-
|
|
205
|
+
The application can declare its keys and their arguments, so that a wrong key or a missing argument fails compilation
|
|
206
|
+
(`lakutata i18n types` generates the declaration from the default language; without it, any key is accepted):
|
|
205
207
|
|
|
206
208
|
```text
|
|
207
209
|
//src/i18n.d.ts
|
|
@@ -217,11 +219,11 @@ declare module 'lakutata/com/i18n' {
|
|
|
217
219
|
|
|
218
220
|
### The validation messages and the exceptions
|
|
219
221
|
|
|
220
|
-
With the middleware,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
`
|
|
224
|
-
code, in the syntax of the validation templates (not ICU):
|
|
222
|
+
With the middleware, a request's validation errors (from its input DTO, `@Accept`/`@Return`, and the `DTO.validate`
|
|
223
|
+
calls in its code) are in its language. When it is created (and for a language added by `addMessages()`), the component
|
|
224
|
+
registers the built-in validation messages of its languages. 20 languages are built in: `en`, `zh-CN`, `zh-TW`, `ja`,
|
|
225
|
+
`ko`, `fr`, `de`, `es`, `pt-BR`, `it`, `nl`, `pl`, `ru`, `uk`, `tr`, `vi`, `th`, `id`, `hi`, `ar`. The application's
|
|
226
|
+
messages under `validation` are layered on top, by error code, in the syntax of the validation templates (not ICU):
|
|
225
227
|
|
|
226
228
|
```json
|
|
227
229
|
{
|
|
@@ -232,12 +234,12 @@ code, in the syntax of the validation templates (not ICU):
|
|
|
232
234
|
}
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
A language's validation messages are those of the languages it falls back to, replaced by its own built-in
|
|
236
|
-
by its own application messages. `validationMessages: false` registers the application's messages
|
|
237
|
+
A language's validation messages are those of the languages it falls back to, replaced by its own built-in messages,
|
|
238
|
+
then by its own application messages. `validationMessages: false` registers only the application's messages.
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
language, formatted with the parameters of its template (`{id}`, or `{0}`, `{1}` for
|
|
240
|
-
its `errno`
|
|
240
|
+
When a request throws an exception whose `errno` has a message under `errors.<errno>`, the exception gets that message
|
|
241
|
+
in the request's language, formatted with the parameters of its template (`{id}`, or `{0}`, `{1}` for parameters
|
|
242
|
+
passed one by one); its `errno` stays unchanged:
|
|
241
243
|
|
|
242
244
|
```typescript
|
|
243
245
|
import {Exception} from 'lakutata'
|
|
@@ -294,7 +296,7 @@ Application.run(() => ({
|
|
|
294
296
|
### Checking the translations
|
|
295
297
|
|
|
296
298
|
`loadCatalogs`, `checkCatalogs` and `generateMessageTypes` are the functions behind `lakutata i18n check` and
|
|
297
|
-
`lakutata i18n types`, for a script or
|
|
299
|
+
`lakutata i18n types`, for use in a script or in CI:
|
|
298
300
|
|
|
299
301
|
```typescript
|
|
300
302
|
import {checkCatalogs, loadCatalogs, type CatalogIssue, type Catalogs} from 'lakutata/com/i18n'
|
|
@@ -310,38 +312,39 @@ if (issues.some((issue: CatalogIssue): boolean => issue.level === 'error')) proc
|
|
|
310
312
|
| Export | What it is |
|
|
311
313
|
|---|---|
|
|
312
314
|
| `I18n` | The component: `t`, `has`, `format`, `translator`, `negotiate`, `addMessages`, `getLocales`, `getDefaultLocale`, `getKeys`, `getTimeZone` |
|
|
313
|
-
| `buildI18nOptions` | Builds the component options
|
|
315
|
+
| `buildI18nOptions` | Builds the component options for `I18n` |
|
|
314
316
|
| `I18nOptions` | The options: `directory`, `messages`, `locales`, `defaultLocale`, `fallback`, `missing`, `currency`, `timeZone`, `validationMessages` |
|
|
315
|
-
| `Translator` | The scoped provider injected
|
|
316
|
-
| `ITranslator` | A translator
|
|
317
|
-
| `LocaleTranslator` | The `ITranslator`
|
|
318
|
-
| `I18nMiddleware` | The entrypoint middleware
|
|
317
|
+
| `Translator` | The scoped provider injected into the objects of a request, in the request's language |
|
|
318
|
+
| `ITranslator` | A translator for one language: `locale`, `t`, `has`, `format`, `number`, `date` |
|
|
319
|
+
| `LocaleTranslator` | The `ITranslator` returned by `I18n.translator()` |
|
|
320
|
+
| `I18nMiddleware` | The entrypoint middleware that chooses the language of each request (`lakutata/com/i18n`, `@lakutata/i18n/middleware`) |
|
|
319
321
|
| `commanderTranslator`, `CommanderTranslatorOptions` | The translation of the Commander CLI help, and its options (`lakutata/com/i18n`, `@lakutata/i18n/middleware`) |
|
|
320
322
|
| `I18nMessages`, `MessageKey`, `MessageParams` | The declaration of the application's keys and arguments, and the types derived from it |
|
|
321
|
-
| `NestedMessages`, `Catalogs` |
|
|
323
|
+
| `NestedMessages`, `Catalogs` | A language's messages, nested by key part; the loaded messages by language and key |
|
|
322
324
|
| `loadCatalogs`, `checkCatalogs`, `CatalogIssue`, `generateMessageTypes` | Load the message directories, check the translations, generate the key types |
|
|
323
|
-
| `normalizeLocale`, `parseAcceptLanguage`, `environmentLocales`, `negotiateLocale`, `fallbackChain` |
|
|
324
|
-
| `parseMessage`, `messageArguments`, `MessagePart` | The ICU MessageFormat parser and the arguments of a message |
|
|
325
|
+
| `normalizeLocale`, `parseAcceptLanguage`, `environmentLocales`, `negotiateLocale`, `fallbackChain` | Language tag helpers |
|
|
326
|
+
| `parseMessage`, `messageArguments`, `MessagePart` | The ICU MessageFormat parser, and the arguments of a message |
|
|
325
327
|
| `I18nCatalogException`, `I18nMessageSyntaxException`, `I18nMissingKeyException`, `I18nMissingParameterException`, `I18nTranslatorException` | The exceptions |
|
|
326
328
|
|
|
327
|
-
All are exported by `lakutata/com/i18n`; `@lakutata/i18n` exports
|
|
328
|
-
`@lakutata/i18n/middleware
|
|
329
|
+
All are exported by `lakutata/com/i18n`; `@lakutata/i18n` exports all but the middleware ones, which
|
|
330
|
+
`@lakutata/i18n/middleware` exports. The typings document each declaration, with examples.
|
|
329
331
|
|
|
330
332
|
## Errors
|
|
331
333
|
|
|
332
334
|
| Exception | When |
|
|
333
335
|
|---|---|
|
|
334
|
-
| `I18nCatalogException` (`E_I18N_CATALOG`) |
|
|
335
|
-
| `I18nMessageSyntaxException` (`E_I18N_MESSAGE_SYNTAX`) | A message is not valid ICU MessageFormat, when it is first used (or parsed by `format()`, `parseMessage()
|
|
336
|
+
| `I18nCatalogException` (`E_I18N_CATALOG`) | Creating the component, `addMessages()` or `loadCatalogs()`: a missing directory, invalid JSON, a file or directory name that is not a language tag, an invalid key or value, an option that is not a language tag, an alias without an application; `generateMessageTypes()`: the default language has no messages |
|
|
337
|
+
| `I18nMessageSyntaxException` (`E_I18N_MESSAGE_SYNTAX`) | A message is not valid ICU MessageFormat, when it is first used (or parsed by `format()`, `parseMessage()` or `generateMessageTypes()`) |
|
|
336
338
|
| `I18nMissingKeyException` (`E_I18N_MISSING_KEY`) | `t()`: no message has the key, with `missing: 'throw'` |
|
|
337
339
|
| `I18nMissingParameterException` (`E_I18N_MISSING_PARAMETER`) | `t()`, `format()`: an argument is missing, or a currency argument has no currency, with `missing: 'throw'` |
|
|
338
|
-
| `I18nTranslatorException` (`E_I18N_TRANSLATOR`) | The `Translator` is used without the `I18nMiddleware`, or
|
|
340
|
+
| `I18nTranslatorException` (`E_I18N_TRANSLATOR`) | The `Translator` is used without the `I18nMiddleware`, or outside a request |
|
|
339
341
|
| `InvalidValueException` (from `lakutata`) | The component's options are invalid, when it is created |
|
|
340
342
|
|
|
341
343
|
## See also
|
|
342
344
|
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
- `@lakutata/
|
|
345
|
+
- The guide on the website: [Internationalization (i18n)](https://lakutata.com/guide/I18n).
|
|
346
|
+
- `doc/en/I18n.md` in the `lakutata` package (Chinese version in `doc/zh`): the internationalization guide.
|
|
347
|
+
- `@lakutata/entrypoint` (`lakutata/com/entrypoint`): controllers, middlewares and adapters.
|
|
348
|
+
- `@lakutata/core` (`lakutata`): components, `Application`, DTO validation and its languages
|
|
346
349
|
(`loadValidationMessages`, `registerValidationMessages`, `runInValidationLanguage`).
|
|
347
350
|
- `@lakutata/cli`: the `lakutata i18n check`, `lakutata i18n types` and `lakutata i18n add` commands.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# @lakutata/i18n
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
lakutata 框架的国际化组件。它按键名保存应用在各种语言下的消息,消息使用 [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) 的语法,由 Node.js 内置的 `Intl` 按各自的语言格式化(数字、货币、日期、复数、选择),不依赖第三方库。它还提供为每个请求确定语言的中间件(这样请求的 `Translator`、校验错误和异常都使用这种语言)、Commander 命令行帮助的翻译,以及检查翻译的工具。`lakutata` 包把它重新导出为 `lakutata/com/i18n`。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
npm install lakutata
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
组件不需要其他包。`I18nMiddleware` 和 `commanderTranslator` 用到入口组件(`lakutata/com/entrypoint`,即可选的 peer 依赖 `@lakutata/entrypoint`),`lakutata` 包已经包含了它。
|
|
14
|
+
|
|
15
|
+
## 用法
|
|
16
|
+
|
|
17
|
+
### 组件和消息
|
|
18
|
+
|
|
19
|
+
用 `buildI18nOptions()` 把组件注册到应用的 `components` 中,名字为 `i18n`(中间件和 `commanderTranslator` 使用这个名字),并给出它的消息:
|
|
20
|
+
|
|
21
|
+
- `directory`:一个或多个目录(路径,或者 `@locales` 这样的应用别名),在组件创建时读取。一种语言可以是一个以语言标签命名的 JSON 文件(`en.json`、`zh-CN.json`),也可以是一个以语言标签命名的目录,目录里的文件名和子目录名作为键名的前缀(`zh-TW/order.json` 对应 `order.*`,`zh-TW/admin/user.json` 对应 `admin.user.*`)。两种布局可以混用。
|
|
22
|
+
- `messages`:在代码里按语言给出的消息(对象字面量或导入的 JSON),因此会和代码一起打包。它们在目录之后加入,会覆盖目录中同名的键。
|
|
23
|
+
|
|
24
|
+
JSON 的嵌套就是键名的层级:`{"order": {"count": "..."}}` 包含键 `order.count`。键名的每一段都不能包含 `.`。
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import {Application} from 'lakutata'
|
|
28
|
+
import {buildI18nOptions, type I18n} from 'lakutata/com/i18n'
|
|
29
|
+
|
|
30
|
+
Application
|
|
31
|
+
.alias({'@locales': '/srv/shop/locales'})
|
|
32
|
+
.run(() => ({
|
|
33
|
+
id: 'shop.app',
|
|
34
|
+
name: 'Shop',
|
|
35
|
+
components: {
|
|
36
|
+
i18n: buildI18nOptions({
|
|
37
|
+
directory: '@locales',
|
|
38
|
+
messages: {
|
|
39
|
+
'en': {order: {count: '{count, plural, =0 {No orders} one {# order} other {# orders}}', total: 'Total: {price, number, currency}'}},
|
|
40
|
+
'zh-CN': {order: {count: '{count, plural, =0 {暂无订单} other {# 个订单}}'}}
|
|
41
|
+
},
|
|
42
|
+
//默认语言:最后的回退语言,也是请求的语言都不支持时选用的语言
|
|
43
|
+
defaultLocale: 'en',
|
|
44
|
+
//zh-TW 缺少的消息依次到 zh-CN、zh、en 中查找
|
|
45
|
+
fallback: {'zh-TW': 'zh-CN'},
|
|
46
|
+
//消息和参数都没有给出货币时,{price, number, currency} 使用的货币
|
|
47
|
+
currency: 'CNY',
|
|
48
|
+
//日期和时间的时区(默认是进程的时区)
|
|
49
|
+
timeZone: 'Asia/Shanghai'
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}))
|
|
53
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
54
|
+
const i18n: I18n = await app.getObject('i18n')
|
|
55
|
+
console.log(i18n.t('order.count', {count: 1200})) //1,200 orders
|
|
56
|
+
console.log(i18n.t('order.count', {count: 0}, 'zh-CN')) //暂无订单
|
|
57
|
+
//zh-CN 中没有这条消息:使用英文消息,按中文格式化
|
|
58
|
+
console.log(i18n.t('order.total', {price: 9.5}, 'zh-CN')) //Total: ¥9.50
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| 选项 | 默认值 | 说明 |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `directory` | 无 | 消息所在的目录(支持别名);后面的目录覆盖前面目录中同名的键 |
|
|
65
|
+
| `messages` | 无 | 在代码里按语言给出的消息,在目录之后加入 |
|
|
66
|
+
| `locales` | 默认语言,加上消息中出现的语言(按字母顺序) | 支持的语言,`negotiate()` 从中选择 |
|
|
67
|
+
| `defaultLocale` | `'en'` | 默认语言 |
|
|
68
|
+
| `fallback` | 无 | 某种语言缺少消息时,接着去哪些语言里找(`{'zh-TW': 'zh-CN'}`,或一个列表) |
|
|
69
|
+
| `missing` | `'throw'`;`NODE_ENV=production` 时为 `'key'` | `'throw'`:缺少消息、参数或货币时抛出异常;`'key'`:返回键名,把缺少的参数写成 `{name}`,把没有货币的货币参数写成普通数字,每种情况只发出一次 `I18nWarning`(按键名和语言,或者按消息和参数计) |
|
|
70
|
+
| `currency` | 无 | 样式和 `currency` 参数都没有给出货币时,`{price, number, currency}` 使用的 ISO 4217 货币(不设置时,这样的消息会抛出异常;`missing: 'key'` 时得到普通数字) |
|
|
71
|
+
| `timeZone` | 进程的时区 | 日期和时间的 IANA 时区 |
|
|
72
|
+
| `validationMessages` | `true` | 是否为各种语言注册内置的校验消息 |
|
|
73
|
+
|
|
74
|
+
**回退。** 某种语言里缺少的消息,依次到它的 `fallback` 语言(以及这些语言的 `fallback` 语言)、范围更宽的标签(对 `zh-Hant-TW` 来说,先 `zh-Hant`,再 `zh`)和默认语言中查找。消息总是按请求的语言格式化:德语请求用到英文消息时,数字写成 `1.000`。哪里都找不到的键会抛出 `I18nMissingKeyException`;`missing: 'key'` 时返回键名本身。
|
|
75
|
+
|
|
76
|
+
**在请求之外**(定时任务或单例组件中),使用组件的 `t(key, params, locale)`、`has(key, locale)`、`format(message, params, locale)`,以及得到某种语言翻译器的 `translator(locale)`。它们都不做语言协商:不支持的语言按原样查找,然后回退。
|
|
77
|
+
|
|
78
|
+
**运行时**,`addMessages(locale, messages)` 添加或覆盖消息。新的语言会成为支持的语言(`getLocales()`、`negotiate()`),除非 `locales` 选项已经列出了支持的语言;无论如何,它的校验消息都会像创建时一样注册。`addMessages()` 返回的 Promise 在校验消息注册完成后才完成;而 `addMessages()` 一返回,`t()` 就能取得新消息。
|
|
79
|
+
|
|
80
|
+
### 请求的翻译器
|
|
81
|
+
|
|
82
|
+
把 `I18nMiddleware` 加到入口的 `middlewares` 中,每个请求就有了自己的语言;请求中的对象(控制器、`@Scoped` 的提供者、访问控制规则)注入的是同一个使用这种语言的 `Translator`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import {Application} from 'lakutata'
|
|
86
|
+
import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
87
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
88
|
+
import {CLIAction, GET} from 'lakutata/decorator/ctrl'
|
|
89
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
90
|
+
import {buildI18nOptions, I18nMiddleware, Translator} from 'lakutata/com/i18n'
|
|
91
|
+
|
|
92
|
+
class OrderController extends Controller {
|
|
93
|
+
@Inject(Translator)
|
|
94
|
+
protected readonly translator: Translator
|
|
95
|
+
|
|
96
|
+
@GET('/orders/summary')
|
|
97
|
+
@CLIAction('order-summary')
|
|
98
|
+
public async summary(): Promise<string> {
|
|
99
|
+
const count: string = this.translator.t('order.count', {count: 3})
|
|
100
|
+
const total: string = this.translator.number(1234.5, {style: 'currency', currency: 'EUR'})
|
|
101
|
+
const today: string = this.translator.date(new Date(), {dateStyle: 'long'})
|
|
102
|
+
return `${this.translator.locale}: ${count}, ${total}, ${today}`
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Application.run(() => ({
|
|
107
|
+
id: 'shop.app',
|
|
108
|
+
name: 'Shop',
|
|
109
|
+
components: {
|
|
110
|
+
i18n: buildI18nOptions({
|
|
111
|
+
messages: {
|
|
112
|
+
'en': {order: {count: '{count, plural, one {# order} other {# orders}}'}},
|
|
113
|
+
'zh-CN': {order: {count: '{count} 个订单'}}
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
entrypoint: buildEntrypoints({
|
|
117
|
+
controllers: [OrderController],
|
|
118
|
+
middlewares: [I18nMiddleware],
|
|
119
|
+
http: expressHTTPEntrypoint({port: 3000})
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
bootstrap: ['entrypoint']
|
|
123
|
+
}))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
翻译器(`Translator`,以及 `translator(locale)` 返回的 `ITranslator`)有 `locale`、`t(key, params)`、`has(key)`、`format(message, params)`、`number(value, options)`(`Intl.NumberFormat`)和 `date(value, options)`(`Intl.DateTimeFormat`,使用 `timeZone` 选项的时区)。没有配置中间件时,请求的 `Translator` 会抛出 `I18nTranslatorException`。
|
|
127
|
+
|
|
128
|
+
### 每个请求的语言
|
|
129
|
+
|
|
130
|
+
中间件读取请求方希望使用的语言(优先的在前),和支持的语言协商(都不匹配时用默认语言),然后设置 `context.locale`:
|
|
131
|
+
|
|
132
|
+
| 传输方式 | 读取的语言(优先的在前) |
|
|
133
|
+
|---|---|
|
|
134
|
+
| HTTP(Express、Fastify) | Query 参数 `?lang=`、Cookie `lang`,然后是 `Accept-Language`(按权重) |
|
|
135
|
+
| Socket.IO | 连接握手时的 Query 参数 `lang`、Cookie `lang` 和 `Accept-Language` |
|
|
136
|
+
| HTTP 服务适配器、JSON-RPC 适配器(HTTP、WebSocket) | HTTP 请求(WebSocket 为建立连接的请求)的 Query 参数 `lang`、Cookie `lang` 和 `Accept-Language` |
|
|
137
|
+
| 命令行、Electron IPC、stdio 上的 JSON-RPC | `environmentVariable` 指定的环境变量(默认没有)、`LANGUAGE`、`LC_ALL`、`LC_MESSAGES`、`LANG`,最后是进程的语言 |
|
|
138
|
+
| 定时任务 | 无:使用默认语言 |
|
|
139
|
+
|
|
140
|
+
不是语言标签的 Query 参数或 Cookie 值会被跳过。协商时依次尝试请求的每一种语言:先找完全相同的标签,再找只有语言部分的标签(`en-GB` 得到 `en`),再找同一语言的其他标签(`pt-PT` 得到 `pt-BR`,按支持的语言的顺序)。
|
|
141
|
+
|
|
142
|
+
子类可以换用别的组件(`@Inject('...') protected readonly i18n`),修改 `queryParameter` 和 `cookie` 的名字以及 `environmentVariable`,或者改变读取的语言(`requested(context)`):
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import {type BaseContext} from 'lakutata/com/entrypoint'
|
|
146
|
+
import {I18nMiddleware} from 'lakutata/com/i18n'
|
|
147
|
+
|
|
148
|
+
export class LanguageMiddleware extends I18nMiddleware {
|
|
149
|
+
protected readonly queryParameter: string = 'locale'
|
|
150
|
+
|
|
151
|
+
protected readonly environmentVariable: string = 'SHOP_LANG'
|
|
152
|
+
|
|
153
|
+
//优先使用登录用户保存的语言
|
|
154
|
+
protected requested(context: BaseContext): string[] {
|
|
155
|
+
const saved: unknown = context.data.userLanguage
|
|
156
|
+
return [...(typeof saved === 'string' ? [saved] : []), ...super.requested(context)]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 消息:插值、复数和格式化
|
|
162
|
+
|
|
163
|
+
| 语法 | 例子 |
|
|
164
|
+
|---|---|
|
|
165
|
+
| 参数 | `Hello, {name}`:数字按语言格式化,`Date` 按日期格式化,其他值转为字符串 |
|
|
166
|
+
| 数字 | `{n, number}`、`{n, number, integer}`、`{n, number, percent}` |
|
|
167
|
+
| 货币 | `{price, number, currency}`(货币来自 `currency` 参数,否则来自 `currency` 选项)、`{price, number, currency/EUR}` |
|
|
168
|
+
| 日期、时间 | `{at, date, short}`、`{at, time, long}`;样式为 `short`、`medium`(默认)、`long`、`full`;值可以是 `Date`、时间戳、日期字符串或有 `toDate()` 的对象 |
|
|
169
|
+
| 复数 | `{count, plural, =0 {none} one {# item} other {# items}}`:先匹配 `=n` 选项,再按该语言的复数规则;`#` 是数字;支持 `offset:1` |
|
|
170
|
+
| 序数 | `{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}` |
|
|
171
|
+
| 选择 | `{role, select, admin {Administrator} other {User}}` |
|
|
172
|
+
| 转义 | `''` 输出一个撇号;`'{literal}'` 原样输出花括号(复数里的 `'#'` 输出 `#`) |
|
|
173
|
+
|
|
174
|
+
`plural`、`selectordinal` 和 `select` 必须有 `other` 选项。消息在第一次使用时解析;语法错误会抛出 `I18nMessageSyntaxException`,异常中指出了消息所在的语言、键名和出错的位置。
|
|
175
|
+
|
|
176
|
+
应用可以声明键名和它们的参数,这样写错键名或漏掉参数时,编译就会失败(`lakutata i18n types` 根据默认语言生成这份声明;没有声明时,接受任意键名):
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
//src/i18n.d.ts
|
|
180
|
+
import 'lakutata/com/i18n'
|
|
181
|
+
|
|
182
|
+
declare module 'lakutata/com/i18n' {
|
|
183
|
+
interface I18nMessages {
|
|
184
|
+
'order.count': { count: number }
|
|
185
|
+
'order.total': { price: number | bigint, currency?: string }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 校验消息和异常
|
|
191
|
+
|
|
192
|
+
配置了中间件时,请求的校验错误(来自输入 DTO、`@Accept`/`@Return`,以及处理请求时代码里调用的 `DTO.validate`)使用请求的语言。组件在创建时(以及 `addMessages()` 添加新语言时)为它的各种语言注册内置的校验消息。内置了 20 种语言:`en`、`zh-CN`、`zh-TW`、`ja`、`ko`、`fr`、`de`、`es`、`pt-BR`、`it`、`nl`、`pl`、`ru`、`uk`、`tr`、`vi`、`th`、`id`、`hi`、`ar`。应用写在 `validation` 下的消息按错误码叠加在上面,使用校验模板的语法(不是 ICU):
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"validation": {
|
|
197
|
+
"any": {"required": "Please enter {{#label}}"},
|
|
198
|
+
"string": {"min": "{{#label}} needs {{#limit}} characters at least"}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
某种语言的校验消息以它回退到的语言的消息为基础,先用它自己的内置消息覆盖,再用它自己的应用消息覆盖。`validationMessages: false` 时只注册应用的消息。
|
|
204
|
+
|
|
205
|
+
请求中抛出异常时,如果 `errors.<errno>` 下有这个 `errno` 对应的消息,异常就会改用这条消息(取请求所用语言的版本),并用异常模板的参数格式化(`{id}`,逐个传入的参数用 `{0}`、`{1}`);`errno` 保持不变:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import {Exception} from 'lakutata'
|
|
209
|
+
|
|
210
|
+
//有消息 {"errors": {"E_ORDER_NOT_FOUND": "订单 {id} 不存在"}} 时,中文请求得到 "订单 7 不存在"
|
|
211
|
+
export class OrderNotFoundException extends Exception {
|
|
212
|
+
public errno: string = 'E_ORDER_NOT_FOUND'
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
throw new OrderNotFoundException('Order {id} not found', {id: 7})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 命令行帮助
|
|
219
|
+
|
|
220
|
+
`commanderTranslator()` 用 `cli` 下的消息(`cli.description`、`cli.commands.<command>.description`、`cli.commands.<command>.arguments.<name>`、`cli.commands.<command>.options.<property>`、`cli.help.usage`、`cli.help.options`、`cli.help.default`、`cli.prompt.json`……)按运行环境的语言翻译 Commander 适配器的帮助和提示;没有消息的文字保持英文:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import {Application} from 'lakutata'
|
|
224
|
+
import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
225
|
+
import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
|
|
226
|
+
import {CLIAction} from 'lakutata/decorator/ctrl'
|
|
227
|
+
import {buildI18nOptions, commanderTranslator, I18nMiddleware} from 'lakutata/com/i18n'
|
|
228
|
+
|
|
229
|
+
class GreetController extends Controller {
|
|
230
|
+
@CLIAction('greet', {description: 'Greet someone'})
|
|
231
|
+
public async greet(): Promise<string> {
|
|
232
|
+
return 'Hello'
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
Application.run(() => ({
|
|
237
|
+
id: 'tool.app',
|
|
238
|
+
name: 'Tool',
|
|
239
|
+
components: {
|
|
240
|
+
i18n: buildI18nOptions({
|
|
241
|
+
messages: {
|
|
242
|
+
'zh-CN': {cli: {commands: {greet: {description: '问候某人'}}, help: {usage: '用法:', options: '选项:'}}}
|
|
243
|
+
}
|
|
244
|
+
}),
|
|
245
|
+
entrypoint: buildEntrypoints({
|
|
246
|
+
controllers: [GreetController],
|
|
247
|
+
middlewares: [I18nMiddleware],
|
|
248
|
+
//TOOL_LANG=zh-CN tool --help,或 LANG=zh_CN.UTF-8
|
|
249
|
+
cli: commanderCLIEntrypoint({bin: 'tool', translate: commanderTranslator({environment: 'TOOL_LANG'})})
|
|
250
|
+
})
|
|
251
|
+
},
|
|
252
|
+
bootstrap: ['entrypoint']
|
|
253
|
+
}))
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 检查翻译
|
|
257
|
+
|
|
258
|
+
`lakutata i18n check` 和 `lakutata i18n types` 背后就是 `loadCatalogs`、`checkCatalogs` 和 `generateMessageTypes` 这几个函数,脚本或 CI 中也可以直接调用:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import {checkCatalogs, loadCatalogs, type CatalogIssue, type Catalogs} from 'lakutata/com/i18n'
|
|
262
|
+
|
|
263
|
+
const catalogs: Catalogs = await loadCatalogs(['./locales'])
|
|
264
|
+
const issues: CatalogIssue[] = checkCatalogs(catalogs, 'en')
|
|
265
|
+
for (const issue of issues) console.log(`${issue.level} ${issue.locale} ${issue.key}: ${issue.message}`)
|
|
266
|
+
if (issues.some((issue: CatalogIssue): boolean => issue.level === 'error')) process.exitCode = 1
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## API
|
|
270
|
+
|
|
271
|
+
| 导出 | 说明 |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `I18n` | 组件:`t`、`has`、`format`、`translator`、`negotiate`、`addMessages`、`getLocales`、`getDefaultLocale`、`getKeys`、`getTimeZone` |
|
|
274
|
+
| `buildI18nOptions` | 构造 `I18n` 的组件选项 |
|
|
275
|
+
| `I18nOptions` | 选项:`directory`、`messages`、`locales`、`defaultLocale`、`fallback`、`missing`、`currency`、`timeZone`、`validationMessages` |
|
|
276
|
+
| `Translator` | 作用域提供者,注入到请求中的各个对象里,使用请求的语言 |
|
|
277
|
+
| `ITranslator` | 某一种语言的翻译器:`locale`、`t`、`has`、`format`、`number`、`date` |
|
|
278
|
+
| `LocaleTranslator` | `I18n.translator()` 返回的 `ITranslator` |
|
|
279
|
+
| `I18nMiddleware` | 为每个请求确定语言的入口中间件(`lakutata/com/i18n`、`@lakutata/i18n/middleware`) |
|
|
280
|
+
| `commanderTranslator`、`CommanderTranslatorOptions` | Commander 命令行帮助的翻译及其选项(`lakutata/com/i18n`、`@lakutata/i18n/middleware`) |
|
|
281
|
+
| `I18nMessages`、`MessageKey`、`MessageParams` | 应用的键名和参数的声明,以及由它推导出的类型 |
|
|
282
|
+
| `NestedMessages`、`Catalogs` | 一种语言的消息,按键名的各段嵌套;已加载的消息,按语言和键名组织 |
|
|
283
|
+
| `loadCatalogs`、`checkCatalogs`、`CatalogIssue`、`generateMessageTypes` | 加载消息目录、检查翻译、生成键名的类型 |
|
|
284
|
+
| `normalizeLocale`、`parseAcceptLanguage`、`environmentLocales`、`negotiateLocale`、`fallbackChain` | 处理语言标签的辅助函数 |
|
|
285
|
+
| `parseMessage`、`messageArguments`、`MessagePart` | ICU MessageFormat 解析器,以及消息的参数 |
|
|
286
|
+
| `I18nCatalogException`、`I18nMessageSyntaxException`、`I18nMissingKeyException`、`I18nMissingParameterException`、`I18nTranslatorException` | 异常 |
|
|
287
|
+
|
|
288
|
+
以上全部由 `lakutata/com/i18n` 导出;`@lakutata/i18n` 导出除中间件相关以外的全部内容,中间件相关的由 `@lakutata/i18n/middleware` 导出。类型声明中的每一项都附有说明和示例。
|
|
289
|
+
|
|
290
|
+
## 错误
|
|
291
|
+
|
|
292
|
+
| 异常 | 什么时候 |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `I18nCatalogException`(`E_I18N_CATALOG`) | 创建组件、`addMessages()` 或 `loadCatalogs()` 时:目录不存在、JSON 无效、文件名或目录名不是语言标签、键或值无效、选项不是语言标签、在没有应用时使用别名;`generateMessageTypes()`:默认语言没有消息 |
|
|
295
|
+
| `I18nMessageSyntaxException`(`E_I18N_MESSAGE_SYNTAX`) | 消息不是有效的 ICU MessageFormat,在第一次使用时(或者由 `format()`、`parseMessage()`、`generateMessageTypes()` 解析时)抛出 |
|
|
296
|
+
| `I18nMissingKeyException`(`E_I18N_MISSING_KEY`) | `t()`:找不到这个键的消息,且 `missing: 'throw'` |
|
|
297
|
+
| `I18nMissingParameterException`(`E_I18N_MISSING_PARAMETER`) | `t()`、`format()`:缺少参数,或者货币参数没有货币,且 `missing: 'throw'` |
|
|
298
|
+
| `I18nTranslatorException`(`E_I18N_TRANSLATOR`) | 没有配置 `I18nMiddleware` 就使用 `Translator`,或者在请求之外使用它 |
|
|
299
|
+
| `InvalidValueException`(来自 `lakutata`) | 创建组件时,组件的选项无效 |
|
|
300
|
+
|
|
301
|
+
## 另见
|
|
302
|
+
|
|
303
|
+
- 网站上的指南:[国际化(i18n)](https://lakutata.com/zh/guide/I18n)。
|
|
304
|
+
- `lakutata` 包中的 `doc/zh/I18n.md`:国际化指南。
|
|
305
|
+
- `@lakutata/entrypoint`(`lakutata/com/entrypoint`):控制器、中间件和适配器。
|
|
306
|
+
- `@lakutata/core`(`lakutata`):组件、`Application`、DTO 校验及其语言(`loadValidationMessages`、`registerValidationMessages`、`runInValidationLanguage`)。
|
|
307
|
+
- `@lakutata/cli`:`lakutata i18n check`、`lakutata i18n types` 和 `lakutata i18n add` 命令。
|
package/dist/cjs/i18n/I18n.d.ts
CHANGED
|
@@ -283,7 +283,7 @@ export declare class I18n extends Component {
|
|
|
283
283
|
* @param locale The language tag: the default language by default, and when it is not a language tag.
|
|
284
284
|
* @returns The formatted message. With `missing: 'key'`, the key itself when no message has it, `{name}` in place
|
|
285
285
|
* of a missing argument, and a plain number for a currency argument without a currency, each with a warning
|
|
286
|
-
* emitted once (per key, or per message and argument).
|
|
286
|
+
* emitted once (per key and language, or per message and argument).
|
|
287
287
|
* @throws {I18nMissingKeyException} When no message has the key and the `missing` mode is `'throw'`.
|
|
288
288
|
* @throws {I18nMissingParameterException} When an argument is missing, or a currency argument has no currency, and
|
|
289
289
|
* the `missing` mode is `'throw'`.
|
package/dist/cjs/i18n/I18n.js
CHANGED
|
@@ -378,7 +378,7 @@ class I18n extends core_1.Component {
|
|
|
378
378
|
* @param locale The language tag: the default language by default, and when it is not a language tag.
|
|
379
379
|
* @returns The formatted message. With `missing: 'key'`, the key itself when no message has it, `{name}` in place
|
|
380
380
|
* of a missing argument, and a plain number for a currency argument without a currency, each with a warning
|
|
381
|
-
* emitted once (per key, or per message and argument).
|
|
381
|
+
* emitted once (per key and language, or per message and argument).
|
|
382
382
|
* @throws {I18nMissingKeyException} When no message has the key and the `missing` mode is `'throw'`.
|
|
383
383
|
* @throws {I18nMissingParameterException} When an argument is missing, or a currency argument has no currency, and
|
|
384
384
|
* the `missing` mode is `'throw'`.
|
package/dist/esm/i18n/I18n.js
CHANGED
|
@@ -371,7 +371,7 @@ export class I18n extends Component {
|
|
|
371
371
|
* @param locale The language tag: the default language by default, and when it is not a language tag.
|
|
372
372
|
* @returns The formatted message. With `missing: 'key'`, the key itself when no message has it, `{name}` in place
|
|
373
373
|
* of a missing argument, and a plain number for a currency argument without a currency, each with a warning
|
|
374
|
-
* emitted once (per key, or per message and argument).
|
|
374
|
+
* emitted once (per key and language, or per message and argument).
|
|
375
375
|
* @throws {I18nMissingKeyException} When no message has the key and the `missing` mode is `'throw'`.
|
|
376
376
|
* @throws {I18nMissingParameterException} When an argument is missing, or a currency argument has no currency, and
|
|
377
377
|
* the `missing` mode is `'throw'`.
|
|
@@ -283,7 +283,7 @@ export declare class I18n extends Component {
|
|
|
283
283
|
* @param locale The language tag: the default language by default, and when it is not a language tag.
|
|
284
284
|
* @returns The formatted message. With `missing: 'key'`, the key itself when no message has it, `{name}` in place
|
|
285
285
|
* of a missing argument, and a plain number for a currency argument without a currency, each with a warning
|
|
286
|
-
* emitted once (per key, or per message and argument).
|
|
286
|
+
* emitted once (per key and language, or per message and argument).
|
|
287
287
|
* @throws {I18nMissingKeyException} When no message has the key and the `missing` mode is `'throw'`.
|
|
288
288
|
* @throws {I18nMissingParameterException} When an argument is missing, or a currency argument has no currency, and
|
|
289
289
|
* the `missing` mode is `'throw'`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/i18n",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "The internationalization of the lakutata framework: the messages of each language by their keys (ICU MessageFormat), the language of each request, the messages of the validation.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lakutata",
|
|
7
|
+
"i18n",
|
|
8
|
+
"internationalization",
|
|
9
|
+
"localization",
|
|
10
|
+
"icu",
|
|
11
|
+
"messageformat",
|
|
12
|
+
"translation",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://lakutata.com/packages/i18n",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/lakutata/lakutata/issues",
|
|
18
|
+
"email": "admin@lakutata.com"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/lakutata/lakutata.git",
|
|
23
|
+
"directory": "packages/i18n"
|
|
24
|
+
},
|
|
25
|
+
"author": "alex.ma",
|
|
26
|
+
"license": "MIT",
|
|
5
27
|
"type": "module",
|
|
6
28
|
"main": "./dist/cjs/exports/I18n.js",
|
|
7
29
|
"types": "./dist/cjs/exports/I18n.d.ts",
|
|
@@ -39,8 +61,8 @@
|
|
|
39
61
|
"test:unit": "npm run compile && node --test \"dist/esm/tests/*.spec.js\""
|
|
40
62
|
},
|
|
41
63
|
"peerDependencies": {
|
|
42
|
-
"@lakutata/core": "3.
|
|
43
|
-
"@lakutata/entrypoint": "3.
|
|
64
|
+
"@lakutata/core": "3.1.0",
|
|
65
|
+
"@lakutata/entrypoint": "3.1.0"
|
|
44
66
|
},
|
|
45
67
|
"peerDependenciesMeta": {
|
|
46
68
|
"@lakutata/entrypoint": {
|
|
@@ -48,7 +70,7 @@
|
|
|
48
70
|
}
|
|
49
71
|
},
|
|
50
72
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
73
|
+
"node": "^22.13.0 || >=24.11.0"
|
|
52
74
|
},
|
|
53
75
|
"files": [
|
|
54
76
|
"dist",
|