@relax.js/core 1.0.3 → 1.0.4

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 (234) hide show
  1. package/README.md +194 -188
  2. package/dist/DependencyInjection.d.ts +42 -24
  3. package/dist/di/index.js +1 -1
  4. package/dist/di/index.js.map +3 -3
  5. package/dist/di/index.mjs +1 -1
  6. package/dist/di/index.mjs.map +3 -3
  7. package/dist/errors.d.ts +20 -0
  8. package/dist/forms/FormValidator.d.ts +1 -20
  9. package/dist/forms/ValidationRules.d.ts +2 -0
  10. package/dist/forms/index.js +1 -1
  11. package/dist/forms/index.js.map +4 -4
  12. package/dist/forms/index.mjs +1 -1
  13. package/dist/forms/index.mjs.map +4 -4
  14. package/dist/html/TableRenderer.d.ts +1 -0
  15. package/dist/html/index.js.map +2 -2
  16. package/dist/html/index.mjs.map +2 -2
  17. package/dist/html/template.d.ts +4 -0
  18. package/dist/http/http.d.ts +1 -0
  19. package/dist/http/index.js.map +2 -2
  20. package/dist/http/index.mjs.map +2 -2
  21. package/dist/index.js +3 -3
  22. package/dist/index.js.map +3 -3
  23. package/dist/index.mjs +3 -3
  24. package/dist/index.mjs.map +3 -3
  25. package/dist/routing/index.js +3 -3
  26. package/dist/routing/index.js.map +3 -3
  27. package/dist/routing/index.mjs +3 -3
  28. package/dist/routing/index.mjs.map +3 -3
  29. package/dist/routing/routeTargetRegistry.d.ts +1 -0
  30. package/dist/routing/types.d.ts +2 -1
  31. package/dist/templates/NodeTemplate.d.ts +2 -0
  32. package/dist/utils/index.js +1 -1
  33. package/dist/utils/index.js.map +2 -2
  34. package/dist/utils/index.mjs +1 -1
  35. package/dist/utils/index.mjs.map +2 -2
  36. package/docs/Architecture.md +333 -333
  37. package/docs/DependencyInjection.md +277 -237
  38. package/docs/Errors.md +87 -87
  39. package/docs/GettingStarted.md +231 -231
  40. package/docs/Pipes.md +5 -5
  41. package/docs/Translations.md +167 -312
  42. package/docs/WhyRelaxjs.md +336 -336
  43. package/docs/api/.nojekyll +1 -0
  44. package/docs/api/assets/hierarchy.js +1 -0
  45. package/docs/api/assets/highlight.css +120 -0
  46. package/docs/api/assets/icons.js +18 -0
  47. package/docs/api/assets/icons.svg +1 -0
  48. package/docs/api/assets/main.js +60 -0
  49. package/docs/api/assets/navigation.js +1 -0
  50. package/docs/api/assets/search.js +1 -0
  51. package/docs/api/assets/style.css +1633 -0
  52. package/docs/api/classes/http.WebSocketClient.html +26 -0
  53. package/docs/api/classes/i18n.LocaleChangeEvent.html +66 -0
  54. package/docs/api/classes/index.Blueprint.html +3 -0
  55. package/docs/api/classes/index.BoundNode.html +3 -0
  56. package/docs/api/classes/index.DigitsValidation.html +10 -0
  57. package/docs/api/classes/index.FormValidator.html +32 -0
  58. package/docs/api/classes/index.HttpError.html +13 -0
  59. package/docs/api/classes/index.LinkedList.html +26 -0
  60. package/docs/api/classes/index.NavigateRouteEvent.html +76 -0
  61. package/docs/api/classes/index.Node.html +15 -0
  62. package/docs/api/classes/index.PageSelectedEvent.html +61 -0
  63. package/docs/api/classes/index.Pager.html +4 -0
  64. package/docs/api/classes/index.RangeValidation.html +15 -0
  65. package/docs/api/classes/index.RelaxError.html +17 -0
  66. package/docs/api/classes/index.RequiredValidation.html +10 -0
  67. package/docs/api/classes/index.RouteError.html +11 -0
  68. package/docs/api/classes/index.RouteGuardError.html +12 -0
  69. package/docs/api/classes/index.RouteLink.html +779 -0
  70. package/docs/api/classes/index.RouteTarget.html +788 -0
  71. package/docs/api/classes/index.SSEClient.html +13 -0
  72. package/docs/api/classes/index.SSEDataEvent.html +63 -0
  73. package/docs/api/classes/index.ServiceCollection.html +28 -0
  74. package/docs/api/classes/index.ServiceContainer.html +24 -0
  75. package/docs/api/classes/index.SortChangeEvent.html +61 -0
  76. package/docs/api/classes/index.TableRenderer.html +5 -0
  77. package/docs/api/classes/index.TableSorter.html +4 -0
  78. package/docs/api/enums/index.GuardResult.html +9 -0
  79. package/docs/api/functions/elements.formError.html +6 -0
  80. package/docs/api/functions/elements.selectOne.html +6 -0
  81. package/docs/api/functions/i18n.getCurrentLocale.html +3 -0
  82. package/docs/api/functions/i18n.loadNamespace.html +7 -0
  83. package/docs/api/functions/i18n.loadNamespaces.html +6 -0
  84. package/docs/api/functions/i18n.onMissingTranslation.html +7 -0
  85. package/docs/api/functions/i18n.setLocale.html +7 -0
  86. package/docs/api/functions/i18n.setMessageFormatter.html +7 -0
  87. package/docs/api/functions/i18n.t.html +9 -0
  88. package/docs/api/functions/index.BooleanConverter.html +6 -0
  89. package/docs/api/functions/index.ContainerService.html +13 -0
  90. package/docs/api/functions/index.DateConverter.html +11 -0
  91. package/docs/api/functions/index.Inject.html +16 -0
  92. package/docs/api/functions/index.NumberConverter.html +5 -0
  93. package/docs/api/functions/index.RegisterValidator.html +7 -0
  94. package/docs/api/functions/index.applyPipes.html +17 -0
  95. package/docs/api/functions/index.asyncHandler.html +11 -0
  96. package/docs/api/functions/index.capitalizePipe.html +4 -0
  97. package/docs/api/functions/index.clearPendingNavigations.html +1 -0
  98. package/docs/api/functions/index.compileTemplate.html +26 -0
  99. package/docs/api/functions/index.configure.html +5 -0
  100. package/docs/api/functions/index.createBluePrint.html +1 -0
  101. package/docs/api/functions/index.createConverterFromDataType.html +4 -0
  102. package/docs/api/functions/index.createConverterFromInputType.html +5 -0
  103. package/docs/api/functions/index.createPipeRegistry.html +12 -0
  104. package/docs/api/functions/index.currencyPipe.html +9 -0
  105. package/docs/api/functions/index.datePipe.html +9 -0
  106. package/docs/api/functions/index.daysAgoPipe.html +8 -0
  107. package/docs/api/functions/index.defaultPipe.html +5 -0
  108. package/docs/api/functions/index.defineRoutes.html +8 -0
  109. package/docs/api/functions/index.del.html +8 -0
  110. package/docs/api/functions/index.findRouteByName.html +5 -0
  111. package/docs/api/functions/index.findRouteByUrl.html +4 -0
  112. package/docs/api/functions/index.firstPipe.html +4 -0
  113. package/docs/api/functions/index.generateSequentialId.html +21 -0
  114. package/docs/api/functions/index.get.html +9 -0
  115. package/docs/api/functions/index.getDataConverter.html +11 -0
  116. package/docs/api/functions/index.getParentComponent.html +18 -0
  117. package/docs/api/functions/index.getValidator.html +4 -0
  118. package/docs/api/functions/index.html.html +19 -0
  119. package/docs/api/functions/index.joinPipe.html +5 -0
  120. package/docs/api/functions/index.keysPipe.html +4 -0
  121. package/docs/api/functions/index.lastPipe.html +4 -0
  122. package/docs/api/functions/index.lowercasePipe.html +4 -0
  123. package/docs/api/functions/index.mapFormToClass.html +17 -0
  124. package/docs/api/functions/index.matchRoute.html +5 -0
  125. package/docs/api/functions/index.navigate.html +8 -0
  126. package/docs/api/functions/index.onError.html +8 -0
  127. package/docs/api/functions/index.piecesPipe.html +8 -0
  128. package/docs/api/functions/index.post.html +9 -0
  129. package/docs/api/functions/index.printRoutes.html +2 -0
  130. package/docs/api/functions/index.put.html +9 -0
  131. package/docs/api/functions/index.readData.html +17 -0
  132. package/docs/api/functions/index.registerRouteTarget.html +9 -0
  133. package/docs/api/functions/index.reportError.html +10 -0
  134. package/docs/api/functions/index.request.html +8 -0
  135. package/docs/api/functions/index.resolveValue.html +18 -0
  136. package/docs/api/functions/index.setFetch.html +6 -0
  137. package/docs/api/functions/index.setFormData.html +17 -0
  138. package/docs/api/functions/index.shortenPipe.html +5 -0
  139. package/docs/api/functions/index.startRouting.html +6 -0
  140. package/docs/api/functions/index.ternaryPipe.html +6 -0
  141. package/docs/api/functions/index.trimPipe.html +4 -0
  142. package/docs/api/functions/index.unregisterRouteTarget.html +3 -0
  143. package/docs/api/functions/index.uppercasePipe.html +4 -0
  144. package/docs/api/hierarchy.html +1 -0
  145. package/docs/api/index.html +323 -0
  146. package/docs/api/interfaces/http.SimpleDataEvent.html +3 -0
  147. package/docs/api/interfaces/http.WebSocketAbstraction.html +9 -0
  148. package/docs/api/interfaces/http.WebSocketCodec.html +4 -0
  149. package/docs/api/interfaces/http.WebSocketOptions.html +20 -0
  150. package/docs/api/interfaces/index.CompiledTemplate.html +10 -0
  151. package/docs/api/interfaces/index.DataLoader.html +19 -0
  152. package/docs/api/interfaces/index.EngineConfig.html +11 -0
  153. package/docs/api/interfaces/index.ErrorContext.html +4 -0
  154. package/docs/api/interfaces/index.FormReaderOptions.html +8 -0
  155. package/docs/api/interfaces/index.HttpOptions.html +16 -0
  156. package/docs/api/interfaces/index.HttpResponse.html +17 -0
  157. package/docs/api/interfaces/index.LoadRoute.html +7 -0
  158. package/docs/api/interfaces/index.NavigateOptions.html +7 -0
  159. package/docs/api/interfaces/index.PipeRegistry.html +12 -0
  160. package/docs/api/interfaces/index.RegistrationOptions.html +22 -0
  161. package/docs/api/interfaces/index.RenderTemplate.html +7 -0
  162. package/docs/api/interfaces/index.RequestOptions.html +11 -0
  163. package/docs/api/interfaces/index.Routable.html +10 -0
  164. package/docs/api/interfaces/index.Route.html +13 -0
  165. package/docs/api/interfaces/index.RouteGuard.html +2 -0
  166. package/docs/api/interfaces/index.RouteValue.html +6 -0
  167. package/docs/api/interfaces/index.SSEOptions.html +24 -0
  168. package/docs/api/interfaces/index.ValidationContext.html +8 -0
  169. package/docs/api/interfaces/index.ValidatorOptions.html +14 -0
  170. package/docs/api/media/Architecture.md +333 -0
  171. package/docs/api/media/DependencyInjection.md +277 -0
  172. package/docs/api/media/GettingStarted.md +231 -0
  173. package/docs/api/media/HttpClient.md +459 -0
  174. package/docs/api/media/Pipes.md +211 -0
  175. package/docs/api/media/Routing.md +332 -0
  176. package/docs/api/media/WhyRelaxjs.md +336 -0
  177. package/docs/api/media/forms.md +99 -0
  178. package/docs/api/media/html.md +175 -0
  179. package/docs/api/media/i18n.md +354 -0
  180. package/docs/api/media/utilities.md +143 -0
  181. package/docs/api/media/validation.md +351 -0
  182. package/docs/api/modules/collections_Index.html +1 -0
  183. package/docs/api/modules/di.html +1 -0
  184. package/docs/api/modules/elements.html +1 -0
  185. package/docs/api/modules/forms.html +1 -0
  186. package/docs/api/modules/html.html +1 -0
  187. package/docs/api/modules/http.html +1 -0
  188. package/docs/api/modules/i18n.html +1 -0
  189. package/docs/api/modules/index.html +1 -0
  190. package/docs/api/modules/routing.html +1 -0
  191. package/docs/api/modules/utils.html +1 -0
  192. package/docs/api/modules.html +1 -0
  193. package/docs/api/types/http.WebSocketFactory.html +2 -0
  194. package/docs/api/types/i18n.MessageFormatter.html +3 -0
  195. package/docs/api/types/i18n.MissingTranslationHandler.html +1 -0
  196. package/docs/api/types/index.Constructor.html +7 -0
  197. package/docs/api/types/index.ConverterFunc.html +2 -0
  198. package/docs/api/types/index.DataType.html +2 -0
  199. package/docs/api/types/index.InputType.html +2 -0
  200. package/docs/api/types/index.PipeFunction.html +6 -0
  201. package/docs/api/types/index.RouteData.html +1 -0
  202. package/docs/api/types/index.RouteMatchResult.html +9 -0
  203. package/docs/api/types/index.RouteParamType.html +1 -0
  204. package/docs/api/types/index.RouteSegmentType.html +2 -0
  205. package/docs/api/types/index.SSEEventFactory.html +5 -0
  206. package/docs/api/types/index.ServiceScope.html +10 -0
  207. package/docs/api/types/index.SortColumn.html +3 -0
  208. package/docs/api/variables/i18n.formatICU.html +3 -0
  209. package/docs/api/variables/index.container.html +6 -0
  210. package/docs/api/variables/index.defaultPipes.html +6 -0
  211. package/docs/api/variables/index.internalRoutes.html +1 -0
  212. package/docs/api/variables/index.serviceCollection.html +6 -0
  213. package/docs/api.json +93171 -0
  214. package/docs/elements/dom.md +102 -102
  215. package/docs/forms/creating-form-components.md +924 -924
  216. package/docs/forms/form-api.md +94 -94
  217. package/docs/forms/forms.md +99 -99
  218. package/docs/forms/patterns.md +311 -311
  219. package/docs/forms/reading-writing.md +365 -365
  220. package/docs/forms/validation.md +351 -351
  221. package/docs/html/TableRenderer.md +291 -291
  222. package/docs/html/html.md +175 -175
  223. package/docs/html/index.md +54 -54
  224. package/docs/html/template.md +422 -422
  225. package/docs/http/HttpClient.md +459 -459
  226. package/docs/http/ServerSentEvents.md +184 -184
  227. package/docs/http/index.md +109 -109
  228. package/docs/i18n/i18n.md +49 -4
  229. package/docs/i18n/intl-standard.md +178 -178
  230. package/docs/routing/RouteLink.md +98 -98
  231. package/docs/routing/Routing.md +332 -332
  232. package/docs/routing/layouts.md +207 -207
  233. package/docs/utilities.md +143 -143
  234. package/package.json +4 -3
@@ -0,0 +1,354 @@
1
+ # Relaxjs i18n
2
+
3
+ Namespace-based translations with ICU message format, lazy loading, and locale change events. Built on top of the [modern Intl standard](intl-standard.md).
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import { setLocale, loadNamespaces, t } from '@relax.js/core/i18n';
9
+
10
+ await setLocale('sv');
11
+ await loadNamespaces(['r-pipes', 'r-validation']);
12
+
13
+ t('greeting', { name: 'Anna' }); // "Hej, Anna!"
14
+ t('items', { count: 3 }); // "3 saker"
15
+ t('r-pipes:daysAgo', { count: 2 }); // "2 dagar sedan"
16
+ ```
17
+
18
+ ## Folder Structure
19
+
20
+ Translations live in `src/i18n/locales/{locale}/{namespace}.json`:
21
+
22
+ ```
23
+ src/i18n/locales/
24
+ ├── en/
25
+ │ ├── r-common.json
26
+ │ ├── r-pipes.json
27
+ │ └── r-validation.json
28
+ └── sv/
29
+ ├── r-common.json
30
+ ├── r-pipes.json
31
+ └── r-validation.json
32
+ ```
33
+
34
+ ## Translation Keys
35
+
36
+ Use `t('namespace:key')` to translate. Omit the namespace to use `r-common`:
37
+
38
+ ```typescript
39
+ t('greeting', { name: 'Anna' }) // r-common:greeting
40
+ t('r-common:greeting', { name: 'Anna' }) // explicit namespace
41
+ t('r-pipes:today') // r-pipes namespace
42
+ ```
43
+
44
+ If a key is not found, `t()` returns the key itself.
45
+
46
+ ## Translation Files
47
+
48
+ ### Simple Interpolation
49
+
50
+ ```json
51
+ {
52
+ "greeting": "Hello, {name}!",
53
+ "welcome": "Welcome to {appName}"
54
+ }
55
+ ```
56
+
57
+ ```typescript
58
+ t('greeting', { name: 'John' }); // "Hello, John!"
59
+ ```
60
+
61
+ ### Pluralization (ICU)
62
+
63
+ Uses `Intl.PluralRules` for locale-aware category selection. The `#` token inserts the count.
64
+
65
+ ```json
66
+ {
67
+ "items": "{count, plural, one {# item} other {# items}}",
68
+ "daysAgo": "{count, plural, one {# day ago} other {# days ago}}"
69
+ }
70
+ ```
71
+
72
+ ```typescript
73
+ t('items', { count: 1 }); // "1 item"
74
+ t('items', { count: 5 }); // "5 items"
75
+ ```
76
+
77
+ ### Exact Matches (`=N`)
78
+
79
+ Exact values take priority over plural categories:
80
+
81
+ ```json
82
+ {
83
+ "pieces": "{count, plural, =0 {none} one {one} other {# pcs}}"
84
+ }
85
+ ```
86
+
87
+ ```typescript
88
+ t('pieces', { count: 0 }); // "none" (exact =0 match)
89
+ t('pieces', { count: 1 }); // "one" (plural category)
90
+ t('pieces', { count: 5 }); // "5 pcs" (plural category)
91
+ ```
92
+
93
+ ### Select (ICU)
94
+
95
+ Chooses a branch based on an exact string match. Always include an `other` fallback.
96
+
97
+ ```json
98
+ {
99
+ "status": "{role, select, admin {Full access} editor {Can edit} other {Read only}}"
100
+ }
101
+ ```
102
+
103
+ ```typescript
104
+ t('status', { role: 'admin' }); // "Full access"
105
+ t('status', { role: 'editor' }); // "Can edit"
106
+ t('status', { role: 'viewer' }); // "Read only" (other)
107
+ ```
108
+
109
+ ### Number and Date Formatting
110
+
111
+ ICU format supports locale-aware number and date formatting:
112
+
113
+ ```json
114
+ {
115
+ "price": "Price: {amount, number, currency}",
116
+ "date": "Date: {date, date, medium}"
117
+ }
118
+ ```
119
+
120
+ ## Example Translation Files
121
+
122
+ **`src/i18n/locales/en/r-common.json`**:
123
+
124
+ ```json
125
+ {
126
+ "welcome": "Welcome to our site!",
127
+ "greeting": "Hello, {name}!",
128
+ "items": "{count, plural, =0 {No items} one {# item} other {# items}}",
129
+ "goodbye": "Goodbye!"
130
+ }
131
+ ```
132
+
133
+ **`src/i18n/locales/en/errors.json`**:
134
+
135
+ ```json
136
+ {
137
+ "notFound": "Page not found",
138
+ "unauthorized": "You are not authorized to view this page",
139
+ "serverError": "An unexpected error occurred"
140
+ }
141
+ ```
142
+
143
+ **`src/i18n/locales/sv/r-common.json`**:
144
+
145
+ ```json
146
+ {
147
+ "welcome": "Välkommen till vår sida!",
148
+ "greeting": "Hej, {name}!",
149
+ "items": "{count, plural, =0 {Inga föremål} one {# föremål} other {# föremål}}",
150
+ "goodbye": "Hejdå!"
151
+ }
152
+ ```
153
+
154
+ ## Built-in Namespaces
155
+
156
+ ### r-common
157
+
158
+ General application strings. Loaded automatically with `setLocale()`.
159
+
160
+ | Key | Message (EN) |
161
+ |-----|-------------|
162
+ | `greeting` | `Hello, {name}!` |
163
+ | `items` | `{count, plural, one {# item} other {# items}}` |
164
+
165
+ ### r-pipes
166
+
167
+ Translations for [locale-aware pipes](../Pipes.md). Load before using `daysAgo` or `pieces` pipes:
168
+
169
+ ```typescript
170
+ await loadNamespace('r-pipes');
171
+ ```
172
+
173
+ | Key | Message (EN) |
174
+ |-----|-------------|
175
+ | `today` | `today` |
176
+ | `yesterday` | `yesterday` |
177
+ | `daysAgo` | `{count, plural, one {# day ago} other {# days ago}}` |
178
+ | `pieces` | `{count, plural, =0 {none} one {one} other {# pcs}}` |
179
+
180
+ ### r-validation
181
+
182
+ Translations for [form validation](../forms/validation.md) error messages:
183
+
184
+ ```typescript
185
+ await loadNamespace('r-validation');
186
+ ```
187
+
188
+ | Key | Message (EN) |
189
+ |-----|-------------|
190
+ | `required` | `This field is required.` |
191
+ | `range` | `Number must be between {min} and {max}, was {actual}.` |
192
+ | `digits` | `Please enter only digits.` |
193
+
194
+ ## Locale Switching
195
+
196
+ ```typescript
197
+ await setLocale('sv'); // Clears translations, loads sv/r-common.json, dispatches event
198
+ await setLocale('en'); // Clears translations, loads en/r-common.json, dispatches event
199
+ ```
200
+
201
+ Locale codes are normalized: `en-US` becomes `en`, `sv-SE` becomes `sv`.
202
+
203
+ ## LocaleChangeEvent
204
+
205
+ `setLocale()` dispatches a `LocaleChangeEvent` on `document` after loading translations. Components can listen for it to re-render:
206
+
207
+ ```typescript
208
+ document.addEventListener('localechange', (e) => {
209
+ console.log(`Switched to ${e.locale}`);
210
+ this.render();
211
+ });
212
+ ```
213
+
214
+ The event has a typed `locale` property (the normalized locale code) and is registered on `DocumentEventMap` for type-safe listeners.
215
+
216
+ ## Missing Translation Handler
217
+
218
+ Register a callback for when `t()` encounters a missing key. Useful for development tooling, logging, or collecting untranslated strings:
219
+
220
+ ```typescript
221
+ import { onMissingTranslation } from '@relax.js/core/i18n';
222
+
223
+ onMissingTranslation((key, namespace, locale) => {
224
+ console.warn(`Missing: ${namespace}:${key} [${locale}]`);
225
+ });
226
+ ```
227
+
228
+ Pass `null` to remove the handler:
229
+
230
+ ```typescript
231
+ onMissingTranslation(null);
232
+ ```
233
+
234
+ ## Fallback Behavior
235
+
236
+ 1. If a namespace file doesn't exist for the current locale, loads from `en/`
237
+ 2. If the key doesn't exist in the namespace, calls the missing handler (if set) and returns the key
238
+ 3. If the namespace doesn't exist at all, logs a warning and returns the key
239
+
240
+ ## Custom Message Formatter
241
+
242
+ The built-in formatter supports interpolation, pluralization, and select. For advanced ICU features (nested arguments, date/time formatting in messages), use an external library:
243
+
244
+ ```typescript
245
+ import { setMessageFormatter } from '@relax.js/core/i18n';
246
+ import { IntlMessageFormat } from 'intl-messageformat';
247
+
248
+ setMessageFormatter((message, values, locale) => {
249
+ const fmt = new IntlMessageFormat(message, locale);
250
+ return fmt.format(values) as string;
251
+ });
252
+ ```
253
+
254
+ ## Integration with Pipes
255
+
256
+ Several [pipes](../Pipes.md) use the i18n system for localized output:
257
+
258
+ - `currency`: uses `getCurrentLocale()` for number formatting
259
+ - `date`: uses `getCurrentLocale()` for date formatting
260
+ - `daysAgo`: uses `t('r-pipes:...')` for translated text
261
+ - `pieces`: uses `t('r-pipes:...')` for translated text
262
+
263
+ ```typescript
264
+ await setLocale('sv');
265
+ await loadNamespace('r-pipes');
266
+
267
+ // Now pipes output Swedish
268
+ // {{createdAt | daysAgo}} → "idag", "igår", "3 dagar sedan"
269
+ ```
270
+
271
+ ## Adding a New Locale
272
+
273
+ 1. Create `src/i18n/locales/{locale}/r-common.json`
274
+ 2. Create `src/i18n/locales/{locale}/r-pipes.json`
275
+ 3. Create `src/i18n/locales/{locale}/r-validation.json`
276
+ 4. Add translations for all keys
277
+
278
+ Example for German:
279
+
280
+ ```json
281
+ // src/i18n/locales/de/r-pipes.json
282
+ {
283
+ "today": "heute",
284
+ "yesterday": "gestern",
285
+ "daysAgo": "{count, plural, one {vor # Tag} other {vor # Tagen}}",
286
+ "pieces": "{count, plural, =0 {keine} one {eins} other {# Stück}}"
287
+ }
288
+ ```
289
+
290
+ ## API Reference
291
+
292
+ ### Functions
293
+
294
+ | Function | Description |
295
+ |----------|-------------|
296
+ | `setLocale(locale)` | Set locale, clear translations, load `r-common`, dispatch event |
297
+ | `loadNamespace(ns)` | Load a single translation namespace |
298
+ | `loadNamespaces(ns[])` | Load multiple namespaces in parallel |
299
+ | `t(key, values?)` | Translate a key with optional interpolation |
300
+ | `getCurrentLocale()` | Get the current normalized locale code |
301
+ | `onMissingTranslation(handler)` | Register/remove missing key handler |
302
+ | `setMessageFormatter(fn)` | Replace the default ICU formatter |
303
+
304
+ ### Types
305
+
306
+ ```typescript
307
+ type MessageFormatter = (
308
+ message: string,
309
+ values?: Record<string, any>,
310
+ locale?: string,
311
+ ) => string;
312
+
313
+ type MissingTranslationHandler = (
314
+ key: string,
315
+ namespace: string,
316
+ locale: string,
317
+ ) => void;
318
+ ```
319
+
320
+ ### Events
321
+
322
+ | Event | Target | Property | Description |
323
+ |-------|--------|----------|-------------|
324
+ | `localechange` | `document` | `locale: string` | Fired after `setLocale()` completes |
325
+
326
+ ## Example: Full Setup
327
+
328
+ ```typescript
329
+ import {
330
+ setLocale, loadNamespaces, t,
331
+ getCurrentLocale, onMissingTranslation,
332
+ } from '@relax.js/core/i18n';
333
+
334
+ async function initI18n() {
335
+ // Dev-time missing key logging
336
+ onMissingTranslation((key, ns, locale) => {
337
+ console.warn(`Missing: ${ns}:${key} [${locale}]`);
338
+ });
339
+
340
+ // Detect browser locale or use default
341
+ const browserLocale = navigator.language || 'en';
342
+ await setLocale(browserLocale);
343
+
344
+ // Load namespaces needed by the app
345
+ await loadNamespaces(['r-pipes', 'r-validation']);
346
+
347
+ console.log(`Locale: ${getCurrentLocale()}`);
348
+ }
349
+
350
+ // React to locale changes
351
+ document.addEventListener('localechange', (e) => {
352
+ console.log(`Locale switched to ${e.locale}`);
353
+ });
354
+ ```
@@ -0,0 +1,143 @@
1
+ # Utilities
2
+
3
+ Small helper functions and data structures exported from `@relax.js/core/utils`.
4
+
5
+ ## generateSequentialId
6
+
7
+ Generates compact, time-ordered unique identifiers. IDs sort lexicographically in creation order, making them useful for ordered collections and databases.
8
+
9
+ ```typescript
10
+ import { generateSequentialId } from '@relax.js/core/utils';
11
+
12
+ const id = generateSequentialId(1);
13
+ // Returns a Base36 string like 'k2j8m3n5p'
14
+ ```
15
+
16
+ ### Parameters
17
+
18
+ | Parameter | Type | Description |
19
+ |-----------|------|-------------|
20
+ | `baseId` | `number` | Unique identifier for the client/process (0 to 1,048,575) |
21
+
22
+ Use different `baseId` values for different servers or processes to avoid collisions:
23
+
24
+ ```typescript
25
+ const SERVER_ID = parseInt(process.env.SERVER_ID || '0');
26
+ const orderId = generateSequentialId(SERVER_ID);
27
+ ```
28
+
29
+ ### Structure
30
+
31
+ Each ID encodes 58 bits of data in Base36:
32
+
33
+ | Bits | Field | Range |
34
+ |------|-------|-------|
35
+ | 30 | Timestamp (seconds since 2025-01-01) | Until ~2059 |
36
+ | 8 | Per-second counter | 256 IDs/second |
37
+ | 20 | Client identifier | ~1M unique sources |
38
+
39
+ ### Time-Sortable
40
+
41
+ IDs generated later sort after earlier IDs:
42
+
43
+ ```typescript
44
+ const id1 = generateSequentialId(0);
45
+ // ... wait ...
46
+ const id2 = generateSequentialId(0);
47
+ console.log(id1 < id2); // true
48
+ ```
49
+
50
+ ### Errors
51
+
52
+ - Throws if `baseId` is out of range (0 to 1,048,575)
53
+ - Throws if more than 256 IDs are generated in a single second
54
+ - Throws if timestamp exceeds range (after ~2059)
55
+
56
+ ## resolveValue
57
+
58
+ Safely navigates nested object properties using a path array. Returns `undefined` if any segment is null or missing (no exceptions thrown).
59
+
60
+ ```typescript
61
+ import { resolveValue } from '@relax.js/core/utils';
62
+
63
+ const user = { address: { city: 'Stockholm' } };
64
+
65
+ resolveValue(['address', 'city'], user);
66
+ // Returns: 'Stockholm'
67
+
68
+ resolveValue(['address', 'zip'], user);
69
+ // Returns: undefined
70
+
71
+ // Safe with null values
72
+ const data = { user: null };
73
+ resolveValue(['user', 'name'], data);
74
+ // Returns: undefined (no error)
75
+ ```
76
+
77
+ ### Use with Dot-Notation Paths
78
+
79
+ ```typescript
80
+ const path = 'user.profile.avatar'.split('.');
81
+ const avatar = resolveValue(path, context);
82
+ ```
83
+
84
+ ## LinkedList
85
+
86
+ A doubly-linked list with O(1) insertion and removal at both ends.
87
+
88
+ ```typescript
89
+ import { LinkedList } from '@relax.js/core/utils';
90
+
91
+ const list = new LinkedList<string>();
92
+
93
+ list.addFirst('A');
94
+ list.addLast('B');
95
+ list.addLast('C');
96
+
97
+ console.log(list.length); // 3
98
+ console.log(list.firstValue); // 'A'
99
+ console.log(list.lastValue); // 'C'
100
+
101
+ list.removeFirst(); // Returns 'A'
102
+ list.removeLast(); // Returns 'C'
103
+ console.log(list.length); // 1
104
+ ```
105
+
106
+ ### Node Access
107
+
108
+ Access the internal `Node` wrappers to traverse or remove specific nodes:
109
+
110
+ ```typescript
111
+ let node = list.first;
112
+ while (node) {
113
+ console.log(node.value);
114
+ node = node.next;
115
+ }
116
+
117
+ // Remove a specific node
118
+ const node = list.first;
119
+ node.remove(); // Removes from list and updates length
120
+ ```
121
+
122
+ ### API
123
+
124
+ | Method/Property | Description |
125
+ |-----------------|-------------|
126
+ | `addFirst(value)` | Insert at the beginning |
127
+ | `addLast(value)` | Insert at the end |
128
+ | `removeFirst()` | Remove and return the first value |
129
+ | `removeLast()` | Remove and return the last value |
130
+ | `length` | Number of nodes |
131
+ | `first` | First `Node` (or `undefined`) |
132
+ | `last` | Last `Node` (or `undefined`) |
133
+ | `firstValue` | Value of the first node |
134
+ | `lastValue` | Value of the last node |
135
+
136
+ ### Node Properties
137
+
138
+ | Property | Description |
139
+ |----------|-------------|
140
+ | `value` | The stored value |
141
+ | `next` | Next node (or `null`) |
142
+ | `prev` | Previous node (or `null`) |
143
+ | `remove()` | Remove this node from the list |