@lakutata/i18n 3.0.0-beta.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.
Files changed (80) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +347 -0
  3. package/dist/cjs/exports/I18n.d.ts +15 -0
  4. package/dist/cjs/exports/I18n.js +45 -0
  5. package/dist/cjs/exports/I18nMiddleware.d.ts +2 -0
  6. package/dist/cjs/exports/I18nMiddleware.js +21 -0
  7. package/dist/cjs/i18n/I18n.d.ts +313 -0
  8. package/dist/cjs/i18n/I18n.js +462 -0
  9. package/dist/cjs/i18n/Translator.d.ts +124 -0
  10. package/dist/cjs/i18n/Translator.js +156 -0
  11. package/dist/cjs/i18n/exceptions/I18nCatalogException.d.ts +15 -0
  12. package/dist/cjs/i18n/exceptions/I18nCatalogException.js +22 -0
  13. package/dist/cjs/i18n/exceptions/I18nMessageSyntaxException.d.ts +14 -0
  14. package/dist/cjs/i18n/exceptions/I18nMessageSyntaxException.js +21 -0
  15. package/dist/cjs/i18n/exceptions/I18nMissingKeyException.d.ts +12 -0
  16. package/dist/cjs/i18n/exceptions/I18nMissingKeyException.js +19 -0
  17. package/dist/cjs/i18n/exceptions/I18nMissingParameterException.d.ts +13 -0
  18. package/dist/cjs/i18n/exceptions/I18nMissingParameterException.js +20 -0
  19. package/dist/cjs/i18n/exceptions/I18nTranslatorException.d.ts +12 -0
  20. package/dist/cjs/i18n/exceptions/I18nTranslatorException.js +19 -0
  21. package/dist/cjs/i18n/interfaces/I18nOptions.d.ts +70 -0
  22. package/dist/cjs/i18n/interfaces/I18nOptions.js +2 -0
  23. package/dist/cjs/i18n/interfaces/ITranslator.d.ts +57 -0
  24. package/dist/cjs/i18n/interfaces/ITranslator.js +2 -0
  25. package/dist/cjs/i18n/lib/Catalog.d.ts +34 -0
  26. package/dist/cjs/i18n/lib/Catalog.js +104 -0
  27. package/dist/cjs/i18n/lib/CatalogTools.d.ts +86 -0
  28. package/dist/cjs/i18n/lib/CatalogTools.js +218 -0
  29. package/dist/cjs/i18n/lib/Locale.d.ts +95 -0
  30. package/dist/cjs/i18n/lib/Locale.js +182 -0
  31. package/dist/cjs/i18n/lib/LocaleTranslator.d.ts +93 -0
  32. package/dist/cjs/i18n/lib/LocaleTranslator.js +105 -0
  33. package/dist/cjs/i18n/lib/MessageFormat.d.ts +144 -0
  34. package/dist/cjs/i18n/lib/MessageFormat.js +372 -0
  35. package/dist/cjs/i18n/middleware/CommanderTranslator.d.ts +71 -0
  36. package/dist/cjs/i18n/middleware/CommanderTranslator.js +69 -0
  37. package/dist/cjs/i18n/middleware/I18nMiddleware.d.ts +126 -0
  38. package/dist/cjs/i18n/middleware/I18nMiddleware.js +210 -0
  39. package/dist/cjs/i18n/types/I18nMessages.d.ts +24 -0
  40. package/dist/cjs/i18n/types/I18nMessages.js +2 -0
  41. package/dist/cjs/package.json +1 -0
  42. package/dist/esm/exports/I18n.js +14 -0
  43. package/dist/esm/exports/I18nMiddleware.js +2 -0
  44. package/dist/esm/i18n/I18n.js +454 -0
  45. package/dist/esm/i18n/Translator.js +153 -0
  46. package/dist/esm/i18n/exceptions/I18nCatalogException.js +18 -0
  47. package/dist/esm/i18n/exceptions/I18nMessageSyntaxException.js +17 -0
  48. package/dist/esm/i18n/exceptions/I18nMissingKeyException.js +15 -0
  49. package/dist/esm/i18n/exceptions/I18nMissingParameterException.js +16 -0
  50. package/dist/esm/i18n/exceptions/I18nTranslatorException.js +15 -0
  51. package/dist/esm/i18n/interfaces/I18nOptions.js +1 -0
  52. package/dist/esm/i18n/interfaces/ITranslator.js +1 -0
  53. package/dist/esm/i18n/lib/Catalog.js +96 -0
  54. package/dist/esm/i18n/lib/CatalogTools.js +213 -0
  55. package/dist/esm/i18n/lib/Locale.js +173 -0
  56. package/dist/esm/i18n/lib/LocaleTranslator.js +101 -0
  57. package/dist/esm/i18n/lib/MessageFormat.js +367 -0
  58. package/dist/esm/i18n/middleware/CommanderTranslator.js +66 -0
  59. package/dist/esm/i18n/middleware/I18nMiddleware.js +206 -0
  60. package/dist/esm/i18n/types/I18nMessages.js +1 -0
  61. package/dist/types/exports/I18n.d.ts +15 -0
  62. package/dist/types/exports/I18nMiddleware.d.ts +2 -0
  63. package/dist/types/i18n/I18n.d.ts +313 -0
  64. package/dist/types/i18n/Translator.d.ts +124 -0
  65. package/dist/types/i18n/exceptions/I18nCatalogException.d.ts +15 -0
  66. package/dist/types/i18n/exceptions/I18nMessageSyntaxException.d.ts +14 -0
  67. package/dist/types/i18n/exceptions/I18nMissingKeyException.d.ts +12 -0
  68. package/dist/types/i18n/exceptions/I18nMissingParameterException.d.ts +13 -0
  69. package/dist/types/i18n/exceptions/I18nTranslatorException.d.ts +12 -0
  70. package/dist/types/i18n/interfaces/I18nOptions.d.ts +70 -0
  71. package/dist/types/i18n/interfaces/ITranslator.d.ts +57 -0
  72. package/dist/types/i18n/lib/Catalog.d.ts +34 -0
  73. package/dist/types/i18n/lib/CatalogTools.d.ts +86 -0
  74. package/dist/types/i18n/lib/Locale.d.ts +95 -0
  75. package/dist/types/i18n/lib/LocaleTranslator.d.ts +93 -0
  76. package/dist/types/i18n/lib/MessageFormat.d.ts +144 -0
  77. package/dist/types/i18n/middleware/CommanderTranslator.d.ts +71 -0
  78. package/dist/types/i18n/middleware/I18nMiddleware.d.ts +126 -0
  79. package/dist/types/i18n/types/I18nMessages.d.ts +24 -0
  80. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Lakutata
4
+
5
+ All rights reserved
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,347 @@
1
+ # @lakutata/i18n
2
+
3
+ The internationalization component of the lakutata framework: the application's messages in each language, by key,
4
+ written in [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) and formatted in their
5
+ language by the `Intl` of Node.js (numbers, currencies, dates, plurals, selects), with no third-party library. It also
6
+ provides the middleware choosing the language of each request (its `Translator`, its validation errors and its
7
+ exceptions in that language), the translation of the Commander CLI help, and the tools checking the translations. The
8
+ umbrella package `lakutata` re-exports it as `lakutata/com/i18n`.
9
+
10
+ ## Installation
11
+
12
+ ```shell
13
+ npm install lakutata
14
+ ```
15
+
16
+ The component needs nothing more. `I18nMiddleware` and `commanderTranslator` use the entrypoint component
17
+ (`lakutata/com/entrypoint`, the optional peer dependency `@lakutata/entrypoint`), included in `lakutata`.
18
+
19
+ ## Usage
20
+
21
+ ### The component and its messages
22
+
23
+ Register the component with `buildI18nOptions()` in the `components` of the application, named `i18n` (the name the
24
+ middleware and `commanderTranslator` use), and give it its messages:
25
+
26
+ - `directory`: one or several directories (a path, or an alias of the application such as `@locales`) read when the
27
+ component is created. A language is a JSON file named by its tag (`en.json`, `zh-CN.json`), or a directory named by
28
+ its tag whose files and subdirectories prefix their keys (`zh-TW/order.json` gives `order.*`,
29
+ `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; added after
31
+ the directories, they replace the same keys.
32
+
33
+ The nesting of the JSON is the one of the keys: `{"order": {"count": "..."}}` holds the key `order.count`; a key part
34
+ cannot contain `.`.
35
+
36
+ ```typescript
37
+ import {Application} from 'lakutata'
38
+ import {buildI18nOptions, type I18n} from 'lakutata/com/i18n'
39
+
40
+ Application
41
+ .alias({'@locales': '/srv/shop/locales'})
42
+ .run(() => ({
43
+ id: 'shop.app',
44
+ name: 'Shop',
45
+ components: {
46
+ i18n: buildI18nOptions({
47
+ directory: '@locales',
48
+ messages: {
49
+ 'en': {order: {count: '{count, plural, =0 {No orders} one {# order} other {# orders}}', total: 'Total: {price, number, currency}'}},
50
+ 'zh-CN': {order: {count: '{count, plural, =0 {暂无订单} other {# 个订单}}'}}
51
+ },
52
+ //The default language: the last fallback, and the language chosen when none is supported
53
+ defaultLocale: 'en',
54
+ //A message missing in zh-TW is looked up in zh-CN, then zh, then en
55
+ fallback: {'zh-TW': 'zh-CN'},
56
+ //The currency of {price, number, currency} when the message and its arguments give none
57
+ currency: 'CNY',
58
+ //The time zone of the dates and the times (the process's one by default)
59
+ timeZone: 'Asia/Shanghai'
60
+ })
61
+ }
62
+ }))
63
+ .onLaunched(async (app: Application): Promise<void> => {
64
+ const i18n: I18n = await app.getObject('i18n')
65
+ console.log(i18n.t('order.count', {count: 1200})) //1,200 orders
66
+ console.log(i18n.t('order.count', {count: 0}, 'zh-CN')) //暂无订单
67
+ //Missing in zh-CN: the English message, formatted in Chinese
68
+ console.log(i18n.t('order.total', {price: 9.5}, 'zh-CN')) //Total: ¥9.50
69
+ })
70
+ ```
71
+
72
+ | Option | Default | Description |
73
+ |---|---|---|
74
+ | `directory` | none | The directories of the messages, aliases accepted; the later ones replace the same keys |
75
+ | `messages` | none | The messages by language given in the code, added after the directories |
76
+ | `locales` | the default language, then the languages of the messages (alphabetical) | The supported languages, which `negotiate()` chooses from |
77
+ | `defaultLocale` | `'en'` | The default language |
78
+ | `fallback` | none | The languages a message missing in a language is looked up in next (`{'zh-TW': 'zh-CN'}`, or a list) |
79
+ | `missing` | `'throw'`, `'key'` when `NODE_ENV=production` | `'throw'` throws on a missing message, argument or currency; `'key'` gives the key, writes a missing argument as `{name}` and a currency argument without a currency as a plain number, each with a warning `I18nWarning` emitted once (per key, or per message and argument) |
80
+ | `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 the dates and the times |
82
+ | `validationMessages` | `true` | Whether the built-in validation messages of the languages are registered |
83
+
84
+ **Fallbacks.** A message missing in a language is looked up in its `fallback` languages (and theirs), then in its less
85
+ specific tags (`zh-Hant-TW`, `zh-Hant`, `zh`), then in the default language. It is always formatted in the language
86
+ asked: an English message given to a German request writes `1.000`. A key found nowhere throws
87
+ `I18nMissingKeyException`, or gives the key itself with `missing: 'key'`.
88
+
89
+ **Out of a request** (a schedule, a singleton component), the component gives `t(key, params, locale)`,
90
+ `has(key, locale)`, `format(message, params, locale)`, and `translator(locale)`, a translator of one language. None of
91
+ them negotiates the language: an unsupported language is looked up as given, then falls back.
92
+
93
+ **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, and its validation messages are
95
+ registered as at the creation; the promise it returns resolves once they are (the messages themselves are given by
96
+ `t()` as soon as it returns).
97
+
98
+ ### The translator of a request
99
+
100
+ With `I18nMiddleware` in the `middlewares` of the entrypoint, each request has a language, and its objects (the
101
+ controller, the `@Scoped` providers, the access control rules) inject the same `Translator` of that language:
102
+
103
+ ```typescript
104
+ import {Application} from 'lakutata'
105
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
106
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
107
+ import {CLIAction, GET} from 'lakutata/decorator/ctrl'
108
+ import {Inject} from 'lakutata/decorator/di'
109
+ import {buildI18nOptions, I18nMiddleware, Translator} from 'lakutata/com/i18n'
110
+
111
+ class OrderController extends Controller {
112
+ @Inject(Translator)
113
+ protected readonly translator: Translator
114
+
115
+ @GET('/orders/summary')
116
+ @CLIAction('order-summary')
117
+ public async summary(): Promise<string> {
118
+ const count: string = this.translator.t('order.count', {count: 3})
119
+ const total: string = this.translator.number(1234.5, {style: 'currency', currency: 'EUR'})
120
+ const today: string = this.translator.date(new Date(), {dateStyle: 'long'})
121
+ return `${this.translator.locale}: ${count}, ${total}, ${today}`
122
+ }
123
+ }
124
+
125
+ Application.run(() => ({
126
+ id: 'shop.app',
127
+ name: 'Shop',
128
+ components: {
129
+ i18n: buildI18nOptions({
130
+ messages: {
131
+ 'en': {order: {count: '{count, plural, one {# order} other {# orders}}'}},
132
+ 'zh-CN': {order: {count: '{count} 个订单'}}
133
+ }
134
+ }),
135
+ entrypoint: buildEntrypoints({
136
+ controllers: [OrderController],
137
+ middlewares: [I18nMiddleware],
138
+ http: expressHTTPEntrypoint({port: 3000})
139
+ })
140
+ },
141
+ bootstrap: ['entrypoint']
142
+ }))
143
+ ```
144
+
145
+ A translator (`Translator`, and the `ITranslator` of `translator(locale)`) has `locale`, `t(key, params)`, `has(key)`,
146
+ `format(message, params)`, `number(value, options)` (`Intl.NumberFormat`) and `date(value, options)`
147
+ (`Intl.DateTimeFormat`, in the `timeZone` option). The `Translator` of a request without the middleware throws
148
+ `I18nTranslatorException`.
149
+
150
+ ### The language of each request
151
+
152
+ The middleware reads the languages a request asks for, preferred first, then negotiates them with the supported
153
+ languages (the default language when none matches) and sets `context.locale`:
154
+
155
+ | Transport | Languages read, preferred first |
156
+ |---|---|
157
+ | 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's handshake |
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 opening the connection) |
160
+ | 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
+ | Schedules | None: the default language |
162
+
163
+ A query or cookie value which is not a language tag is skipped. The negotiation tries each requested language in turn:
164
+ the same tag, then its language alone (`en-GB` gives `en`), then another tag of its language (`pt-PT` gives `pt-BR`, in
165
+ the order of the supported languages).
166
+
167
+ A subclass changes the component (`@Inject('...') protected readonly i18n`), the names `queryParameter` and `cookie`,
168
+ `environmentVariable`, or the languages read (`requested(context)`):
169
+
170
+ ```typescript
171
+ import {type BaseContext} from 'lakutata/com/entrypoint'
172
+ import {I18nMiddleware} from 'lakutata/com/i18n'
173
+
174
+ export class LanguageMiddleware extends I18nMiddleware {
175
+ protected readonly queryParameter: string = 'locale'
176
+
177
+ protected readonly environmentVariable: string = 'SHOP_LANG'
178
+
179
+ //The language saved by the signed-in user first
180
+ protected requested(context: BaseContext): string[] {
181
+ const saved: unknown = context.data.userLanguage
182
+ return [...(typeof saved === 'string' ? [saved] : []), ...super.requested(context)]
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### The messages: interpolation, plurals, formatting
188
+
189
+ | Syntax | Example |
190
+ |---|---|
191
+ | Argument | `Hello, {name}`: a number is formatted in the language, a `Date` as a date, the rest as a string |
192
+ | 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}` |
194
+ | 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 |
196
+ | Ordinal | `{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}` |
197
+ | Select | `{role, select, admin {Administrator} other {User}}` |
198
+ | Quoting | `''` writes an apostrophe; `'{literal}'` writes braces (`'#'` writes `#` in a plural) |
199
+
200
+ `plural`, `selectordinal` and `select` need an `other` option. A message is parsed the first time it is used; a syntax
201
+ error throws `I18nMessageSyntaxException` naming its language, key and position.
202
+
203
+ The application can declare its keys and their arguments, so that a wrong key or a missing argument fails the
204
+ compilation (`lakutata i18n types` generates it from the default language; without it, any key is accepted):
205
+
206
+ ```text
207
+ //src/i18n.d.ts
208
+ import 'lakutata/com/i18n'
209
+
210
+ declare module 'lakutata/com/i18n' {
211
+ interface I18nMessages {
212
+ 'order.count': { count: number }
213
+ 'order.total': { price: number | bigint, currency?: string }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### The validation messages and the exceptions
219
+
220
+ With the middleware, the validation errors of a request (its input DTO, `@Accept`/`@Return`, the `DTO.validate` calls
221
+ of its code) are in its language. The component registers, when it is created (and for a language added by
222
+ `addMessages()`), the built-in validation messages of its languages (20 languages are built in: `en`, `zh-CN`,
223
+ `zh-TW`, `ja`, `ko`, `fr`, `de`, `es`, `pt-BR`, `it`, `nl`, `pl`, `ru`, `uk`, `tr`, `vi`, `th`, `id`, `hi`, `ar`), layered with the application's messages under `validation`, by error
224
+ code, in the syntax of the validation templates (not ICU):
225
+
226
+ ```json
227
+ {
228
+ "validation": {
229
+ "any": {"required": "Please enter {{#label}}"},
230
+ "string": {"min": "{{#label}} needs {{#limit}} characters at least"}
231
+ }
232
+ }
233
+ ```
234
+
235
+ A language's validation messages are those of the languages it falls back to, replaced by its own built-in ones, then
236
+ by its own application messages. `validationMessages: false` registers the application's messages only.
237
+
238
+ An exception thrown by a request whose `errno` has a message `errors.<errno>` gets that message in the request's
239
+ language, formatted with the parameters of its template (`{id}`, or `{0}`, `{1}` for the parameters given one by one);
240
+ its `errno` is unchanged:
241
+
242
+ ```typescript
243
+ import {Exception} from 'lakutata'
244
+
245
+ //With the message {"errors": {"E_ORDER_NOT_FOUND": "订单 {id} 不存在"}}, a Chinese request gets "订单 7 不存在"
246
+ export class OrderNotFoundException extends Exception {
247
+ public errno: string = 'E_ORDER_NOT_FOUND'
248
+ }
249
+
250
+ throw new OrderNotFoundException('Order {id} not found', {id: 7})
251
+ ```
252
+
253
+ ### The command line help
254
+
255
+ `commanderTranslator()` translates the help and the prompts of the Commander adapter with the messages under `cli`
256
+ (`cli.description`, `cli.commands.<command>.description`, `cli.commands.<command>.arguments.<name>`,
257
+ `cli.commands.<command>.options.<property>`, `cli.help.usage`, `cli.help.options`, `cli.help.default`,
258
+ `cli.prompt.json`…), in the language of the environment; a text without a message stays in English:
259
+
260
+ ```typescript
261
+ import {Application} from 'lakutata'
262
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
263
+ import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
264
+ import {CLIAction} from 'lakutata/decorator/ctrl'
265
+ import {buildI18nOptions, commanderTranslator, I18nMiddleware} from 'lakutata/com/i18n'
266
+
267
+ class GreetController extends Controller {
268
+ @CLIAction('greet', {description: 'Greet someone'})
269
+ public async greet(): Promise<string> {
270
+ return 'Hello'
271
+ }
272
+ }
273
+
274
+ Application.run(() => ({
275
+ id: 'tool.app',
276
+ name: 'Tool',
277
+ components: {
278
+ i18n: buildI18nOptions({
279
+ messages: {
280
+ 'zh-CN': {cli: {commands: {greet: {description: '问候某人'}}, help: {usage: '用法:', options: '选项:'}}}
281
+ }
282
+ }),
283
+ entrypoint: buildEntrypoints({
284
+ controllers: [GreetController],
285
+ middlewares: [I18nMiddleware],
286
+ //TOOL_LANG=zh-CN tool --help, or LANG=zh_CN.UTF-8
287
+ cli: commanderCLIEntrypoint({bin: 'tool', translate: commanderTranslator({environment: 'TOOL_LANG'})})
288
+ })
289
+ },
290
+ bootstrap: ['entrypoint']
291
+ }))
292
+ ```
293
+
294
+ ### Checking the translations
295
+
296
+ `loadCatalogs`, `checkCatalogs` and `generateMessageTypes` are the functions behind `lakutata i18n check` and
297
+ `lakutata i18n types`, for a script or the CI:
298
+
299
+ ```typescript
300
+ import {checkCatalogs, loadCatalogs, type CatalogIssue, type Catalogs} from 'lakutata/com/i18n'
301
+
302
+ const catalogs: Catalogs = await loadCatalogs(['./locales'])
303
+ const issues: CatalogIssue[] = checkCatalogs(catalogs, 'en')
304
+ for (const issue of issues) console.log(`${issue.level} ${issue.locale} ${issue.key}: ${issue.message}`)
305
+ if (issues.some((issue: CatalogIssue): boolean => issue.level === 'error')) process.exitCode = 1
306
+ ```
307
+
308
+ ## API
309
+
310
+ | Export | What it is |
311
+ |---|---|
312
+ | `I18n` | The component: `t`, `has`, `format`, `translator`, `negotiate`, `addMessages`, `getLocales`, `getDefaultLocale`, `getKeys`, `getTimeZone` |
313
+ | `buildI18nOptions` | Builds the component options of `I18n` |
314
+ | `I18nOptions` | The options: `directory`, `messages`, `locales`, `defaultLocale`, `fallback`, `missing`, `currency`, `timeZone`, `validationMessages` |
315
+ | `Translator` | The scoped provider injected in the objects of a request, in the request's language |
316
+ | `ITranslator` | A translator of one language: `locale`, `t`, `has`, `format`, `number`, `date` |
317
+ | `LocaleTranslator` | The `ITranslator` given by `I18n.translator()` |
318
+ | `I18nMiddleware` | The entrypoint middleware choosing the language of each request (`lakutata/com/i18n`, `@lakutata/i18n/middleware`) |
319
+ | `commanderTranslator`, `CommanderTranslatorOptions` | The translation of the Commander CLI help, and its options (`lakutata/com/i18n`, `@lakutata/i18n/middleware`) |
320
+ | `I18nMessages`, `MessageKey`, `MessageParams` | The declaration of the application's keys and arguments, and the types derived from it |
321
+ | `NestedMessages`, `Catalogs` | The messages of a language nested by key parts; the loaded messages by language and key |
322
+ | `loadCatalogs`, `checkCatalogs`, `CatalogIssue`, `generateMessageTypes` | Load the message directories, check the translations, generate the key types |
323
+ | `normalizeLocale`, `parseAcceptLanguage`, `environmentLocales`, `negotiateLocale`, `fallbackChain` | The language tag helpers |
324
+ | `parseMessage`, `messageArguments`, `MessagePart` | The ICU MessageFormat parser and the arguments of a message |
325
+ | `I18nCatalogException`, `I18nMessageSyntaxException`, `I18nMissingKeyException`, `I18nMissingParameterException`, `I18nTranslatorException` | The exceptions |
326
+
327
+ All are exported by `lakutata/com/i18n`; `@lakutata/i18n` exports them except the middleware ones, exported by
328
+ `@lakutata/i18n/middleware`. The typings document each declaration with examples.
329
+
330
+ ## Errors
331
+
332
+ | Exception | When |
333
+ |---|---|
334
+ | `I18nCatalogException` (`E_I18N_CATALOG`) | The component's creation, `addMessages()` or `loadCatalogs()`: a missing directory, invalid JSON, a file or directory name which is not a language tag, an invalid key or value, an option which is not a language tag, an alias without an application; `generateMessageTypes()`: the default language has no messages |
335
+ | `I18nMessageSyntaxException` (`E_I18N_MESSAGE_SYNTAX`) | A message is not valid ICU MessageFormat, when it is first used (or parsed by `format()`, `parseMessage()`, `generateMessageTypes()`) |
336
+ | `I18nMissingKeyException` (`E_I18N_MISSING_KEY`) | `t()`: no message has the key, with `missing: 'throw'` |
337
+ | `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 out of a request |
339
+ | `InvalidValueException` (from `lakutata`) | The component's options are invalid, when it is created |
340
+
341
+ ## See also
342
+
343
+ - `doc/en/I18n.md` of the package `lakutata` (Chinese in `doc/zh`): the guide of the internationalization.
344
+ - `@lakutata/entrypoint` (`lakutata/com/entrypoint`): the controllers, the middlewares and the adapters.
345
+ - `@lakutata/core` (`lakutata`): the components, `Application`, the DTO validation and its languages
346
+ (`loadValidationMessages`, `registerValidationMessages`, `runInValidationLanguage`).
347
+ - `@lakutata/cli`: the `lakutata i18n check`, `lakutata i18n types` and `lakutata i18n add` commands.
@@ -0,0 +1,15 @@
1
+ export * from '../i18n/I18n.js';
2
+ export * from '../i18n/Translator.js';
3
+ export { LocaleTranslator } from '../i18n/lib/LocaleTranslator.js';
4
+ export * from '../i18n/interfaces/I18nOptions.js';
5
+ export * from '../i18n/interfaces/ITranslator.js';
6
+ export * from '../i18n/types/I18nMessages.js';
7
+ export { type Catalogs, type NestedMessages } from '../i18n/lib/Catalog.js';
8
+ export { checkCatalogs, generateMessageTypes, loadCatalogs, type CatalogIssue } from '../i18n/lib/CatalogTools.js';
9
+ export { environmentLocales, fallbackChain, negotiateLocale, normalizeLocale, parseAcceptLanguage } from '../i18n/lib/Locale.js';
10
+ export { messageArguments, parseMessage, type MessagePart } from '../i18n/lib/MessageFormat.js';
11
+ export * from '../i18n/exceptions/I18nCatalogException.js';
12
+ export * from '../i18n/exceptions/I18nMessageSyntaxException.js';
13
+ export * from '../i18n/exceptions/I18nMissingKeyException.js';
14
+ export * from '../i18n/exceptions/I18nMissingParameterException.js';
15
+ export * from '../i18n/exceptions/I18nTranslatorException.js';
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.parseMessage = exports.messageArguments = exports.parseAcceptLanguage = exports.normalizeLocale = exports.negotiateLocale = exports.fallbackChain = exports.environmentLocales = exports.loadCatalogs = exports.generateMessageTypes = exports.checkCatalogs = exports.LocaleTranslator = void 0;
18
+ __exportStar(require("../i18n/I18n.js"), exports);
19
+ __exportStar(require("../i18n/Translator.js"), exports);
20
+ var LocaleTranslator_js_1 = require("../i18n/lib/LocaleTranslator.js");
21
+ Object.defineProperty(exports, "LocaleTranslator", { enumerable: true, get: function () { return LocaleTranslator_js_1.LocaleTranslator; } });
22
+ __exportStar(require("../i18n/interfaces/I18nOptions.js"), exports);
23
+ __exportStar(require("../i18n/interfaces/ITranslator.js"), exports);
24
+ __exportStar(require("../i18n/types/I18nMessages.js"), exports);
25
+ var CatalogTools_js_1 = require("../i18n/lib/CatalogTools.js");
26
+ Object.defineProperty(exports, "checkCatalogs", { enumerable: true, get: function () { return CatalogTools_js_1.checkCatalogs; } });
27
+ Object.defineProperty(exports, "generateMessageTypes", { enumerable: true, get: function () { return CatalogTools_js_1.generateMessageTypes; } });
28
+ Object.defineProperty(exports, "loadCatalogs", { enumerable: true, get: function () { return CatalogTools_js_1.loadCatalogs; } });
29
+ var Locale_js_1 = require("../i18n/lib/Locale.js");
30
+ Object.defineProperty(exports, "environmentLocales", { enumerable: true, get: function () { return Locale_js_1.environmentLocales; } });
31
+ Object.defineProperty(exports, "fallbackChain", { enumerable: true, get: function () { return Locale_js_1.fallbackChain; } });
32
+ Object.defineProperty(exports, "negotiateLocale", { enumerable: true, get: function () { return Locale_js_1.negotiateLocale; } });
33
+ Object.defineProperty(exports, "normalizeLocale", { enumerable: true, get: function () { return Locale_js_1.normalizeLocale; } });
34
+ Object.defineProperty(exports, "parseAcceptLanguage", { enumerable: true, get: function () { return Locale_js_1.parseAcceptLanguage; } });
35
+ var MessageFormat_js_1 = require("../i18n/lib/MessageFormat.js");
36
+ Object.defineProperty(exports, "messageArguments", { enumerable: true, get: function () { return MessageFormat_js_1.messageArguments; } });
37
+ Object.defineProperty(exports, "parseMessage", { enumerable: true, get: function () { return MessageFormat_js_1.parseMessage; } });
38
+ __exportStar(require("../i18n/exceptions/I18nCatalogException.js"), exports);
39
+ __exportStar(require("../i18n/exceptions/I18nMessageSyntaxException.js"), exports);
40
+ __exportStar(require("../i18n/exceptions/I18nMissingKeyException.js"), exports);
41
+ __exportStar(require("../i18n/exceptions/I18nMissingParameterException.js"), exports);
42
+ __exportStar(require("../i18n/exceptions/I18nTranslatorException.js"), exports);
43
+
44
+ //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
45
+ if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})
@@ -0,0 +1,2 @@
1
+ export * from '../i18n/middleware/I18nMiddleware.js';
2
+ export * from '../i18n/middleware/CommanderTranslator.js';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../i18n/middleware/I18nMiddleware.js"), exports);
18
+ __exportStar(require("../i18n/middleware/CommanderTranslator.js"), exports);
19
+
20
+ //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
21
+ if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})