@alepha/react 0.14.2 → 0.14.3

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 (34) hide show
  1. package/dist/auth/index.js +2 -2
  2. package/dist/auth/index.js.map +1 -1
  3. package/dist/core/index.d.ts +4 -0
  4. package/dist/core/index.d.ts.map +1 -1
  5. package/dist/core/index.js +7 -4
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/head/index.d.ts +17 -17
  8. package/dist/head/index.d.ts.map +1 -1
  9. package/dist/head/index.js +2 -1
  10. package/dist/head/index.js.map +1 -1
  11. package/dist/router/index.d.ts.map +1 -1
  12. package/dist/router/index.js +2 -2
  13. package/dist/router/index.js.map +1 -1
  14. package/package.json +3 -3
  15. package/src/auth/__tests__/$auth.spec.ts +188 -0
  16. package/src/core/__tests__/Router.spec.tsx +169 -0
  17. package/src/core/hooks/useAction.browser.spec.tsx +569 -0
  18. package/src/core/hooks/useAction.ts +11 -0
  19. package/src/form/hooks/useForm.browser.spec.tsx +366 -0
  20. package/src/head/__tests__/expandSeo.spec.ts +203 -0
  21. package/src/head/__tests__/page-head.spec.ts +39 -0
  22. package/src/head/__tests__/seo-head.spec.ts +121 -0
  23. package/src/head/hooks/useHead.spec.tsx +288 -0
  24. package/src/head/index.ts +2 -1
  25. package/src/head/providers/BrowserHeadProvider.browser.spec.ts +271 -0
  26. package/src/head/providers/ServerHeadProvider.spec.ts +163 -0
  27. package/src/i18n/__tests__/integration.spec.tsx +239 -0
  28. package/src/i18n/components/Localize.spec.tsx +357 -0
  29. package/src/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  30. package/src/i18n/providers/I18nProvider.spec.ts +389 -0
  31. package/src/router/primitives/$page.browser.spec.tsx +702 -0
  32. package/src/router/primitives/$page.spec.tsx +702 -0
  33. package/src/router/providers/ReactServerProvider.spec.tsx +316 -0
  34. package/src/router/providers/ReactServerProvider.ts +4 -3
@@ -0,0 +1,389 @@
1
+ import { Alepha } from "alepha";
2
+ import { describe, test } from "vitest";
3
+ import { $dictionary } from "../primitives/$dictionary.ts";
4
+ import { AlephaReactI18n } from "../index.ts";
5
+ import { I18nProvider } from "./I18nProvider.ts";
6
+
7
+ describe("I18nProvider", () => {
8
+ test("should register dictionaries on initialization", async ({ expect }) => {
9
+ class App {
10
+ en = $dictionary({
11
+ lazy: async () => ({ default: { hello: "Hello" } }),
12
+ });
13
+
14
+ fr = $dictionary({
15
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
16
+ });
17
+ }
18
+
19
+ const alepha = Alepha.create().with(AlephaReactI18n);
20
+ const app = alepha.inject(App);
21
+ const i18n = alepha.inject(I18nProvider);
22
+
23
+ expect(i18n.registry).toHaveLength(2);
24
+ expect(i18n.registry[0].lang).toBe("en");
25
+ expect(i18n.registry[1].lang).toBe("fr");
26
+ });
27
+
28
+ test("should load all translations on server start", async ({ expect }) => {
29
+ class App {
30
+ en = $dictionary({
31
+ lazy: async () => ({ default: { hello: "Hello", world: "World" } }),
32
+ });
33
+
34
+ fr = $dictionary({
35
+ lazy: async () => ({ default: { hello: "Bonjour", world: "Monde" } }),
36
+ });
37
+ }
38
+
39
+ const alepha = Alepha.create().with(AlephaReactI18n);
40
+ const app = alepha.inject(App);
41
+ const i18n = alepha.inject(I18nProvider);
42
+
43
+ await alepha.start();
44
+
45
+ expect(i18n.registry[0].translations).toEqual({
46
+ hello: "Hello",
47
+ world: "World",
48
+ });
49
+ expect(i18n.registry[1].translations).toEqual({
50
+ hello: "Bonjour",
51
+ world: "Monde",
52
+ });
53
+ });
54
+
55
+ test("should default to fallback language", async ({ expect }) => {
56
+ class App {
57
+ en = $dictionary({
58
+ lazy: async () => ({ default: { hello: "Hello" } }),
59
+ });
60
+ }
61
+
62
+ const alepha = Alepha.create().with(AlephaReactI18n);
63
+ const app = alepha.inject(App);
64
+ const i18n = alepha.inject(I18nProvider);
65
+
66
+ expect(i18n.lang).toBe("en");
67
+ });
68
+
69
+ test("should translate keys in current language", async ({ expect }) => {
70
+ class App {
71
+ en = $dictionary({
72
+ lazy: async () => ({ default: { greeting: "Hello, World!" } }),
73
+ });
74
+
75
+ fr = $dictionary({
76
+ lazy: async () => ({ default: { greeting: "Bonjour, Monde!" } }),
77
+ });
78
+ }
79
+
80
+ const alepha = Alepha.create().with(AlephaReactI18n);
81
+ const app = alepha.inject(App);
82
+ const i18n = alepha.inject(I18nProvider);
83
+
84
+ await alepha.start();
85
+
86
+ expect(i18n.tr("greeting")).toBe("Hello, World!");
87
+
88
+ await i18n.setLang("fr");
89
+ expect(i18n.tr("greeting")).toBe("Bonjour, Monde!");
90
+ });
91
+
92
+ test("should fallback to fallback language when key missing", async ({
93
+ expect,
94
+ }) => {
95
+ class App {
96
+ en = $dictionary({
97
+ lazy: async () => ({
98
+ default: { hello: "Hello", world: "World" },
99
+ }),
100
+ });
101
+
102
+ fr = $dictionary({
103
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
104
+ });
105
+ }
106
+
107
+ const alepha = Alepha.create().with(AlephaReactI18n);
108
+ const app = alepha.inject(App);
109
+ const i18n = alepha.inject(I18nProvider);
110
+
111
+ await alepha.start();
112
+ await i18n.setLang("fr");
113
+
114
+ expect(i18n.tr("hello")).toBe("Bonjour");
115
+ expect(i18n.tr("world")).toBe("World"); // Falls back to English
116
+ });
117
+
118
+ test("should return key when translation not found", async ({ expect }) => {
119
+ class App {
120
+ en = $dictionary({
121
+ lazy: async () => ({ default: { hello: "Hello" } }),
122
+ });
123
+ }
124
+
125
+ const alepha = Alepha.create().with(AlephaReactI18n);
126
+ const app = alepha.inject(App);
127
+ const i18n = alepha.inject(I18nProvider);
128
+
129
+ await alepha.start();
130
+
131
+ expect(i18n.tr("missing.key")).toBe("missing.key");
132
+ });
133
+
134
+ test("should support default option when key missing", async ({ expect }) => {
135
+ class App {
136
+ en = $dictionary({
137
+ lazy: async () => ({ default: { hello: "Hello" } }),
138
+ });
139
+ }
140
+
141
+ const alepha = Alepha.create().with(AlephaReactI18n);
142
+ const app = alepha.inject(App);
143
+ const i18n = alepha.inject(I18nProvider);
144
+
145
+ await alepha.start();
146
+
147
+ expect(i18n.tr("missing", { default: "Default Text" })).toBe(
148
+ "Default Text",
149
+ );
150
+ });
151
+
152
+ test("should support variable interpolation", async ({ expect }) => {
153
+ class App {
154
+ en = $dictionary({
155
+ lazy: async () => ({
156
+ default: { greeting: "Hello, $1! You have $2 messages." },
157
+ }),
158
+ });
159
+ }
160
+
161
+ const alepha = Alepha.create().with(AlephaReactI18n);
162
+ const app = alepha.inject(App);
163
+ const i18n = alepha.inject(I18nProvider);
164
+
165
+ await alepha.start();
166
+
167
+ expect(i18n.tr("greeting", { args: ["John", "5"] })).toBe(
168
+ "Hello, John! You have 5 messages.",
169
+ );
170
+ });
171
+
172
+ test("should list all available languages", async ({ expect }) => {
173
+ class App {
174
+ en = $dictionary({
175
+ lazy: async () => ({ default: { hello: "Hello" } }),
176
+ });
177
+
178
+ fr = $dictionary({
179
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
180
+ });
181
+
182
+ de = $dictionary({
183
+ lazy: async () => ({ default: { hello: "Hallo" } }),
184
+ });
185
+ }
186
+
187
+ const alepha = Alepha.create().with(AlephaReactI18n);
188
+ const app = alepha.inject(App);
189
+ const i18n = alepha.inject(I18nProvider);
190
+
191
+ const languages = i18n.languages;
192
+ expect(languages).toContain("en");
193
+ expect(languages).toContain("fr");
194
+ expect(languages).toContain("de");
195
+ expect(languages).toHaveLength(3);
196
+ });
197
+
198
+ test("should support custom language codes", async ({ expect }) => {
199
+ class App {
200
+ enUS = $dictionary({
201
+ lang: "en-US",
202
+ lazy: async () => ({ default: { color: "color" } }),
203
+ });
204
+
205
+ enGB = $dictionary({
206
+ lang: "en-GB",
207
+ lazy: async () => ({ default: { color: "colour" } }),
208
+ });
209
+ }
210
+
211
+ const alepha = Alepha.create().with(AlephaReactI18n);
212
+ const app = alepha.inject(App);
213
+ const i18n = alepha.inject(I18nProvider);
214
+
215
+ await alepha.start();
216
+
217
+ expect(i18n.registry[0].lang).toBe("en-US");
218
+ expect(i18n.registry[1].lang).toBe("en-GB");
219
+ });
220
+
221
+ test("should support custom dictionary names", async ({ expect }) => {
222
+ class App {
223
+ english = $dictionary({
224
+ name: "custom-en",
225
+ lang: "en",
226
+ lazy: async () => ({ default: { hello: "Hello" } }),
227
+ });
228
+ }
229
+
230
+ const alepha = Alepha.create().with(AlephaReactI18n);
231
+ const app = alepha.inject(App);
232
+ const i18n = alepha.inject(I18nProvider);
233
+
234
+ expect(i18n.registry[0].name).toBe("custom-en");
235
+ });
236
+
237
+ test("should update state when language changes", async ({ expect }) => {
238
+ class App {
239
+ en = $dictionary({
240
+ lazy: async () => ({ default: { hello: "Hello" } }),
241
+ });
242
+
243
+ fr = $dictionary({
244
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
245
+ });
246
+ }
247
+
248
+ const alepha = Alepha.create().with(AlephaReactI18n);
249
+ const app = alepha.inject(App);
250
+ const i18n = alepha.inject(I18nProvider);
251
+
252
+ await alepha.start();
253
+
254
+ // State is set lazily, so we should set a language first
255
+ await i18n.setLang("en");
256
+ expect(alepha.store.get("alepha.react.i18n.lang")).toBe("en");
257
+
258
+ await i18n.setLang("fr");
259
+ expect(alepha.store.get("alepha.react.i18n.lang")).toBe("fr");
260
+ });
261
+
262
+ test("should have number formatter for current language", async ({
263
+ expect,
264
+ }) => {
265
+ class App {
266
+ en = $dictionary({
267
+ lazy: async () => ({ default: {} }),
268
+ });
269
+ }
270
+
271
+ const alepha = Alepha.create().with(AlephaReactI18n);
272
+ const app = alepha.inject(App);
273
+ const i18n = alepha.inject(I18nProvider);
274
+
275
+ await alepha.start();
276
+
277
+ expect(i18n.numberFormat).toBeDefined();
278
+ expect(i18n.numberFormat.format).toBeTypeOf("function");
279
+ });
280
+
281
+ test("should handle multiple translations with same key across dictionaries", async ({
282
+ expect,
283
+ }) => {
284
+ class App {
285
+ en = $dictionary({
286
+ lazy: async () => ({
287
+ default: {
288
+ app: "Application",
289
+ settings: "Settings",
290
+ },
291
+ }),
292
+ });
293
+
294
+ fr = $dictionary({
295
+ lazy: async () => ({
296
+ default: {
297
+ app: "Programme",
298
+ settings: "Paramètres",
299
+ },
300
+ }),
301
+ });
302
+
303
+ es = $dictionary({
304
+ lazy: async () => ({
305
+ default: {
306
+ app: "Aplicación",
307
+ settings: "Configuración",
308
+ },
309
+ }),
310
+ });
311
+ }
312
+
313
+ const alepha = Alepha.create().with(AlephaReactI18n);
314
+ const app = alepha.inject(App);
315
+ const i18n = alepha.inject(I18nProvider);
316
+
317
+ await alepha.start();
318
+
319
+ expect(i18n.tr("app")).toBe("Application");
320
+ expect(i18n.tr("settings")).toBe("Settings");
321
+
322
+ await i18n.setLang("fr");
323
+ expect(i18n.tr("app")).toBe("Programme");
324
+ expect(i18n.tr("settings")).toBe("Paramètres");
325
+
326
+ await i18n.setLang("es");
327
+ expect(i18n.tr("app")).toBe("Aplicación");
328
+ expect(i18n.tr("settings")).toBe("Configuración");
329
+ });
330
+
331
+ test("should handle empty dictionaries", async ({ expect }) => {
332
+ class App {
333
+ en = $dictionary({
334
+ lazy: async () => ({ default: {} }),
335
+ });
336
+ }
337
+
338
+ const alepha = Alepha.create().with(AlephaReactI18n);
339
+ const app = alepha.inject(App);
340
+ const i18n = alepha.inject(I18nProvider);
341
+
342
+ await alepha.start();
343
+
344
+ expect(i18n.tr("anything")).toBe("anything");
345
+ });
346
+
347
+ test("should handle complex interpolation patterns", async ({ expect }) => {
348
+ class App {
349
+ en = $dictionary({
350
+ lazy: async () => ({
351
+ default: {
352
+ complex: "User $1 has $2 followers and follows $3 people",
353
+ },
354
+ }),
355
+ });
356
+ }
357
+
358
+ const alepha = Alepha.create().with(AlephaReactI18n);
359
+ const app = alepha.inject(App);
360
+ const i18n = alepha.inject(I18nProvider);
361
+
362
+ await alepha.start();
363
+
364
+ expect(i18n.tr("complex", { args: ["Alice", "1000", "500"] })).toBe(
365
+ "User Alice has 1000 followers and follows 500 people",
366
+ );
367
+ });
368
+
369
+ test("should handle partial interpolation args", async ({ expect }) => {
370
+ class App {
371
+ en = $dictionary({
372
+ lazy: async () => ({
373
+ default: { message: "Hello $1, you have $2 messages and $3 alerts" },
374
+ }),
375
+ });
376
+ }
377
+
378
+ const alepha = Alepha.create().with(AlephaReactI18n);
379
+ const app = alepha.inject(App);
380
+ const i18n = alepha.inject(I18nProvider);
381
+
382
+ await alepha.start();
383
+
384
+ // Provide fewer args than placeholders
385
+ expect(i18n.tr("message", { args: ["Bob"] })).toBe(
386
+ "Hello Bob, you have $2 messages and $3 alerts",
387
+ );
388
+ });
389
+ });