@relax.js/core 1.0.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 (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/DataLoader.d.ts +51 -0
  4. package/dist/DependencyInjection.d.ts +271 -0
  5. package/dist/DependencyInjectionOld.d.ts +35 -0
  6. package/dist/Metadata.d.ts +8 -0
  7. package/dist/SequentialId.d.ts +47 -0
  8. package/dist/_alt/src/MustardEngine.d.ts +30 -0
  9. package/dist/_alt/src/MustardParser.d.ts +63 -0
  10. package/dist/_alt/src/MustardParser2.d.ts +35 -0
  11. package/dist/_alt/src/pipes.d.ts +93 -0
  12. package/dist/_alt/src/template.d.ts +166 -0
  13. package/dist/_alt/src/tools.d.ts +4 -0
  14. package/dist/_alt/tests/pipes.tests.d.ts +1 -0
  15. package/dist/_alt/tests/template.tests.d.ts +1 -0
  16. package/dist/_alt/vitest.config.d.ts +2 -0
  17. package/dist/collections/Index.d.ts +1 -0
  18. package/dist/collections/LinkedList.d.ts +75 -0
  19. package/dist/collections/Pager.d.ts +15 -0
  20. package/dist/collections/index.js +2 -0
  21. package/dist/collections/index.js.map +7 -0
  22. package/dist/collections/index.mjs +2 -0
  23. package/dist/collections/index.mjs.map +7 -0
  24. package/dist/components/Table.d.ts +13 -0
  25. package/dist/components/index.d.ts +4 -0
  26. package/dist/components/index.js +128 -0
  27. package/dist/components/index.js.map +7 -0
  28. package/dist/components/index.mjs +128 -0
  29. package/dist/components/index.mjs.map +7 -0
  30. package/dist/components/lists/Table.d.ts +59 -0
  31. package/dist/components/lists/TreeView.d.ts +67 -0
  32. package/dist/components/lists/index.d.ts +2 -0
  33. package/dist/components/loader.d.ts +60 -0
  34. package/dist/components/menus/MenuItem.d.ts +30 -0
  35. package/dist/components/menus/TopMenu.d.ts +16 -0
  36. package/dist/components/menus/index.d.ts +2 -0
  37. package/dist/components/panels/tabs.d.ts +15 -0
  38. package/dist/di/index.d.ts +1 -0
  39. package/dist/di/index.js +2 -0
  40. package/dist/di/index.js.map +7 -0
  41. package/dist/di/index.mjs +2 -0
  42. package/dist/di/index.mjs.map +7 -0
  43. package/dist/elements/CopyAttributes.d.ts +2 -0
  44. package/dist/elements/dom.d.ts +18 -0
  45. package/dist/elements/index.d.ts +2 -0
  46. package/dist/elements/index.js +2 -0
  47. package/dist/elements/index.js.map +7 -0
  48. package/dist/elements/index.mjs +2 -0
  49. package/dist/elements/index.mjs.map +7 -0
  50. package/dist/errors.d.ts +71 -0
  51. package/dist/forms/FormReader.d.ts +182 -0
  52. package/dist/forms/FormValidator.d.ts +114 -0
  53. package/dist/forms/ValidationRules.d.ts +103 -0
  54. package/dist/forms/index.d.ts +4 -0
  55. package/dist/forms/index.js +2 -0
  56. package/dist/forms/index.js.map +7 -0
  57. package/dist/forms/index.mjs +2 -0
  58. package/dist/forms/index.mjs.map +7 -0
  59. package/dist/forms/setFormData.d.ts +49 -0
  60. package/dist/getParentComponent.d.ts +43 -0
  61. package/dist/html/TableRenderer.d.ts +44 -0
  62. package/dist/html/TreeBinder.d.ts +9 -0
  63. package/dist/html/html.d.ts +55 -0
  64. package/dist/html/index.d.ts +5 -0
  65. package/dist/html/index.js +2 -0
  66. package/dist/html/index.js.map +7 -0
  67. package/dist/html/index.mjs +2 -0
  68. package/dist/html/index.mjs.map +7 -0
  69. package/dist/html/template.d.ts +167 -0
  70. package/dist/http/ServerSentEvents.d.ts +116 -0
  71. package/dist/http/SimpleWebSocket.d.ts +153 -0
  72. package/dist/http/http.d.ts +177 -0
  73. package/dist/http/index.d.ts +3 -0
  74. package/dist/http/index.js +2 -0
  75. package/dist/http/index.js.map +7 -0
  76. package/dist/http/index.mjs +2 -0
  77. package/dist/http/index.mjs.map +7 -0
  78. package/dist/i18n/i18n.d.ts +105 -0
  79. package/dist/i18n/icu.d.ts +64 -0
  80. package/dist/i18n/index.d.ts +2 -0
  81. package/dist/i18n/index.js +2 -0
  82. package/dist/i18n/index.js.map +7 -0
  83. package/dist/i18n/index.mjs +2 -0
  84. package/dist/i18n/index.mjs.map +7 -0
  85. package/dist/index.d.ts +16 -0
  86. package/dist/index.js +5 -0
  87. package/dist/index.js.map +7 -0
  88. package/dist/index.mjs +5 -0
  89. package/dist/index.mjs.map +7 -0
  90. package/dist/lib/DataLoader.d.ts +51 -0
  91. package/dist/lib/DependencyInjection.d.ts +271 -0
  92. package/dist/lib/InvokeParent.d.ts +10 -0
  93. package/dist/lib/Pipes.d.ts +236 -0
  94. package/dist/lib/SequentialId.d.ts +47 -0
  95. package/dist/lib/collections/Index.d.ts +1 -0
  96. package/dist/lib/collections/LinkedList.d.ts +75 -0
  97. package/dist/lib/collections/Pager.d.ts +15 -0
  98. package/dist/lib/collections/TableRenderer.d.ts +44 -0
  99. package/dist/lib/di/index.d.ts +1 -0
  100. package/dist/lib/elements/CopyAttributes.d.ts +2 -0
  101. package/dist/lib/elements/dom.d.ts +18 -0
  102. package/dist/lib/elements/index.d.ts +2 -0
  103. package/dist/lib/errors.d.ts +71 -0
  104. package/dist/lib/forms/FormReader.d.ts +182 -0
  105. package/dist/lib/forms/FormValidator.d.ts +114 -0
  106. package/dist/lib/forms/ValidationRules.d.ts +103 -0
  107. package/dist/lib/forms/index.d.ts +4 -0
  108. package/dist/lib/forms/setFormData.d.ts +49 -0
  109. package/dist/lib/getParentComponent.d.ts +43 -0
  110. package/dist/lib/html/TableRenderer.d.ts +44 -0
  111. package/dist/lib/html/TreeBinder.d.ts +9 -0
  112. package/dist/lib/html/html.d.ts +55 -0
  113. package/dist/lib/html/html2.d.ts +55 -0
  114. package/dist/lib/html/index.d.ts +5 -0
  115. package/dist/lib/html/m.d.ts +167 -0
  116. package/dist/lib/html/m2.d.ts +8 -0
  117. package/dist/lib/html/m3.d.ts +0 -0
  118. package/dist/lib/html/template.d.ts +167 -0
  119. package/dist/lib/http/HttpClient.d.ts +153 -0
  120. package/dist/lib/http/ServerSentEvents.d.ts +116 -0
  121. package/dist/lib/http/SimpleWebSocket.d.ts +153 -0
  122. package/dist/lib/http/http.d.ts +177 -0
  123. package/dist/lib/http/index.d.ts +3 -0
  124. package/dist/lib/i18n/i18n.d.ts +105 -0
  125. package/dist/lib/i18n/icu.d.ts +64 -0
  126. package/dist/lib/i18n/index.d.ts +2 -0
  127. package/dist/lib/index.d.ts +16 -0
  128. package/dist/lib/routing/NavigateRouteEvent.d.ts +52 -0
  129. package/dist/lib/routing/RouteLink.d.ts +7 -0
  130. package/dist/lib/routing/Routing.d.ts +270 -0
  131. package/dist/lib/routing/RoutingTarget.d.ts +22 -0
  132. package/dist/lib/routing/index.d.ts +7 -0
  133. package/dist/lib/routing/navigation.d.ts +70 -0
  134. package/dist/lib/routing/routeMatching.d.ts +21 -0
  135. package/dist/lib/routing/routeTargetRegistry.d.ts +23 -0
  136. package/dist/lib/routing/types.d.ts +130 -0
  137. package/dist/lib/templates/NodeTemplate.d.ts +38 -0
  138. package/dist/lib/templates/accessorParser.d.ts +87 -0
  139. package/dist/lib/templates/parseTemplate.d.ts +6 -0
  140. package/dist/lib/templates/tokenizer.d.ts +76 -0
  141. package/dist/lib/tools.d.ts +30 -0
  142. package/dist/lib/utils/index.d.ts +4 -0
  143. package/dist/pipes.d.ts +236 -0
  144. package/dist/routing/NavigateRouteEvent.d.ts +52 -0
  145. package/dist/routing/RouteLink.d.ts +7 -0
  146. package/dist/routing/RoutingTarget.d.ts +22 -0
  147. package/dist/routing/index.d.ts +7 -0
  148. package/dist/routing/index.js +5 -0
  149. package/dist/routing/index.js.map +7 -0
  150. package/dist/routing/index.mjs +5 -0
  151. package/dist/routing/index.mjs.map +7 -0
  152. package/dist/routing/navigation.d.ts +70 -0
  153. package/dist/routing/routeMatching.d.ts +21 -0
  154. package/dist/routing/routeTargetRegistry.d.ts +23 -0
  155. package/dist/routing/types.d.ts +130 -0
  156. package/dist/templates/NodeTemplate.d.ts +38 -0
  157. package/dist/templates/accessorParser.d.ts +87 -0
  158. package/dist/templates/parseTemplate.d.ts +6 -0
  159. package/dist/templates/tokenizer.d.ts +76 -0
  160. package/dist/tools.d.ts +30 -0
  161. package/dist/utils/index.d.ts +4 -0
  162. package/dist/utils/index.js +2 -0
  163. package/dist/utils/index.js.map +7 -0
  164. package/dist/utils/index.mjs +2 -0
  165. package/dist/utils/index.mjs.map +7 -0
  166. package/docs/Architecture.md +333 -0
  167. package/docs/DependencyInjection.md +237 -0
  168. package/docs/Errors.md +87 -0
  169. package/docs/GettingStarted.md +231 -0
  170. package/docs/Pipes.md +211 -0
  171. package/docs/Translations.md +312 -0
  172. package/docs/WhyRelaxjs.md +336 -0
  173. package/docs/elements/dom.md +102 -0
  174. package/docs/forms/creating-form-components.md +924 -0
  175. package/docs/forms/form-api.md +94 -0
  176. package/docs/forms/forms.md +99 -0
  177. package/docs/forms/patterns.md +311 -0
  178. package/docs/forms/reading-writing.md +365 -0
  179. package/docs/forms/validation.md +351 -0
  180. package/docs/html/TableRenderer.md +292 -0
  181. package/docs/html/html.md +175 -0
  182. package/docs/html/index.md +54 -0
  183. package/docs/html/template.md +422 -0
  184. package/docs/http/HttpClient.md +459 -0
  185. package/docs/http/ServerSentEvents.md +184 -0
  186. package/docs/http/index.md +109 -0
  187. package/docs/i18n/i18n.md +309 -0
  188. package/docs/i18n/intl-standard.md +178 -0
  189. package/docs/routing/RouteLink.md +98 -0
  190. package/docs/routing/Routing.md +332 -0
  191. package/docs/routing/RoutingTarget.md +136 -0
  192. package/docs/routing/layouts.md +207 -0
  193. package/docs/utilities.md +143 -0
  194. package/package.json +93 -0
@@ -0,0 +1,109 @@
1
+ # HTTP, WebSocket & SSE
2
+
3
+ This module provides HTTP, WebSocket, and Server-Sent Events clients for network communication.
4
+
5
+ ## Available Features
6
+
7
+ | Feature | Description | Use Case |
8
+ |---------|-------------|----------|
9
+ | [HTTP Client](HttpClient.md#http-client) | Type-safe HTTP functions | REST API calls with JWT handling |
10
+ | [WebSocketClient](HttpClient.md#websocket-client) | WebSocket client with auto-reconnect | Real-time bidirectional messaging |
11
+ | [SSEClient](ServerSentEvents.md) | Server-Sent Events as DOM events | Server push notifications |
12
+
13
+ ## Quick Start
14
+
15
+ ### HTTP Requests
16
+
17
+ ```typescript
18
+ import { configure, get, post } from 'relaxjs/http';
19
+
20
+ configure({ baseUrl: '/api/v1' });
21
+
22
+ // GET with query parameters
23
+ const response = await get('/users', { status: 'active' });
24
+ if (response.success) {
25
+ const users = response.as<User[]>();
26
+ }
27
+
28
+ // POST with JSON body
29
+ const result = await post('/users', JSON.stringify({ name: 'John' }));
30
+ ```
31
+
32
+ ### WebSocket Messaging
33
+
34
+ ```typescript
35
+ import { WebSocketClient } from 'relaxjs/http';
36
+
37
+ const ws = new WebSocketClient<Message>('wss://api.example.com');
38
+ ws.connect();
39
+
40
+ // Check connection state
41
+ console.log(ws.connected); // true when connected
42
+
43
+ // Send (queued if disconnected)
44
+ ws.send({ type: 'chat', text: 'Hello' });
45
+
46
+ // Receive (rejects if connection closes)
47
+ try {
48
+ const message = await ws.receive();
49
+ } catch (e) {
50
+ console.log('Connection closed');
51
+ }
52
+
53
+ // Disconnect when done
54
+ ws.disconnect();
55
+ ```
56
+
57
+ ### Server-Sent Events
58
+
59
+ ```typescript
60
+ import { SSEClient } from 'relaxjs/http';
61
+
62
+ const sse = new SSEClient('/api/events', {
63
+ eventTypes: ['user-updated', 'notification']
64
+ });
65
+ sse.connect();
66
+
67
+ // Events dispatch to document by default
68
+ document.addEventListener('notification', (e: SSEMessageEvent) => {
69
+ showNotification(e.data);
70
+ });
71
+
72
+ // Disconnect when done
73
+ sse.disconnect();
74
+ ```
75
+
76
+ ## Key Features
77
+
78
+ ### HTTP Client
79
+
80
+ - Standalone functions (`get`, `post`, `put`, `del`, `request`)
81
+ - Module-wide configuration via `configure()`
82
+ - Automatic base URL prefixing
83
+ - JWT token from localStorage (configurable)
84
+ - Query string building
85
+ - Type-safe response casting
86
+ - Replaceable fetch for testing via `setFetch()`
87
+
88
+ ### WebSocketClient
89
+
90
+ - Auto-reconnect with exponential backoff
91
+ - Message queuing when disconnected
92
+ - Custom message codecs
93
+ - Type-safe generic messages
94
+ - Connection state tracking
95
+ - Graceful disconnect support
96
+
97
+ ### SSEClient
98
+
99
+ - Dispatches SSE events as DOM events
100
+ - Configurable target element or document
101
+ - Auto-reconnect with exponential backoff
102
+ - Automatic JSON parsing
103
+
104
+ ## Choosing the Right Tool
105
+
106
+ - **REST API calls?** → Use `get`, `post`, `put`, `del`
107
+ - **Bidirectional real-time?** → Use `WebSocketClient`
108
+ - **Server push only?** → Use `SSEClient`
109
+ - **Both?** → Use `HttpClient` for requests, `WebSocketClient` or `SSEClient` for subscriptions
@@ -0,0 +1,309 @@
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 'relaxjs/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
+ ## Built-in Namespaces
110
+
111
+ ### r-common
112
+
113
+ General application strings. Loaded automatically with `setLocale()`.
114
+
115
+ | Key | Message (EN) |
116
+ |-----|-------------|
117
+ | `greeting` | `Hello, {name}!` |
118
+ | `items` | `{count, plural, one {# item} other {# items}}` |
119
+
120
+ ### r-pipes
121
+
122
+ Translations for [locale-aware pipes](../Pipes.md). Load before using `daysAgo` or `pieces` pipes:
123
+
124
+ ```typescript
125
+ await loadNamespace('r-pipes');
126
+ ```
127
+
128
+ | Key | Message (EN) |
129
+ |-----|-------------|
130
+ | `today` | `today` |
131
+ | `yesterday` | `yesterday` |
132
+ | `daysAgo` | `{count, plural, one {# day ago} other {# days ago}}` |
133
+ | `pieces` | `{count, plural, =0 {none} one {one} other {# pcs}}` |
134
+
135
+ ### r-validation
136
+
137
+ Translations for [form validation](../forms/validation.md) error messages:
138
+
139
+ ```typescript
140
+ await loadNamespace('r-validation');
141
+ ```
142
+
143
+ | Key | Message (EN) |
144
+ |-----|-------------|
145
+ | `required` | `This field is required.` |
146
+ | `range` | `Number must be between {min} and {max}, was {actual}.` |
147
+ | `digits` | `Please enter only digits.` |
148
+
149
+ ## Locale Switching
150
+
151
+ ```typescript
152
+ await setLocale('sv'); // Clears translations, loads sv/r-common.json, dispatches event
153
+ await setLocale('en'); // Clears translations, loads en/r-common.json, dispatches event
154
+ ```
155
+
156
+ Locale codes are normalized: `en-US` becomes `en`, `sv-SE` becomes `sv`.
157
+
158
+ ## LocaleChangeEvent
159
+
160
+ `setLocale()` dispatches a `LocaleChangeEvent` on `document` after loading translations. Components can listen for it to re-render:
161
+
162
+ ```typescript
163
+ document.addEventListener('localechange', (e) => {
164
+ console.log(`Switched to ${e.locale}`);
165
+ this.render();
166
+ });
167
+ ```
168
+
169
+ The event has a typed `locale` property (the normalized locale code) and is registered on `DocumentEventMap` for type-safe listeners.
170
+
171
+ ## Missing Translation Handler
172
+
173
+ Register a callback for when `t()` encounters a missing key. Useful for development tooling, logging, or collecting untranslated strings:
174
+
175
+ ```typescript
176
+ import { onMissingTranslation } from 'relaxjs/i18n';
177
+
178
+ onMissingTranslation((key, namespace, locale) => {
179
+ console.warn(`Missing: ${namespace}:${key} [${locale}]`);
180
+ });
181
+ ```
182
+
183
+ Pass `null` to remove the handler:
184
+
185
+ ```typescript
186
+ onMissingTranslation(null);
187
+ ```
188
+
189
+ ## Fallback Behavior
190
+
191
+ 1. If a namespace file doesn't exist for the current locale, loads from `en/`
192
+ 2. If the key doesn't exist in the namespace, calls the missing handler (if set) and returns the key
193
+ 3. If the namespace doesn't exist at all, logs a warning and returns the key
194
+
195
+ ## Custom Message Formatter
196
+
197
+ The built-in formatter supports interpolation, pluralization, and select. For advanced ICU features (nested arguments, date/time formatting in messages), use an external library:
198
+
199
+ ```typescript
200
+ import { setMessageFormatter } from 'relaxjs/i18n';
201
+ import { IntlMessageFormat } from 'intl-messageformat';
202
+
203
+ setMessageFormatter((message, values, locale) => {
204
+ const fmt = new IntlMessageFormat(message, locale);
205
+ return fmt.format(values) as string;
206
+ });
207
+ ```
208
+
209
+ ## Integration with Pipes
210
+
211
+ Several [pipes](../Pipes.md) use the i18n system for localized output:
212
+
213
+ - `currency`: uses `getCurrentLocale()` for number formatting
214
+ - `date`: uses `getCurrentLocale()` for date formatting
215
+ - `daysAgo`: uses `t('r-pipes:...')` for translated text
216
+ - `pieces`: uses `t('r-pipes:...')` for translated text
217
+
218
+ ```typescript
219
+ await setLocale('sv');
220
+ await loadNamespace('r-pipes');
221
+
222
+ // Now pipes output Swedish
223
+ // {{createdAt | daysAgo}} → "idag", "igår", "3 dagar sedan"
224
+ ```
225
+
226
+ ## Adding a New Locale
227
+
228
+ 1. Create `src/i18n/locales/{locale}/r-common.json`
229
+ 2. Create `src/i18n/locales/{locale}/r-pipes.json`
230
+ 3. Create `src/i18n/locales/{locale}/r-validation.json`
231
+ 4. Add translations for all keys
232
+
233
+ Example for German:
234
+
235
+ ```json
236
+ // src/i18n/locales/de/r-pipes.json
237
+ {
238
+ "today": "heute",
239
+ "yesterday": "gestern",
240
+ "daysAgo": "{count, plural, one {vor # Tag} other {vor # Tagen}}",
241
+ "pieces": "{count, plural, =0 {keine} one {eins} other {# Stück}}"
242
+ }
243
+ ```
244
+
245
+ ## API Reference
246
+
247
+ ### Functions
248
+
249
+ | Function | Description |
250
+ |----------|-------------|
251
+ | `setLocale(locale)` | Set locale, clear translations, load `r-common`, dispatch event |
252
+ | `loadNamespace(ns)` | Load a single translation namespace |
253
+ | `loadNamespaces(ns[])` | Load multiple namespaces in parallel |
254
+ | `t(key, values?)` | Translate a key with optional interpolation |
255
+ | `getCurrentLocale()` | Get the current normalized locale code |
256
+ | `onMissingTranslation(handler)` | Register/remove missing key handler |
257
+ | `setMessageFormatter(fn)` | Replace the default ICU formatter |
258
+
259
+ ### Types
260
+
261
+ ```typescript
262
+ type MessageFormatter = (
263
+ message: string,
264
+ values?: Record<string, any>,
265
+ locale?: string,
266
+ ) => string;
267
+
268
+ type MissingTranslationHandler = (
269
+ key: string,
270
+ namespace: string,
271
+ locale: string,
272
+ ) => void;
273
+ ```
274
+
275
+ ### Events
276
+
277
+ | Event | Target | Property | Description |
278
+ |-------|--------|----------|-------------|
279
+ | `localechange` | `document` | `locale: string` | Fired after `setLocale()` completes |
280
+
281
+ ## Example: Full Setup
282
+
283
+ ```typescript
284
+ import {
285
+ setLocale, loadNamespaces, t,
286
+ getCurrentLocale, onMissingTranslation,
287
+ } from 'relaxjs/i18n';
288
+
289
+ async function initI18n() {
290
+ // Dev-time missing key logging
291
+ onMissingTranslation((key, ns, locale) => {
292
+ console.warn(`Missing: ${ns}:${key} [${locale}]`);
293
+ });
294
+
295
+ // Detect browser locale or use default
296
+ const browserLocale = navigator.language || 'en';
297
+ await setLocale(browserLocale);
298
+
299
+ // Load namespaces needed by the app
300
+ await loadNamespaces(['r-pipes', 'r-validation']);
301
+
302
+ console.log(`Locale: ${getCurrentLocale()}`);
303
+ }
304
+
305
+ // React to locale changes
306
+ document.addEventListener('localechange', (e) => {
307
+ console.log(`Locale switched to ${e.locale}`);
308
+ });
309
+ ```
@@ -0,0 +1,178 @@
1
+ # The Modern Intl Standard
2
+
3
+ The browser's built-in [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) object provides locale-aware formatting for numbers, dates, strings, and more. It is supported in all modern browsers and Node.js, so no polyfills or libraries are needed.
4
+
5
+ Relaxjs uses `Intl` internally (e.g. `Intl.PluralRules` for ICU pluralization) and exposes the current locale via `getCurrentLocale()` so pipes and components can pass it directly to `Intl` APIs.
6
+
7
+ ## Intl.PluralRules
8
+
9
+ Determines the plural category (`zero`, `one`, `two`, `few`, `many`, `other`) for a number in a given locale. Different languages have different rules. English has two categories (`one` / `other`), Arabic has six.
10
+
11
+ ```typescript
12
+ const en = new Intl.PluralRules('en');
13
+ en.select(1); // 'one'
14
+ en.select(0); // 'other'
15
+ en.select(5); // 'other'
16
+
17
+ const sv = new Intl.PluralRules('sv');
18
+ sv.select(1); // 'one' → en
19
+ sv.select(0); // 'other' → flera
20
+ sv.select(5); // 'other' → flera
21
+ ```
22
+
23
+ The returned strings (`one`, `other`, `few`, etc.) are category identifiers, not localized text. The locale determines *which numbers* map to *which categories*. English and Swedish both use `one`/`other`, while languages like Arabic use all six categories (`zero`, `one`, `two`, `few`, `many`, `other`).
24
+
25
+ This is the engine behind ICU `{count, plural, ...}` messages. The Relaxjs ICU formatter delegates to `Intl.PluralRules` for category selection.
26
+
27
+ [MDN: Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules)
28
+
29
+ ## Intl.NumberFormat
30
+
31
+ Formats numbers according to locale conventions: thousands separators, decimal marks, currency, percentages, units, and compact notation.
32
+
33
+ ```typescript
34
+ new Intl.NumberFormat('en').format(1234.5); // '1,234.5'
35
+ new Intl.NumberFormat('sv').format(1234.5); // '1 234,5'
36
+
37
+ new Intl.NumberFormat('en', {
38
+ style: 'currency', currency: 'USD'
39
+ }).format(42); // '$42.00'
40
+
41
+ new Intl.NumberFormat('sv', {
42
+ style: 'currency', currency: 'SEK'
43
+ }).format(42); // '42,00 kr'
44
+
45
+ new Intl.NumberFormat('en', {
46
+ notation: 'compact'
47
+ }).format(1_500_000); // '1.5M'
48
+ ```
49
+
50
+ The Relaxjs `currency` pipe uses `Intl.NumberFormat` with `getCurrentLocale()`.
51
+
52
+ [MDN: Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
53
+
54
+ ## Intl.DateTimeFormat
55
+
56
+ Formats dates and times according to locale conventions and configurable options.
57
+
58
+ ```typescript
59
+ const date = new Date('2025-06-15T14:30:00');
60
+
61
+ new Intl.DateTimeFormat('en').format(date); // '6/15/2025'
62
+ new Intl.DateTimeFormat('sv').format(date); // '2025-06-15'
63
+
64
+ new Intl.DateTimeFormat('en', {
65
+ dateStyle: 'long', timeStyle: 'short'
66
+ }).format(date); // 'June 15, 2025 at 2:30 PM'
67
+
68
+ new Intl.DateTimeFormat('sv', {
69
+ weekday: 'long', month: 'long', day: 'numeric'
70
+ }).format(date); // 'söndag 15 juni'
71
+ ```
72
+
73
+ The Relaxjs `date` pipe uses `Intl.DateTimeFormat` with `getCurrentLocale()`.
74
+
75
+ [MDN: Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat)
76
+
77
+ ## Intl.RelativeTimeFormat
78
+
79
+ Formats relative time descriptions ("3 days ago", "in 2 hours") with locale-aware phrasing.
80
+
81
+ ```typescript
82
+ const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
83
+ rtf.format(-1, 'day'); // 'yesterday'
84
+ rtf.format(0, 'day'); // 'today'
85
+ rtf.format(3, 'day'); // 'in 3 days'
86
+
87
+ const svRtf = new Intl.RelativeTimeFormat('sv', { numeric: 'auto' });
88
+ svRtf.format(-1, 'day'); // 'igår'
89
+ svRtf.format(-3, 'day'); // 'för 3 dagar sedan'
90
+ ```
91
+
92
+ [MDN: Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat)
93
+
94
+ ## Intl.Collator
95
+
96
+ Provides locale-sensitive string comparison for sorting. Handles accents, case, and locale-specific sort orders (e.g. Swedish å/ä/ö sort after z).
97
+
98
+ ```typescript
99
+ const col = new Intl.Collator('sv');
100
+ ['ö', 'a', 'å', 'ä'].sort(col.compare); // ['a', 'å', 'ä', 'ö']
101
+
102
+ const enCol = new Intl.Collator('en', { sensitivity: 'base' });
103
+ enCol.compare('café', 'cafe'); // 0 (equal, ignoring accent)
104
+ ```
105
+
106
+ [MDN: Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator)
107
+
108
+ ## Intl.Segmenter
109
+
110
+ Splits text into meaningful segments (graphemes, words, or sentences) while respecting locale rules. Essential for languages without spaces between words (Chinese, Japanese, Thai).
111
+
112
+ ```typescript
113
+ const seg = new Intl.Segmenter('en', { granularity: 'word' });
114
+ const words = [...seg.segment('Hello world!')].filter(s => s.isWordLike);
115
+ // [{segment: 'Hello'}, {segment: 'world'}]
116
+ ```
117
+
118
+ [MDN: Intl.Segmenter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter)
119
+
120
+ ## Intl.ListFormat
121
+
122
+ Formats lists with locale-appropriate conjunctions and punctuation.
123
+
124
+ ```typescript
125
+ new Intl.ListFormat('en', { type: 'conjunction' })
126
+ .format(['Alice', 'Bob', 'Charlie']); // 'Alice, Bob, and Charlie'
127
+
128
+ new Intl.ListFormat('sv', { type: 'conjunction' })
129
+ .format(['Alice', 'Bob', 'Charlie']); // 'Alice, Bob och Charlie'
130
+ ```
131
+
132
+ [MDN: Intl.ListFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat)
133
+
134
+ ## Intl.DisplayNames
135
+
136
+ Translates language, region, currency, and script codes into human-readable names.
137
+
138
+ ```typescript
139
+ new Intl.DisplayNames('en', { type: 'language' }).of('sv'); // 'Swedish'
140
+ new Intl.DisplayNames('sv', { type: 'language' }).of('en'); // 'engelska'
141
+ new Intl.DisplayNames('en', { type: 'currency' }).of('SEK'); // 'Swedish Krona'
142
+ ```
143
+
144
+ [MDN: Intl.DisplayNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames)
145
+
146
+ ## ICU Message Format
147
+
148
+ ICU (International Components for Unicode) defines a message syntax used across platforms. The three core patterns:
149
+
150
+ ### Simple Interpolation
151
+
152
+ ```
153
+ Hello, {name}!
154
+ ```
155
+
156
+ ### Plural
157
+
158
+ Selects a branch based on `Intl.PluralRules`. The `#` token inserts the numeric value. Exact matches (`=0`, `=1`) take priority over category matches.
159
+
160
+ ```
161
+ {count, plural, =0 {No items} one {# item} other {# items}}
162
+ ```
163
+
164
+ ### Select
165
+
166
+ Selects a branch based on an exact string match. Always include an `other` fallback.
167
+
168
+ ```
169
+ {gender, select, male {He} female {She} other {They}} liked your post.
170
+ ```
171
+
172
+ [ICU User Guide: Formatting Messages](https://unicode-org.github.io/icu/userguide/format_parse/messages/)
173
+
174
+ ## Further Reading
175
+
176
+ - [MDN: Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), full API reference
177
+ - [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/), the message syntax standard
178
+ - [Can I Use: Intl](https://caniuse.com/?search=Intl), browser support tables
@@ -0,0 +1,98 @@
1
+ # RouteLink
2
+
3
+ A clickable element that triggers client-side navigation to a named route.
4
+
5
+ ## Usage
6
+
7
+ ### Basic Navigation
8
+
9
+ ```html
10
+ <r-link name="home">Home</r-link>
11
+ <r-link name="about">About Us</r-link>
12
+ ```
13
+
14
+ ### With Route Parameters
15
+
16
+ ```html
17
+ <r-link name="user" param-id="123">View User</r-link>
18
+ <r-link name="product" param-id="456" param-tab="details">Product Details</r-link>
19
+ ```
20
+
21
+ ### With Target
22
+
23
+ ```html
24
+ <r-link name="settings" target="sidebar">Settings</r-link>
25
+ <r-link name="preview" target="modal">Preview</r-link>
26
+ ```
27
+
28
+ ### Complex Parameters via JSON
29
+
30
+ ```html
31
+ <r-link name="search" params='{"query":"typescript","page":1}'>
32
+ Search Results
33
+ </r-link>
34
+ ```
35
+
36
+ ## Attributes
37
+
38
+ | Attribute | Type | Description |
39
+ |-----------|------|-------------|
40
+ | `name` | `string` | Route name to navigate to (required) |
41
+ | `target` | `string` | Target `r-route-target` name |
42
+ | `params` | `string` | JSON object with additional route data |
43
+ | `param-*` | `string` | Individual route parameters (e.g., `param-id="123"`) |
44
+
45
+ ## Behavior
46
+
47
+ 1. Renders as an inline element with `cursor: pointer`
48
+ 2. Sets `role="link"` and `tabindex="0"` for accessibility
49
+ 3. On click, calls `navigate()` with the route name and collected parameters
50
+ 4. Prevents default click behavior
51
+
52
+ ## Combining Parameters
53
+
54
+ Both `param-*` attributes and `params` JSON can be used together:
55
+
56
+ ```html
57
+ <r-link name="product"
58
+ param-id="789"
59
+ params='{"source":"email","campaign":"summer"}'>
60
+ View Product
61
+ </r-link>
62
+ ```
63
+
64
+ All parameters are merged into a single flat object:
65
+ ```typescript
66
+ { id: '789', source: 'email', campaign: 'summer' }
67
+ ```
68
+
69
+ ## Styling
70
+
71
+ Style as any inline element:
72
+
73
+ ```css
74
+ r-link {
75
+ color: var(--accent-primary);
76
+ text-decoration: underline;
77
+ }
78
+
79
+ r-link:hover {
80
+ color: var(--accent-hover);
81
+ }
82
+
83
+ r-link:focus {
84
+ outline: 2px solid var(--accent-primary);
85
+ outline-offset: 2px;
86
+ }
87
+ ```
88
+
89
+ ## Comparison with Native Links
90
+
91
+ | Feature | `<a href>` | `<r-link>` |
92
+ |---------|------------|---------|
93
+ | Navigation | Full page reload | Client-side routing |
94
+ | Parameters | Query string | Route params + data |
95
+ | Target | Window/frame | Route target |
96
+ | SEO | Crawlable | Requires SSR |
97
+
98
+ Use `<r-link>` for SPA navigation within the app. Use native `<a>` for external links or when SEO is critical.