lakutata 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.
Files changed (117) hide show
  1. package/README.md +50 -30
  2. package/README.zh-CN.md +130 -0
  3. package/dist/cjs/exports/QueueComponent.d.ts +1 -0
  4. package/dist/cjs/exports/QueueComponent.js +20 -0
  5. package/dist/esm/exports/QueueComponent.js +1 -0
  6. package/dist/types/exports/QueueComponent.d.ts +1 -0
  7. package/doc/en/Application.md +107 -0
  8. package/doc/en/CLI.md +274 -0
  9. package/doc/en/Cache.md +379 -0
  10. package/doc/en/Command.md +110 -0
  11. package/doc/en/ContainerdIntegration.md +17 -16
  12. package/doc/en/Controllers.md +256 -0
  13. package/doc/en/DBus.md +291 -0
  14. package/doc/en/DTOValidation.md +72 -43
  15. package/doc/en/Database.md +188 -0
  16. package/doc/en/DatabaseDrivers.md +287 -0
  17. package/doc/en/Decorator.md +52 -40
  18. package/doc/en/DockerIntegration.md +7 -6
  19. package/doc/en/Entities.md +240 -0
  20. package/doc/en/Entrypoint.md +216 -0
  21. package/doc/en/Exceptions.md +174 -0
  22. package/doc/en/GRPC.md +251 -0
  23. package/doc/en/GettingStarted.md +165 -0
  24. package/doc/en/HTTP.md +507 -0
  25. package/doc/en/HelperFunctions.md +11 -11
  26. package/doc/en/I18n.md +119 -115
  27. package/doc/en/Injection.md +144 -0
  28. package/doc/en/Installers.md +280 -0
  29. package/doc/en/Logger.md +206 -0
  30. package/doc/en/MCP.md +293 -0
  31. package/doc/en/Middlewares.md +213 -0
  32. package/doc/en/Migrations.md +202 -0
  33. package/doc/en/MongoEmbedded.md +149 -0
  34. package/doc/en/Monitor.md +164 -0
  35. package/doc/en/Mutex.md +27 -25
  36. package/doc/en/NATS.md +246 -0
  37. package/doc/en/NativeAddons.md +212 -0
  38. package/doc/en/Objects.md +143 -0
  39. package/doc/en/Overview.md +62 -85
  40. package/doc/en/Packaging.md +193 -0
  41. package/doc/en/ProjectStructure.md +124 -0
  42. package/doc/en/Queue.md +269 -0
  43. package/doc/en/Schedules.md +217 -0
  44. package/doc/en/Security.md +10 -9
  45. package/doc/en/ServiceCalls.md +504 -0
  46. package/doc/en/Time.md +19 -20
  47. package/doc/en/UpgradingFrom1.md +435 -0
  48. package/doc/en/UpgradingFrom2.md +315 -0
  49. package/doc/en/Workspaces.md +189 -0
  50. package/doc/en/examples/BackgroundJobs.md +677 -0
  51. package/doc/en/examples/CommandLineTool.md +664 -0
  52. package/doc/en/examples/DesktopApp.md +809 -0
  53. package/doc/en/examples/DeviceService.md +607 -0
  54. package/doc/en/examples/MCPServer.md +653 -0
  55. package/doc/en/examples/README.md +16 -0
  56. package/doc/en/examples/RestAPI.md +490 -0
  57. package/doc/en/examples/ServiceCalls.md +829 -0
  58. package/doc/en/examples/WebApp.md +568 -0
  59. package/doc/zh/Application.md +75 -0
  60. package/doc/zh/CLI.md +250 -0
  61. package/doc/zh/Cache.md +328 -0
  62. package/doc/zh/Command.md +78 -0
  63. package/doc/zh/ContainerdIntegration.md +9 -9
  64. package/doc/zh/Controllers.md +228 -0
  65. package/doc/zh/DBus.md +257 -0
  66. package/doc/zh/DTOValidation.md +49 -19
  67. package/doc/zh/Database.md +175 -0
  68. package/doc/zh/DatabaseDrivers.md +261 -0
  69. package/doc/zh/Decorator.md +25 -18
  70. package/doc/zh/DockerIntegration.md +6 -6
  71. package/doc/zh/Entities.md +224 -0
  72. package/doc/zh/Entrypoint.md +193 -0
  73. package/doc/zh/Exceptions.md +158 -0
  74. package/doc/zh/GRPC.md +226 -0
  75. package/doc/zh/GettingStarted.md +148 -0
  76. package/doc/zh/HTTP.md +445 -0
  77. package/doc/zh/HelperFunctions.md +2 -2
  78. package/doc/zh/I18n.md +26 -26
  79. package/doc/zh/Injection.md +129 -0
  80. package/doc/zh/Installers.md +232 -0
  81. package/doc/zh/Logger.md +188 -0
  82. package/doc/zh/MCP.md +265 -0
  83. package/doc/zh/Middlewares.md +198 -0
  84. package/doc/zh/Migrations.md +178 -0
  85. package/doc/zh/MongoEmbedded.md +118 -0
  86. package/doc/zh/Monitor.md +145 -0
  87. package/doc/zh/Mutex.md +10 -10
  88. package/doc/zh/NATS.md +207 -0
  89. package/doc/zh/NativeAddons.md +180 -0
  90. package/doc/zh/Objects.md +119 -0
  91. package/doc/zh/Overview.md +46 -71
  92. package/doc/zh/Packaging.md +154 -0
  93. package/doc/zh/ProjectStructure.md +102 -0
  94. package/doc/zh/Queue.md +214 -0
  95. package/doc/zh/Schedules.md +193 -0
  96. package/doc/zh/Security.md +6 -6
  97. package/doc/zh/ServiceCalls.md +459 -0
  98. package/doc/zh/Time.md +2 -2
  99. package/doc/zh/UpgradingFrom1.md +347 -0
  100. package/doc/zh/UpgradingFrom2.md +230 -0
  101. package/doc/zh/Workspaces.md +150 -0
  102. package/doc/zh/examples/BackgroundJobs.md +618 -0
  103. package/doc/zh/examples/CommandLineTool.md +618 -0
  104. package/doc/zh/examples/DesktopApp.md +748 -0
  105. package/doc/zh/examples/DeviceService.md +546 -0
  106. package/doc/zh/examples/MCPServer.md +604 -0
  107. package/doc/zh/examples/README.md +14 -0
  108. package/doc/zh/examples/RestAPI.md +458 -0
  109. package/doc/zh/examples/ServiceCalls.md +765 -0
  110. package/doc/zh/examples/WebApp.md +520 -0
  111. package/package.json +46 -17
  112. package/doc/en/ActionPattern.md +0 -408
  113. package/doc/en/Core.md +0 -312
  114. package/doc/en/ORMIntegration.md +0 -341
  115. package/doc/zh/ActionPattern.md +0 -334
  116. package/doc/zh/Core.md +0 -256
  117. package/doc/zh/ORMIntegration.md +0 -319
package/doc/en/I18n.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # Internationalization (i18n)
2
2
 
3
- `lakutata/com/i18n` (the `@lakutata/i18n` package) manages the messages of each language by their **keys**: the code
4
- uses a key, and each language gives the sentence of the key in its own file. The messages use the syntax of
5
- [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/); their numbers, currencies, dates
6
- and plurals are formatted in their language by the `Intl` of Node.js, with no third-party library.
3
+ `lakutata/com/i18n` (package `@lakutata/i18n`) manages the messages of an application by **keys**: the code uses a key,
4
+ and each language gives the text of that key in its own file. The messages are written in [ICU
5
+ MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/); their numbers, currencies, dates and
6
+ plurals are formatted for the language by the built-in `Intl` of Node.js, with no third-party library.
7
7
 
8
- ## The files of the messages
8
+ ## Message files
9
9
 
10
- A JSON file by language, named by its language tag (BCP 47: `en`, `zh-CN`, `zh-Hant-TW`):
10
+ One JSON file per language, named after its language tag (BCP 47: `en`, `zh-CN`, `zh-Hant-TW`):
11
11
 
12
12
  ```
13
13
  locales/
@@ -24,10 +24,10 @@ locales/
24
24
  }
25
25
  ```
26
26
 
27
- The nesting of the JSON is the one of the keys: the keys above are `order.count` and `order.created`. A part of a key
28
- cannot contain `.`.
27
+ The nesting of the JSON gives the keys: the keys above are `order.count` and `order.created`. No part of a key may
28
+ contain `.`.
29
29
 
30
- A language can also be a directory, the names of its files (and of their directories) prefixing their keys:
30
+ A language can also be a directory, in which the names of the files (and of the subdirectories) prefix their keys:
31
31
 
32
32
  ```
33
33
  locales/
@@ -36,7 +36,7 @@ locales/
36
36
  admin/user.json → admin.user.*
37
37
  ```
38
38
 
39
- Both layouts can be mixed in a directory.
39
+ Both layouts can be mixed in one directory.
40
40
 
41
41
  ## Configuration
42
42
 
@@ -53,9 +53,9 @@ Application
53
53
  i18n: buildI18nOptions({
54
54
  directory: '@locales', //an alias, or several directories
55
55
  defaultLocale: 'en', //the default language, the last fallback (en by default)
56
- fallback: {'zh-TW': 'zh-CN'}, //the messages missing in zh-TW are the ones of zh-CN
56
+ fallback: {'zh-TW': 'zh-CN'}, //the messages missing in zh-TW are taken from zh-CN
57
57
  currency: 'CNY', //the currency of {price, number, currency}
58
- timeZone: 'Asia/Shanghai' //the time zone of the dates and the times (the process's by default)
58
+ timeZone: 'Asia/Shanghai' //the time zone of dates and times (that of the process by default)
59
59
  })
60
60
  }
61
61
  }))
@@ -63,8 +63,9 @@ Application
63
63
 
64
64
  Name the component `i18n`: the middleware and `commanderTranslator` use that name.
65
65
 
66
- The messages can also be given in the code instead of a directory: the `messages` option gives them by language, as
67
- object literals or imported JSON. They are then bundled with the code (`lakutata package bundle`), no file read at run time:
66
+ The messages can also be given in the code instead of a directory: the `messages` option takes them by language, as
67
+ object literals or imported JSON. They are then bundled with the code (`lakutata package bundle`), and no file is read
68
+ at run time:
68
69
 
69
70
  ```typescript
70
71
  import {buildI18nOptions} from 'lakutata/com/i18n'
@@ -82,15 +83,15 @@ const i18n = buildI18nOptions({
82
83
 
83
84
  | Option | Description |
84
85
  |---|---|
85
- | `directory` | The directories of the messages; aliases accepted |
86
- | `locales` | The supported languages, which `negotiate()` chooses from; by default the default language, then the languages of the messages |
86
+ | `directory` | The directories of the message files; aliases are accepted |
87
+ | `locales` | The supported languages, which `negotiate()` chooses from; by default, the default language followed by the languages found in the messages |
87
88
  | `defaultLocale` | The default language, `en` by default |
88
- | `fallback` | The languages a message missing in a language is looked up in next (`{'zh-TW': 'zh-CN'}`, or a list) |
89
- | `messages` | Messages by language given in the code, loaded after the directories (the same keys replaced); no `directory` is needed with them |
90
- | `missing` | A missing message or argument: `'throw'` throws; `'key'` gives its key (the argument as `{name}`) and warns once. Thrown by default, the key given when `NODE_ENV=production` |
89
+ | `fallback` | Where to look next when a language lacks a message (`{'zh-TW': 'zh-CN'}`, or a list) |
90
+ | `messages` | Messages by language given in the code, loaded after the directories (replacing the same keys); with them, `directory` is not needed |
91
+ | `missing` | What happens to a missing message or argument: `'throw'` throws; `'key'` returns the key (and leaves a missing argument as `{name}`), with a warning the first time. `'throw'` by default, `'key'` when `NODE_ENV=production` |
91
92
  | `currency` | The currency when neither the message nor its arguments give one (ISO 4217) |
92
- | `timeZone` | The time zone of the dates and the times (IANA) |
93
- | `validationMessages` | Whether the built-in validation messages of the supported languages are registered, `true` by default (see "The errors of the validation") |
93
+ | `timeZone` | The time zone of dates and times (IANA) |
94
+ | `validationMessages` | Whether to register the built-in validation messages of the supported languages, `true` by default (see "Validation errors") |
94
95
 
95
96
  ## Usage
96
97
 
@@ -111,22 +112,22 @@ class OrderService extends Provider {
111
112
 
112
113
  | Method | Description |
113
114
  |---|---|
114
- | `t(key, params?, locale?)` | The message of a key in a language, formatted with its arguments; the default language by default |
115
+ | `t(key, params?, locale?)` | The message of a key in a language, formatted with the arguments; in the default language when none is given |
115
116
  | `has(key, locale?)` | Whether a language (or the ones it falls back to) has a key |
116
- | `format(message, params?, locale?)` | Format an ICU message which is not in the files |
117
- | `translator(locale)` | A translator of a language (`ITranslator`), which does not need the language each time |
118
- | `negotiate(requested)` | The supported language for the requested ones (preferred first), the default one when none is supported |
119
- | `addMessages(locale, messages)` | Add messages of a language at run time, the same keys replaced |
120
- | `getLocales()`, `getDefaultLocale()`, `getKeys(locale)`, `getTimeZone()` | The supported languages, the default one, the own keys of a language, the time zone |
117
+ | `format(message, params?, locale?)` | Formats an ICU message that is not in the files |
118
+ | `translator(locale)` | A translator bound to a language (`ITranslator`), so that the language is not passed each time |
119
+ | `negotiate(requested)` | The supported language that best matches the requested ones (preferred first); the default language when none is supported |
120
+ | `addMessages(locale, messages)` | Adds messages to a language at run time, replacing the same keys |
121
+ | `getLocales()`, `getDefaultLocale()`, `getKeys(locale)`, `getTimeZone()` | The supported languages, the default language, the keys a language defines itself, the time zone |
121
122
 
122
- `t()`, `has()`, `format()` and `translator()` do not negotiate the language: an unsupported language is looked up as
123
- given, then falls back. `addMessages()` returns a promise resolved once the validation messages of a new language are
124
- registered (the messages themselves are given by `t()` as soon as it returns).
123
+ `t()`, `has()`, `format()` and `translator()` do not negotiate: a language that is not supported is looked up as given,
124
+ then falls back. `addMessages()` returns a promise that resolves once the validation messages of a new language are
125
+ registered; `t()` gives the new messages as soon as `addMessages()` returns.
125
126
 
126
127
  ### Answering a request in its language
127
128
 
128
- With the `I18nMiddleware` on the entrypoint, each request has its language; the objects of the request inject the
129
- `Translator`, the messages of that language:
129
+ With the `I18nMiddleware` on the entrypoint, each request gets a language; the objects of the request inject the
130
+ `Translator`, which gives the messages in that language:
130
131
 
131
132
  ```typescript
132
133
  import {Application} from 'lakutata'
@@ -165,8 +166,8 @@ Application.run(() => ({
165
166
  }))
166
167
  ```
167
168
 
168
- The languages a request asks for are read in this order, then negotiated with the supported ones (see below), the
169
- default language when none is supported:
169
+ The languages a request asks for are read from the sources below, in order, then negotiated with the supported languages
170
+ (see "Negotiation and fallbacks"); when none is supported, the request gets the default language:
170
171
 
171
172
  | Entrypoint | Sources (preferred first) |
172
173
  |---|---|
@@ -179,11 +180,11 @@ default language when none is supported:
179
180
  A query or cookie value that is not a language tag is skipped.
180
181
 
181
182
  - The objects of a request (the controller, the `@Scoped` providers, the access control rules) inject the same
182
- `Translator`; `translator.locale` is the language of the request, so is `context.locale`.
183
- - The middleware uses the component named `i18n`. To use another one (`@Inject('...') protected readonly i18n`), other
184
- names of the query parameter or of the cookie (`queryParameter`, `cookie`), an environment variable
185
- (`environmentVariable`), or another way to choose the language (the one saved by the signed-in user), extend
186
- `I18nMiddleware`:
183
+ `Translator`; `translator.locale` and `context.locale` are the language of the request.
184
+ - The middleware uses the component named `i18n`. Extend `I18nMiddleware` to use another component
185
+ (`@Inject('...') protected readonly i18n`), to rename the query parameter or the cookie (`queryParameter`, `cookie`),
186
+ to read an environment variable (`environmentVariable`), or to choose the language another way (for example, the
187
+ language the signed-in user saved):
187
188
 
188
189
  ```typescript
189
190
  import {type BaseContext} from 'lakutata/com/entrypoint'
@@ -202,21 +203,22 @@ A query or cookie value that is not a language tag is skipped.
202
203
  }
203
204
  ```
204
205
 
205
- - The `Translator` of a request without the `I18nMiddleware` throws an `I18nTranslatorException`.
206
+ - Without the `I18nMiddleware`, the methods of the `Translator` throw an `I18nTranslatorException`.
206
207
  - A translator (the `Translator`, and the `ITranslator` of `translator(locale)`) has `locale`, `t(key, params)`,
207
208
  `has(key)`, `format(message, params)`, `number(value, options)` and `date(value, options)`.
208
- - Out of a request (the schedules, the singleton components), the component gives `t(key, params, locale)` and
209
- `translator(locale)`.
210
- - To handle the languages yourself, `parseAcceptLanguage(header)` reads `Accept-Language` and `environmentLocales()`
211
- the languages of the environment (`zh_CN.UTF-8` becomes `zh-CN`), for `negotiate()`.
209
+ - Outside a request (in the schedules or the singleton components), use the `t(key, params, locale)` and
210
+ `translator(locale)` of the component.
211
+ - To choose the language yourself, `parseAcceptLanguage(header)` reads an `Accept-Language` header and
212
+ `environmentLocales()` reads the languages of the environment (`zh_CN.UTF-8` becomes `zh-CN`); pass their result to
213
+ `negotiate()`.
212
214
 
213
- ## The help and the prompts of a command line tool
215
+ ## Help and prompts of a command line tool
214
216
 
215
- The `translate` option of the Commander adapter (`lakutata/adapter/commander`) gives the help and the prompts of a
216
- command line tool in the language of the user: the descriptions of the program and of its commands, of their arguments
217
- and options, the hints of the defaults and of the choices, the titles of the help (Usage:, Options:), the descriptions
218
- of `--help` and `--version`, and the questions asked for the missing required options. The names of the commands and of
219
- the options are not translated.
217
+ The `translate` option of the Commander adapter (`lakutata/adapter/commander`) shows the help and the prompts of a
218
+ command line tool in the language of the user: the descriptions of the program, of its commands and of their arguments
219
+ and options, the hints about defaults and choices, the headings of the help (Usage:, Options:), the descriptions of
220
+ `--help` and `--version`, and the questions asked for missing required options. The names of commands and options are
221
+ not translated.
220
222
 
221
223
  `commanderTranslator()` translates them with the messages of an i18n component:
222
224
 
@@ -242,7 +244,7 @@ Application.run(() => ({
242
244
  i18n: buildI18nOptions({messages: {'zh-CN': zhCN}}),
243
245
  entrypoint: buildEntrypoints({
244
246
  controllers: [GreetController],
245
- //The Translator of the controllers running the commands has the same language
247
+ //The controllers that run the commands get a Translator in the same language
246
248
  middlewares: [I18nMiddleware],
247
249
  //TOOL_LANG=zh-CN tool --help, or LANG=zh_CN.UTF-8
248
250
  cli: commanderCLIEntrypoint({bin: 'tool', translate: commanderTranslator({environment: 'TOOL_LANG'})})
@@ -268,39 +270,40 @@ Application.run(() => ({
268
270
  }
269
271
  ```
270
272
 
271
- - The keys (prefixed by `cli.` by default, the `prefix` option changes it):
273
+ - The keys (with the prefix `cli.` by default, which the `prefix` option changes):
272
274
  - the program: `description`; the commands: `commands.<command>.description`, `commands.<command>.arguments.<name>`,
273
275
  `commands.<command>.options.<property>`;
274
276
  - the help: `help.usage`, `help.arguments`, `help.options`, `help.commands`, `help.helpOption`, `help.helpCommand`,
275
277
  `help.versionOption`;
276
278
  - the hints: `help.default` (`default: {value}`), `help.choices` (`choices: {choices}`), `help.hints` (` ({hints})`,
277
- the hints after a description), `help.separator` (`, `), `help.negate` (`Turn off {flag}`);
279
+ how the hints follow a description), `help.separator` (`, `), `help.negate` (`Turn off {flag}`);
278
280
  - the questions: `prompt.json` (`Not a JSON object`).
279
- - A text without a message is the English one of the code: only the other languages need messages.
280
- - The language is chosen once, when the adapter starts: the environment variable of the `environment` option, then the
281
- environment (`LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG`), then the language of the process, negotiated with the
282
- supported languages.
283
- - The wide characters (Chinese, Japanese, Korean) take two columns: the long lines of the help are wrapped between them,
284
- no punctuation starting a line.
281
+ - A text without a message stays in the English of the code, so only the other languages need messages.
282
+ - The language is chosen once, when the adapter starts: from the variable named by the `environment` option, then
283
+ `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG`, then the language of the process, and negotiated with the supported
284
+ languages.
285
+ - Wide characters (Chinese, Japanese, Korean) count as two columns; the long lines of the help can break between them,
286
+ and no line starts with punctuation.
285
287
  - `translate` can be given a function too: `translate: () => (text) => messages[text.key] ?? text.text`.
286
288
 
287
- ## The errors of the validation
289
+ ## Validation errors
288
290
 
289
- With the `I18nMiddleware`, the errors of the validations of a request are in the language of the request: its input
290
- (the DTO of `@HTTPAction`, `@CLIAction`, `@ServiceAction`), the arguments and the results of the methods it calls
291
- (`@Accept`, `@Return`), and the validations of its code (`DTO.validate`, `DTO.validateAsync`).
291
+ With the `I18nMiddleware`, the validation errors of a request are in its language: the errors of its input (the DTO of
292
+ `@HTTPAction`, `@CLIAction`, `@ServiceAction`), of the arguments and results of the methods it calls (`@Accept`,
293
+ `@Return`), and of the validations its code runs (`DTO.validate`, `DTO.validateAsync`).
292
294
 
293
- The validation messages of 20 languages are built in; the component registers the ones of its supported languages when
294
- it starts (the languages used only are loaded):
295
+ Validation messages for 20 languages are built in; when it starts, the component registers those of its supported
296
+ languages (only the languages in use are loaded):
295
297
 
296
- `en`, `zh-CN`, `zh-TW`, `ja`, `ko`, `fr`, `de`, `es`, `pt-BR`, `it`, `nl`, `pl`, `ru`, `uk`, `tr`, `vi`, `th`, `id`, `hi`, `ar`
298
+ `en`, `zh-CN`, `zh-TW`, `ja`, `ko`, `fr`, `de`, `es`, `pt-BR`, `it`, `nl`, `pl`, `ru`, `uk`, `tr`, `vi`, `th`, `id`,
299
+ `hi`, `ar`
297
300
 
298
- A supported language uses the closest built-in one: `zh`, `zh-Hans`, `zh-SG` use `zh-CN`; `zh-Hant`, `zh-HK`, `zh-MO`
299
- use `zh-TW`; the others match by their language (`pt-PT` uses `pt-BR`, `fr-CA` uses `fr`). A language without built-in
300
- messages uses the ones of the languages it falls back to, then English.
301
+ Each supported language uses the closest built-in one: `zh`, `zh-Hans`, `zh-SG` use `zh-CN`; `zh-Hant`, `zh-HK`, `zh-MO`
302
+ use `zh-TW`; other tags match by their language part (`pt-PT` uses `pt-BR`, `fr-CA` uses `fr`). A language without
303
+ built-in messages uses those of the languages it falls back to, then English.
301
304
 
302
- An application replaces or adds validation messages in its messages: under `validation`, by the codes of the errors, in
303
- the syntax of the templates of the validation (`{{#label}}`, `{{#limit}}`), not ICU:
305
+ An application can replace or add validation messages in its own messages: under `validation`, keyed by error code, and
306
+ written in the template syntax of the validation (`{{#label}}`, `{{#limit}}`) rather than ICU:
304
307
 
305
308
  ```json
306
309
  {
@@ -311,23 +314,23 @@ the syntax of the templates of the validation (`{{#label}}`, `{{#limit}}`), not
311
314
  }
312
315
  ```
313
316
 
314
- - The validation messages of a language are layered: the built-in and the application messages of the languages it
315
- falls back to, then its own built-in and application messages, the later ones replacing the earlier ones.
316
- - `validationMessages: false` registers no built-in messages, the `validation` messages of the application only.
317
- - The input of a request is validated as before; only an invalid input is validated again in the language of the
318
- request, for its messages. A valid input is not affected at all.
319
- - A few messages contain English given by the validation itself: the sort order (`ascending`), the reason of a
317
+ - The validation messages of a language are layered: first the built-in and application messages of the languages it
318
+ falls back to, then its own built-in and application messages; later layers replace earlier ones.
319
+ - `validationMessages: false` registers no built-in messages, only the `validation` messages of the application.
320
+ - The input of a request is validated as usual; only invalid input is validated a second time, in the language of the
321
+ request, to get the messages. Valid input is not affected at all.
322
+ - A few messages contain English text from the validation library itself: the sort order (`ascending`), the reason for a
320
323
  reference error, the message of a custom assertion. These parts stay in English in every language.
321
- - Without the i18n component, the functions of `lakutata` do it: `loadValidationMessages(language)` loads the built-in
322
- messages of a language, `registerValidationMessages(language, messages)` registers them, and a validation gives them
323
- with its `{errors: {language}}` option, or `runInValidationLanguage(language, fn)` gives the language to every
324
- validation of a code (its asynchronous calls too).
324
+ - Without the i18n component, use the functions of `lakutata`: `loadValidationMessages(language)` loads the built-in
325
+ messages of a language and `registerValidationMessages(language, messages)` registers them. A validation then chooses
326
+ a language with its `{errors: {language}}` option, or `runInValidationLanguage(language, fn)` sets the language of
327
+ every validation that `fn` runs, its asynchronous calls included.
325
328
 
326
- ## The messages of the exceptions
329
+ ## Exception messages
327
330
 
328
- With the `I18nMiddleware`, an exception thrown by a request whose message is in the messages of the application is
329
- given the message in the language of the request: its key is `errors.<the errno of the exception>`, formatted with the
330
- parameters of the template of the exception:
331
+ With the `I18nMiddleware`, when a request throws an exception that has a message among the messages of the application,
332
+ the exception gets that message in the language of the request. The key is `errors.<errno of the exception>`, and the
333
+ message is formatted with the parameters of the template of the exception:
331
334
 
332
335
  ```typescript
333
336
  import {Exception} from 'lakutata'
@@ -343,45 +346,46 @@ throw new OrderNotFoundException('Order {id} not found', {id: 7})
343
346
  {"errors": {"E_ORDER_NOT_FOUND": "订单 {id} 不存在"}}
344
347
  ```
345
348
 
346
- - A request in Chinese gets `订单 7 不存在`, a request in English the original `Order 7 not found`; the `errno` and
347
- the other properties are not changed.
348
- - The parameters given one by one (`new XxxException('{0} of {1}', a, b)`) are `{0}`, `{1}` in the message.
349
- - An exception without a message is as it is; so is one whose message cannot be formatted (a missing parameter), with a
350
- warning.
351
- - Out of a request, `exceptionParams(exception)` (from `lakutata`) gives the parameters of an exception, to translate
352
- it.
349
+ - A request in Chinese gets `订单 7 不存在`, a request in English the original `Order 7 not found`; the `errno` and the other
350
+ properties stay unchanged.
351
+ - Parameters passed one by one (`new XxxException('{0} of {1}', a, b)`) are referred to as `{0}`, `{1}` in the message.
352
+ - An exception that has no message in the application stays as it is; so does one whose message cannot be formatted
353
+ (because of a missing parameter, for example), with a warning.
354
+ - Outside a request, `exceptionParams(exception)` (from `lakutata`) gives the parameters of an exception, so that you
355
+ can translate it yourself.
353
356
 
354
357
  ## Negotiation and fallbacks
355
358
 
356
- **Negotiation** (`negotiate`): for each requested language in turn, the same tag, then its language alone
357
- (`en-GB` → `en`), then another region of its language (`pt-PT` → `pt-BR`, in the order of the supported languages); the
358
- default language when none matches.
359
+ **Negotiation** (`negotiate`) tries each requested language in turn: the same tag, then its language alone (`en-GB` →
360
+ `en`), then another region of the same language (`pt-PT` → `pt-BR`, in the order of the supported languages). When none
361
+ matches, it returns the default language.
359
362
 
360
- **Fallbacks**: a message missing in a language is looked up in the languages of its `fallback`, then in the tags it is
361
- more specific than (`zh-Hant-TW` → `zh-Hant` → `zh`), then in the default language. A message is always formatted in
362
- the **language asked**: an English message falling back in a German request still writes `1.000`.
363
+ **Fallbacks**: when a language lacks a message, the message is looked up in the languages of its `fallback`, then in its
364
+ shorter tags (`zh-Hant-TW` → `zh-Hant` → `zh`), then in the default language. A message is always formatted in the
365
+ **requested language**: an English message used as the fallback for a German request still writes `1.000`.
363
366
 
364
- ## The syntax of the messages
367
+ ## Message syntax
365
368
 
366
369
  | Syntax | Example |
367
370
  |---|---|
368
- | Argument | `Hello, {name}`: a number is formatted in the language, a `Date` as a date, the rest as a string |
371
+ | Argument | `Hello, {name}`: a number is formatted for the language, a `Date` as a date, anything else as a string |
369
372
  | Number | `{n, number}`, `{n, number, integer}`, `{n, number, percent}` |
370
373
  | Currency | `{price, number, currency}` (the currency of the `currency` argument or of the configuration), `{price, number, currency/EUR}` |
371
374
  | Date, time | `{at, date, short}`, `{at, time, long}`, styles `short`, `medium` (the default), `long`, `full`; a `Date`, a timestamp, a date string or an object with `toDate()` |
372
- | Plural | `{count, plural, =0 {none} one {# item} other {# items}}`: the `=value` first, then the plural rules of the language; `#` is the number formatted; `offset:1` supported |
375
+ | Plural | `{count, plural, =0 {none} one {# item} other {# items}}`: the `=value` cases first, then the plural rules of the language; `#` is the formatted number; `offset:1` is supported |
373
376
  | Ordinal | `{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}` |
374
377
  | Select | `{role, select, admin {Administrator} other {User}}` |
375
378
  | Quoting | `''` is an apostrophe; `'{literal}'` writes the braces (`'#'` writes `#` in a plural) |
376
379
 
377
380
  `plural`, `selectordinal` and `select` need an `other` option. A message is parsed once, when it is first used; a syntax
378
- error throws an `I18nMessageSyntaxException` telling its language, its key and its position, e.g. `zh-CN: order.count: the "other" option is required at 1`.
381
+ error throws an `I18nMessageSyntaxException` that gives the language, the key and the position, e.g.
382
+ `zh-CN: order.count: the "other" option is required at 1`.
379
383
 
380
- ## The types of the keys
384
+ ## Typed keys
381
385
 
382
- An application can declare its keys and their arguments: a wrong key or a missing argument is then an error of the
383
- compilation. `lakutata i18n types` generates the declaration from the messages of the default language (see below), or
384
- it is written by hand:
386
+ An application can declare its keys and their arguments; a wrong key or a missing argument then fails the compilation.
387
+ `lakutata i18n types` generates the declaration from the messages of the default language (see below), or you can write
388
+ it by hand:
385
389
 
386
390
  ```typescript
387
391
  //src/i18n.d.ts
@@ -397,18 +401,18 @@ declare module 'lakutata/com/i18n' {
397
401
 
398
402
  Without them, `t()` accepts any key and any arguments.
399
403
 
400
- ## The command line tools
404
+ ## Command line tools
401
405
 
402
- The `lakutata` command line (`@lakutata/cli`) handles the directories of the messages (`--dir`, `locales` by default;
403
- several separated by commas):
406
+ The `lakutata` command line (`@lakutata/cli`) works on the message directories (`--dir`, `locales` by default; separate
407
+ several with commas):
404
408
 
405
409
  | Command | Description |
406
410
  |---|---|
407
- | `lakutata i18n check` | Checks the messages of the languages against the default language (`--default`, `en` by default). Errors: a syntax error of ICU, a key the default language does not have, an argument the default message does not have, an unknown validation code or a variable of another code; warnings: a missing message (falling back), an argument not used, a message the same as the default one (not translated yet). It fails on the errors, on the warnings too with `--strict`, for the CI |
408
- | `lakutata i18n types` | Declares the keys of the messages of the default language and their arguments (`--out`, `src/i18n.d.ts` by default); `--module @lakutata/i18n` for the applications importing `@lakutata/i18n` |
409
- | `lakutata i18n add <locale>` | Adds a language made of the messages of the default language (a file or a directory, as the default language has; its `validation` part left, the language having its built-in validation messages), to be translated |
411
+ | `lakutata i18n check` | Checks the messages of each language against the default language (`--default`, `en` by default). Errors: an ICU syntax error, a key the default language does not have, an argument the default message does not have, an unknown validation code, or a variable that belongs to another code. Warnings: a missing message (which falls back), an unused argument, a message identical to the default one (probably not translated yet). It fails on errors, and also on warnings with `--strict`, for CI |
412
+ | `lakutata i18n types` | Writes the type declaration of the keys of the default language and their arguments (`--out`, `src/i18n.d.ts` by default); add `--module @lakutata/i18n` for applications that import `@lakutata/i18n` |
413
+ | `lakutata i18n add <locale>` | Adds a language as a copy of the messages of the default language, ready to translate: a file or a directory, like the default language, without the `validation` part (the new language uses its built-in validation messages) |
410
414
 
411
- The checks are functions of the code too: `loadCatalogs(directories)`, `checkCatalogs(catalogs, defaultLocale)`,
415
+ The checks are also available as functions: `loadCatalogs(directories)`, `checkCatalogs(catalogs, defaultLocale)`,
412
416
  `generateMessageTypes(catalogs, defaultLocale)`.
413
417
 
414
418
  ## Exceptions
@@ -416,7 +420,7 @@ The checks are functions of the code too: `loadCatalogs(directories)`, `checkCat
416
420
  | Exception | When |
417
421
  |---|---|
418
422
  | `I18nCatalogException` | An invalid file or configuration: invalid JSON, a file name which is not a language tag, a missing directory, an alias not resolved |
419
- | `I18nMessageSyntaxException` | The syntax of a message is wrong |
423
+ | `I18nMessageSyntaxException` | A message has a syntax error |
420
424
  | `I18nMissingKeyException` | A key is missing (`missing: 'throw'`) |
421
425
  | `I18nMissingParameterException` | An argument is missing (`missing: 'throw'`), or a currency message has no currency |
422
- | `I18nTranslatorException` | The `Translator` used without the `I18nMiddleware`, or out of a request |
426
+ | `I18nTranslatorException` | The `Translator` is used without the `I18nMiddleware`, or outside a request |
@@ -0,0 +1,144 @@
1
+ # Lakutata Dependency Injection
2
+
3
+ An object does not create the objects it uses: it declares them, and the container provides them when it creates the
4
+ object. This page covers `@Inject` and `@Autoload`, the options of objects (`@Configurable`), and sharing a feature
5
+ between applications as a package. [Decorator](Decorator.md), section 1, gives every rule of these decorators.
6
+
7
+ ## 1. Injecting objects
8
+
9
+ `@Inject` (from `lakutata/decorator/di`) sets a property when the object is created, before `init()`:
10
+
11
+ - `@Inject()`: the object registered under the property name;
12
+ - `@Inject('name')` or `@Inject(symbol)`: the object registered under that name;
13
+ - `@Inject(SomeClass)`: the object registered for the class (`@Inject(Application)` gives the application);
14
+ - `@Inject('name', transform)`: the value returned by `transform(object)` (it may be async).
15
+
16
+ A class marked `@Autoload()` is registered the first time it is requested, instead of failing: a singleton in the root
17
+ container, a scoped class in the nearest scope, a transient class in the container that requested it. A dependency that
18
+ cannot be resolved fails the creation with a `DependencyInjectionException`.
19
+
20
+ ```typescript
21
+ import {Application, Component, Provider} from 'lakutata'
22
+ import {Autoload, Inject, Scoped} from 'lakutata/decorator/di'
23
+
24
+ //Not declared in the application: registered the first time it is injected
25
+ @Autoload()
26
+ @Scoped()
27
+ class RequestClock extends Provider {
28
+ public readonly startedAt: number = Date.now()
29
+ }
30
+
31
+ class Reports extends Component {
32
+ @Inject(RequestClock)
33
+ protected readonly clock: RequestClock
34
+
35
+ @Inject(Application)
36
+ protected readonly app: Application
37
+
38
+ public elapsed(): number {
39
+ return Date.now() - this.clock.startedAt
40
+ }
41
+ }
42
+ ```
43
+
44
+ Code that is not itself an object of the container (an `onLaunched` handler, for example) gets objects with
45
+ `app.getObject(name)`; `app.getObject(SomeClass)` works for a class registered on its own, such as an autoloaded class,
46
+ not for a component declared under a name.
47
+
48
+ ## 2. Options
49
+
50
+ `@Configurable(schema, fn?)` turns a property into an option of the object. Its value comes from the object's
51
+ declaration (`{class: Mailer, host: 'smtp.example.com'}`) or from the second argument of `getObject()`; the schema
52
+ validates it, and `fn`, when given, transforms it. The application checks the options of the objects it declares when
53
+ it launches: an invalid value stops the launch with an `InvalidObjectOptionsException`, and an option that matches no
54
+ configurable property produces a warning.
55
+
56
+ ```typescript
57
+ import {Application, Component, DTO, type ILogger} from 'lakutata'
58
+ import {Configurable, Inject} from 'lakutata/decorator/di'
59
+
60
+ class Mailer extends Component {
61
+ //An option, set by the component's declaration in the application options
62
+ @Configurable(DTO.String().required())
63
+ protected readonly host: string
64
+
65
+ //25 unless configured: the initializer is the default and is validated like a configured value
66
+ @Configurable(DTO.Number().integer().min(1).max(65535))
67
+ protected readonly port: number = 25
68
+
69
+ //The logger preset by the lakutata package as the "log" component
70
+ @Inject('log')
71
+ protected readonly log: ILogger
72
+
73
+ protected async init(): Promise<void> {
74
+ this.log.info('Mailer connected to %s:%d', this.host, this.port)
75
+ }
76
+
77
+ public async send(to: string, text: string): Promise<void> {
78
+ this.log.info('Mail to %s: %s', to, text)
79
+ }
80
+ }
81
+
82
+ Application
83
+ .run(() => ({
84
+ id: 'com.example.app',
85
+ name: 'Example',
86
+ components: {
87
+ mailer: {class: Mailer, host: 'smtp.example.com'}
88
+ },
89
+ bootstrap: ['mailer']
90
+ }))
91
+ .onLaunched(async (app: Application): Promise<void> => {
92
+ const mailer: Mailer = await app.getObject<Mailer>('mailer')
93
+ await mailer.send('someone@example.com', 'Hello')
94
+ })
95
+ ```
96
+
97
+ [Decorator](Decorator.md), section 1.2, gives the order in which a value is chosen (the configured value, the
98
+ initializer, the default of the schema) and the checks made at launch.
99
+
100
+ ## 3. Sharing features between applications
101
+
102
+ To use a feature in several applications, put it in a package. The package exports a `Component` (or a `Provider`) and
103
+ a function that builds its declaration, the way the packages of the framework do (`buildDatabaseOptions` of
104
+ `lakutata/com/database`, `buildQueueOptions` of `lakutata/com/queue`). The application declares the component under a
105
+ name of its choice and passes the options to the function. The component's `@Configurable` schemas check the options
106
+ when the application launches, and the other objects inject the component by its name.
107
+
108
+ ```typescript
109
+ import {Application, Component, type ComponentOptions, DTO} from 'lakutata'
110
+ import {Configurable, Inject} from 'lakutata/decorator/di'
111
+
112
+ //In the package: the component, and the function that builds its declaration
113
+ export interface BillingOptions {
114
+ currency: string
115
+ }
116
+
117
+ export class Billing extends Component {
118
+ @Configurable(DTO.String().length(3).required())
119
+ public readonly currency: string
120
+
121
+ public charge(amount: number): string {
122
+ return `Charged ${amount} ${this.currency}`
123
+ }
124
+ }
125
+
126
+ export function buildBillingOptions(options: BillingOptions): ComponentOptions<BillingOptions> {
127
+ return {class: Billing, ...options}
128
+ }
129
+
130
+ //In the application
131
+ class Checkout extends Component {
132
+ @Inject('billing')
133
+ protected readonly billing: Billing
134
+ }
135
+
136
+ Application.run(() => ({
137
+ id: 'shop.app',
138
+ name: 'Shop',
139
+ components: {
140
+ billing: buildBillingOptions({currency: 'EUR'}),
141
+ checkout: {class: Checkout}
142
+ }
143
+ }))
144
+ ```