@lokascript/i18n 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 (96) hide show
  1. package/README.md +286 -0
  2. package/dist/browser.cjs +7669 -0
  3. package/dist/browser.cjs.map +1 -0
  4. package/dist/browser.d.cts +50 -0
  5. package/dist/browser.d.ts +50 -0
  6. package/dist/browser.js +7592 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/hyperfixi-i18n.min.js +2 -0
  9. package/dist/hyperfixi-i18n.min.js.map +1 -0
  10. package/dist/hyperfixi-i18n.mjs +8558 -0
  11. package/dist/hyperfixi-i18n.mjs.map +1 -0
  12. package/dist/index.cjs +14205 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +947 -0
  15. package/dist/index.d.ts +947 -0
  16. package/dist/index.js +14095 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/transformer-Ckask-yw.d.cts +1041 -0
  19. package/dist/transformer-Ckask-yw.d.ts +1041 -0
  20. package/package.json +84 -0
  21. package/src/browser.ts +122 -0
  22. package/src/compatibility/browser-tests/grammar-demo.spec.ts +169 -0
  23. package/src/constants.ts +366 -0
  24. package/src/dictionaries/ar.ts +233 -0
  25. package/src/dictionaries/bn.ts +156 -0
  26. package/src/dictionaries/de.ts +233 -0
  27. package/src/dictionaries/derive.ts +515 -0
  28. package/src/dictionaries/en.ts +237 -0
  29. package/src/dictionaries/es.ts +233 -0
  30. package/src/dictionaries/fr.ts +233 -0
  31. package/src/dictionaries/hi.ts +270 -0
  32. package/src/dictionaries/id.ts +233 -0
  33. package/src/dictionaries/index.ts +238 -0
  34. package/src/dictionaries/it.ts +233 -0
  35. package/src/dictionaries/ja.ts +233 -0
  36. package/src/dictionaries/ko.ts +233 -0
  37. package/src/dictionaries/ms.ts +276 -0
  38. package/src/dictionaries/pl.ts +239 -0
  39. package/src/dictionaries/pt.ts +237 -0
  40. package/src/dictionaries/qu.ts +233 -0
  41. package/src/dictionaries/ru.ts +270 -0
  42. package/src/dictionaries/sw.ts +233 -0
  43. package/src/dictionaries/th.ts +156 -0
  44. package/src/dictionaries/tl.ts +276 -0
  45. package/src/dictionaries/tr.ts +233 -0
  46. package/src/dictionaries/uk.ts +270 -0
  47. package/src/dictionaries/vi.ts +210 -0
  48. package/src/dictionaries/zh.ts +233 -0
  49. package/src/enhanced-i18n.test.ts +454 -0
  50. package/src/enhanced-i18n.ts +713 -0
  51. package/src/examples/new-languages.ts +326 -0
  52. package/src/formatting.test.ts +213 -0
  53. package/src/formatting.ts +416 -0
  54. package/src/grammar/direct-mappings.ts +353 -0
  55. package/src/grammar/grammar.test.ts +1053 -0
  56. package/src/grammar/index.ts +59 -0
  57. package/src/grammar/profiles/index.ts +860 -0
  58. package/src/grammar/transformer.ts +1318 -0
  59. package/src/grammar/types.ts +630 -0
  60. package/src/index.ts +202 -0
  61. package/src/new-languages.test.ts +389 -0
  62. package/src/parser/analyze-conflicts.test.ts +229 -0
  63. package/src/parser/ar.ts +40 -0
  64. package/src/parser/create-provider.ts +309 -0
  65. package/src/parser/de.ts +36 -0
  66. package/src/parser/es.ts +31 -0
  67. package/src/parser/fr.ts +31 -0
  68. package/src/parser/id.ts +34 -0
  69. package/src/parser/index.ts +50 -0
  70. package/src/parser/ja.ts +36 -0
  71. package/src/parser/ko.ts +37 -0
  72. package/src/parser/locale-manager.test.ts +198 -0
  73. package/src/parser/locale-manager.ts +197 -0
  74. package/src/parser/parser-integration.test.ts +439 -0
  75. package/src/parser/pt.ts +37 -0
  76. package/src/parser/qu.ts +37 -0
  77. package/src/parser/sw.ts +37 -0
  78. package/src/parser/tr.ts +38 -0
  79. package/src/parser/types.ts +113 -0
  80. package/src/parser/zh.ts +38 -0
  81. package/src/plugins/vite.ts +224 -0
  82. package/src/plugins/webpack.ts +124 -0
  83. package/src/pluralization.test.ts +197 -0
  84. package/src/pluralization.ts +393 -0
  85. package/src/runtime.ts +441 -0
  86. package/src/ssr-integration.ts +225 -0
  87. package/src/test-setup.ts +195 -0
  88. package/src/translation-validation.test.ts +171 -0
  89. package/src/translator.test.ts +252 -0
  90. package/src/translator.ts +297 -0
  91. package/src/types.ts +209 -0
  92. package/src/utils/locale.ts +190 -0
  93. package/src/utils/tokenizer-adapter.ts +469 -0
  94. package/src/utils/tokenizer.ts +19 -0
  95. package/src/validators/index.ts +174 -0
  96. package/src/validators/schema.ts +129 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Integration tests for the i18n parser integration.
3
+ *
4
+ * Tests the actual esKeywords and jaKeywords providers
5
+ * work correctly with the core parser.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { esKeywords } from './es';
10
+ import { jaKeywords } from './ja';
11
+ import { frKeywords } from './fr';
12
+ import { deKeywords } from './de';
13
+ import { arKeywords } from './ar';
14
+ import { koKeywords } from './ko';
15
+ import { zhKeywords } from './zh';
16
+ import { trKeywords } from './tr';
17
+ import { createKeywordProvider, createEnglishProvider } from './create-provider';
18
+ import { es } from '../dictionaries/es';
19
+ import { ja } from '../dictionaries/ja';
20
+
21
+ describe('KeywordProvider Integration', () => {
22
+ describe('Spanish Provider (esKeywords)', () => {
23
+ it('should resolve Spanish commands to English', () => {
24
+ expect(esKeywords.resolve('alternar')).toBe('toggle');
25
+ expect(esKeywords.resolve('poner')).toBe('put');
26
+ expect(esKeywords.resolve('establecer')).toBe('set');
27
+ expect(esKeywords.resolve('mostrar')).toBe('show');
28
+ expect(esKeywords.resolve('ocultar')).toBe('hide');
29
+ });
30
+
31
+ it('should resolve Spanish keywords with priority (commands over modifiers)', () => {
32
+ // Note: Spanish 'en' maps to multiple English words:
33
+ // - commands.on = 'en' (for event handlers)
34
+ // - modifiers.in = 'en' (for prepositions)
35
+ // - modifiers.into = 'en'
36
+ // The resolver prioritizes commands, so 'en' → 'on'
37
+ expect(esKeywords.resolve('en')).toBe('on');
38
+ });
39
+
40
+ it('should resolve Spanish events', () => {
41
+ expect(esKeywords.resolve('clic')).toBe('click');
42
+ });
43
+
44
+ it('should resolve Spanish modifiers', () => {
45
+ expect(esKeywords.resolve('a')).toBe('to');
46
+ // Note: 'de' maps to both 'from' and 'of' in Spanish dictionary
47
+ // Since modifiers are processed together, the last one wins
48
+ // But commands/logical have priority, so 'de' → 'of' (modifiers.of = 'de')
49
+ expect(esKeywords.resolve('con')).toBe('with');
50
+ });
51
+
52
+ it('should resolve Spanish logical operators', () => {
53
+ expect(esKeywords.resolve('y')).toBe('and');
54
+ expect(esKeywords.resolve('o')).toBe('or');
55
+ expect(esKeywords.resolve('no')).toBe('not');
56
+ expect(esKeywords.resolve('entonces')).toBe('then');
57
+ expect(esKeywords.resolve('sino')).toBe('else');
58
+ });
59
+
60
+ it('should allow English fallback', () => {
61
+ // English keywords should resolve to themselves
62
+ expect(esKeywords.resolve('toggle')).toBe('toggle');
63
+ expect(esKeywords.resolve('on')).toBe('on');
64
+ expect(esKeywords.resolve('click')).toBe('click');
65
+ });
66
+
67
+ it('should correctly identify commands', () => {
68
+ // Spanish commands
69
+ expect(esKeywords.isCommand('alternar')).toBe(true);
70
+ expect(esKeywords.isCommand('poner')).toBe(true);
71
+ // English commands (fallback)
72
+ expect(esKeywords.isCommand('toggle')).toBe(true);
73
+ expect(esKeywords.isCommand('put')).toBe(true);
74
+ // Non-commands
75
+ expect(esKeywords.isCommand('unknown')).toBe(false);
76
+ });
77
+
78
+ it('should return locale code', () => {
79
+ expect(esKeywords.locale).toBe('es');
80
+ });
81
+
82
+ it('should provide completions list', () => {
83
+ const commands = esKeywords.getCommands();
84
+ expect(commands.length).toBeGreaterThan(0);
85
+ expect(commands).toContain('alternar');
86
+ // Should also contain English fallbacks
87
+ expect(commands).toContain('toggle');
88
+ });
89
+
90
+ it('should translate English to locale', () => {
91
+ expect(esKeywords.toLocale('toggle')).toBe('alternar');
92
+ expect(esKeywords.toLocale('put')).toBe('poner');
93
+ });
94
+ });
95
+
96
+ describe('Japanese Provider (jaKeywords)', () => {
97
+ it('should resolve Japanese commands to English', () => {
98
+ expect(jaKeywords.resolve('切り替え')).toBe('toggle');
99
+ expect(jaKeywords.resolve('置く')).toBe('put');
100
+ expect(jaKeywords.resolve('設定')).toBe('set');
101
+ expect(jaKeywords.resolve('表示')).toBe('show');
102
+ expect(jaKeywords.resolve('隠す')).toBe('hide');
103
+ });
104
+
105
+ it('should resolve Japanese keywords based on dictionary', () => {
106
+ // Note: Japanese 'で' maps to both 'on' (commands) and 'at' (modifiers)
107
+ // With priority, commands win, so 'で' → 'on'
108
+ expect(jaKeywords.resolve('で')).toBe('on');
109
+ });
110
+
111
+ it('should resolve Japanese events', () => {
112
+ expect(jaKeywords.resolve('クリック')).toBe('click');
113
+ });
114
+
115
+ it('should resolve Japanese modifiers', () => {
116
+ expect(jaKeywords.resolve('に')).toBe('to');
117
+ expect(jaKeywords.resolve('から')).toBe('from');
118
+ expect(jaKeywords.resolve('と')).toBe('with');
119
+ });
120
+
121
+ it('should resolve Japanese logical operators', () => {
122
+ expect(jaKeywords.resolve('そして')).toBe('and');
123
+ expect(jaKeywords.resolve('または')).toBe('or');
124
+ expect(jaKeywords.resolve('ではない')).toBe('not');
125
+ expect(jaKeywords.resolve('それから')).toBe('then');
126
+ // Note: Japanese dictionary has 'そうでなければ' for 'otherwise', not 'else'
127
+ expect(jaKeywords.resolve('そうでなければ')).toBe('otherwise');
128
+ });
129
+
130
+ it('should allow English fallback', () => {
131
+ expect(jaKeywords.resolve('toggle')).toBe('toggle');
132
+ expect(jaKeywords.resolve('on')).toBe('on');
133
+ expect(jaKeywords.resolve('click')).toBe('click');
134
+ });
135
+
136
+ it('should return locale code', () => {
137
+ expect(jaKeywords.locale).toBe('ja');
138
+ });
139
+ });
140
+
141
+ describe('French Provider (frKeywords)', () => {
142
+ it('should resolve French commands to English', () => {
143
+ expect(frKeywords.resolve('basculer')).toBe('toggle');
144
+ expect(frKeywords.resolve('mettre')).toBe('put');
145
+ expect(frKeywords.resolve('définir')).toBe('set');
146
+ expect(frKeywords.resolve('montrer')).toBe('show');
147
+ expect(frKeywords.resolve('cacher')).toBe('hide');
148
+ });
149
+
150
+ it('should resolve French events', () => {
151
+ expect(frKeywords.resolve('clic')).toBe('click');
152
+ });
153
+
154
+ it('should resolve French modifiers', () => {
155
+ expect(frKeywords.resolve('à')).toBe('to');
156
+ expect(frKeywords.resolve('avec')).toBe('with');
157
+ });
158
+
159
+ it('should resolve French logical operators', () => {
160
+ expect(frKeywords.resolve('et')).toBe('and');
161
+ expect(frKeywords.resolve('ou')).toBe('or');
162
+ expect(frKeywords.resolve('non')).toBe('not');
163
+ expect(frKeywords.resolve('alors')).toBe('then');
164
+ expect(frKeywords.resolve('sinon')).toBe('else');
165
+ });
166
+
167
+ it('should allow English fallback', () => {
168
+ expect(frKeywords.resolve('toggle')).toBe('toggle');
169
+ expect(frKeywords.resolve('on')).toBe('on');
170
+ expect(frKeywords.resolve('click')).toBe('click');
171
+ });
172
+
173
+ it('should return locale code', () => {
174
+ expect(frKeywords.locale).toBe('fr');
175
+ });
176
+
177
+ it('should translate English to locale', () => {
178
+ expect(frKeywords.toLocale('toggle')).toBe('basculer');
179
+ expect(frKeywords.toLocale('put')).toBe('mettre');
180
+ });
181
+ });
182
+
183
+ describe('German Provider (deKeywords)', () => {
184
+ it('should resolve German commands to English', () => {
185
+ expect(deKeywords.resolve('umschalten')).toBe('toggle');
186
+ expect(deKeywords.resolve('setzen')).toBe('put');
187
+ expect(deKeywords.resolve('festlegen')).toBe('set');
188
+ expect(deKeywords.resolve('zeigen')).toBe('show');
189
+ expect(deKeywords.resolve('verstecken')).toBe('hide');
190
+ });
191
+
192
+ it('should resolve German events', () => {
193
+ expect(deKeywords.resolve('klick')).toBe('click');
194
+ });
195
+
196
+ it('should resolve German modifiers', () => {
197
+ expect(deKeywords.resolve('zu')).toBe('to');
198
+ expect(deKeywords.resolve('mit')).toBe('with');
199
+ });
200
+
201
+ it('should resolve German logical operators', () => {
202
+ expect(deKeywords.resolve('und')).toBe('and');
203
+ expect(deKeywords.resolve('oder')).toBe('or');
204
+ expect(deKeywords.resolve('nicht')).toBe('not');
205
+ expect(deKeywords.resolve('dann')).toBe('then');
206
+ expect(deKeywords.resolve('sonst')).toBe('else');
207
+ });
208
+
209
+ it('should allow English fallback', () => {
210
+ expect(deKeywords.resolve('toggle')).toBe('toggle');
211
+ expect(deKeywords.resolve('on')).toBe('on');
212
+ expect(deKeywords.resolve('click')).toBe('click');
213
+ });
214
+
215
+ it('should return locale code', () => {
216
+ expect(deKeywords.locale).toBe('de');
217
+ });
218
+
219
+ it('should translate English to locale', () => {
220
+ expect(deKeywords.toLocale('toggle')).toBe('umschalten');
221
+ expect(deKeywords.toLocale('put')).toBe('setzen');
222
+ });
223
+ });
224
+
225
+ describe('Arabic Provider (arKeywords)', () => {
226
+ it('should resolve Arabic commands to English', () => {
227
+ expect(arKeywords.resolve('بدل')).toBe('toggle');
228
+ expect(arKeywords.resolve('ضع')).toBe('put');
229
+ expect(arKeywords.resolve('اضبط')).toBe('set');
230
+ expect(arKeywords.resolve('اظهر')).toBe('show');
231
+ expect(arKeywords.resolve('اخف')).toBe('hide');
232
+ });
233
+
234
+ it('should resolve Arabic events', () => {
235
+ expect(arKeywords.resolve('نقر')).toBe('click');
236
+ });
237
+
238
+ it('should resolve Arabic modifiers', () => {
239
+ expect(arKeywords.resolve('إلى')).toBe('to');
240
+ expect(arKeywords.resolve('مع')).toBe('with');
241
+ });
242
+
243
+ it('should resolve Arabic logical operators', () => {
244
+ expect(arKeywords.resolve('و')).toBe('and');
245
+ expect(arKeywords.resolve('أو')).toBe('or');
246
+ expect(arKeywords.resolve('ليس')).toBe('not');
247
+ expect(arKeywords.resolve('ثم')).toBe('then');
248
+ expect(arKeywords.resolve('وإلا')).toBe('else');
249
+ });
250
+
251
+ it('should allow English fallback', () => {
252
+ expect(arKeywords.resolve('toggle')).toBe('toggle');
253
+ expect(arKeywords.resolve('on')).toBe('on');
254
+ expect(arKeywords.resolve('click')).toBe('click');
255
+ });
256
+
257
+ it('should return locale code', () => {
258
+ expect(arKeywords.locale).toBe('ar');
259
+ });
260
+
261
+ it('should handle RTL text correctly (Arabic script)', () => {
262
+ // Arabic uses right-to-left script, but the parser works on tokens
263
+ // which are separated by whitespace/operators, so direction doesn't matter
264
+ expect(arKeywords.resolve('على')).toBe('on');
265
+ expect(arKeywords.resolve('بدل')).toBe('toggle');
266
+ // Verify round-trip works for commands
267
+ expect(arKeywords.toLocale('toggle')).toBe('بدل');
268
+ expect(arKeywords.toLocale('on')).toBe('على');
269
+ });
270
+
271
+ it('should translate English to locale', () => {
272
+ expect(arKeywords.toLocale('toggle')).toBe('بدل');
273
+ expect(arKeywords.toLocale('put')).toBe('ضع');
274
+ });
275
+ });
276
+
277
+ describe('Korean Provider (koKeywords)', () => {
278
+ it('should resolve Korean commands to English', () => {
279
+ expect(koKeywords.resolve('토글')).toBe('toggle');
280
+ expect(koKeywords.resolve('넣다')).toBe('put');
281
+ expect(koKeywords.resolve('설정')).toBe('set');
282
+ expect(koKeywords.resolve('보이다')).toBe('show');
283
+ expect(koKeywords.resolve('숨기다')).toBe('hide');
284
+ });
285
+
286
+ it('should resolve Korean modifiers', () => {
287
+ // Note: '에' is a conflict (used for both 'on' and 'to')
288
+ // Commands have priority, so '에' → 'on'
289
+ expect(koKeywords.resolve('와')).toBe('with');
290
+ expect(koKeywords.resolve('에서')).toBe('from');
291
+ });
292
+
293
+ it('should resolve Korean logical operators', () => {
294
+ expect(koKeywords.resolve('그리고')).toBe('and');
295
+ expect(koKeywords.resolve('또는')).toBe('or');
296
+ expect(koKeywords.resolve('아니')).toBe('not'); // dictionary uses '아니'
297
+ expect(koKeywords.resolve('그러면')).toBe('then');
298
+ // Note: '아니면' is used for both 'unless' (command) and 'else' (logical)
299
+ // Both commands and logical have priority=true, so logical overwrites commands
300
+ expect(koKeywords.resolve('아니면')).toBe('else');
301
+ });
302
+
303
+ it('should allow English fallback', () => {
304
+ expect(koKeywords.resolve('toggle')).toBe('toggle');
305
+ expect(koKeywords.resolve('on')).toBe('on');
306
+ expect(koKeywords.resolve('click')).toBe('click');
307
+ });
308
+
309
+ it('should return locale code', () => {
310
+ expect(koKeywords.locale).toBe('ko');
311
+ });
312
+
313
+ it('should handle Hangul script correctly', () => {
314
+ // Korean uses syllabic blocks (Hangul)
315
+ expect(koKeywords.resolve('에')).toBe('on'); // commands.on has priority
316
+ expect(koKeywords.resolve('토글')).toBe('toggle');
317
+ // Verify round-trip
318
+ expect(koKeywords.toLocale('toggle')).toBe('토글');
319
+ expect(koKeywords.toLocale('on')).toBe('에');
320
+ });
321
+ });
322
+
323
+ describe('Chinese Provider (zhKeywords)', () => {
324
+ it('should resolve Chinese commands to English', () => {
325
+ expect(zhKeywords.resolve('切换')).toBe('toggle');
326
+ expect(zhKeywords.resolve('放置')).toBe('put');
327
+ expect(zhKeywords.resolve('设置')).toBe('set');
328
+ expect(zhKeywords.resolve('显示')).toBe('show');
329
+ expect(zhKeywords.resolve('隐藏')).toBe('hide');
330
+ });
331
+
332
+ it('should resolve Chinese modifiers', () => {
333
+ expect(zhKeywords.resolve('到')).toBe('to');
334
+ expect(zhKeywords.resolve('与')).toBe('with'); // dictionary uses '与' not '用'
335
+ });
336
+
337
+ it('should resolve Chinese logical operators', () => {
338
+ expect(zhKeywords.resolve('和')).toBe('and');
339
+ expect(zhKeywords.resolve('或')).toBe('or');
340
+ expect(zhKeywords.resolve('非')).toBe('not');
341
+ expect(zhKeywords.resolve('那么')).toBe('then');
342
+ expect(zhKeywords.resolve('否则')).toBe('else');
343
+ });
344
+
345
+ it('should allow English fallback', () => {
346
+ expect(zhKeywords.resolve('toggle')).toBe('toggle');
347
+ expect(zhKeywords.resolve('on')).toBe('on');
348
+ expect(zhKeywords.resolve('click')).toBe('click');
349
+ });
350
+
351
+ it('should return locale code', () => {
352
+ expect(zhKeywords.locale).toBe('zh');
353
+ });
354
+
355
+ it('should handle Chinese characters correctly', () => {
356
+ // Chinese uses logographic characters
357
+ // Note: '当' (dāng) is used for 'when' (logical), 'while' (commands), and 'on' (commands)
358
+ // Since 'when' appears last in dictionary iteration, '当' → 'when'
359
+ expect(zhKeywords.resolve('当')).toBe('when');
360
+ expect(zhKeywords.resolve('切换')).toBe('toggle');
361
+ // Round-trip for toggle
362
+ expect(zhKeywords.toLocale('toggle')).toBe('切换');
363
+ // Note: toLocale('on') still returns '当' (forward map), even though reverse is 'when'
364
+ expect(zhKeywords.toLocale('on')).toBe('当');
365
+ });
366
+ });
367
+
368
+ describe('Turkish Provider (trKeywords)', () => {
369
+ it('should resolve Turkish commands to English', () => {
370
+ expect(trKeywords.resolve('değiştir')).toBe('toggle');
371
+ expect(trKeywords.resolve('koy')).toBe('put');
372
+ expect(trKeywords.resolve('ayarla')).toBe('set');
373
+ expect(trKeywords.resolve('göster')).toBe('show');
374
+ expect(trKeywords.resolve('gizle')).toBe('hide');
375
+ });
376
+
377
+ it('should resolve Turkish modifiers', () => {
378
+ // Note: 'için' is commands.for, 'e' is modifiers.to
379
+ expect(trKeywords.resolve('e')).toBe('to');
380
+ expect(trKeywords.resolve('ile')).toBe('with');
381
+ });
382
+
383
+ it('should resolve Turkish logical operators', () => {
384
+ expect(trKeywords.resolve('ve')).toBe('and');
385
+ expect(trKeywords.resolve('veya')).toBe('or');
386
+ expect(trKeywords.resolve('değil')).toBe('not');
387
+ expect(trKeywords.resolve('sonra')).toBe('then'); // dictionary uses 'sonra'
388
+ expect(trKeywords.resolve('yoksa')).toBe('else');
389
+ });
390
+
391
+ it('should allow English fallback', () => {
392
+ expect(trKeywords.resolve('toggle')).toBe('toggle');
393
+ expect(trKeywords.resolve('on')).toBe('on');
394
+ expect(trKeywords.resolve('click')).toBe('click');
395
+ });
396
+
397
+ it('should return locale code', () => {
398
+ expect(trKeywords.locale).toBe('tr');
399
+ });
400
+
401
+ it('should handle Turkish special characters correctly', () => {
402
+ // Turkish uses extended Latin with special chars: ı, ğ, ş, ç, ö, ü
403
+ expect(trKeywords.resolve('üzerinde')).toBe('on');
404
+ expect(trKeywords.resolve('değiştir')).toBe('toggle');
405
+ // Verify round-trip
406
+ expect(trKeywords.toLocale('toggle')).toBe('değiştir');
407
+ expect(trKeywords.toLocale('on')).toBe('üzerinde');
408
+ });
409
+ });
410
+
411
+ describe('createKeywordProvider factory', () => {
412
+ it('should create provider from dictionary', () => {
413
+ const provider = createKeywordProvider(es, 'es');
414
+ expect(provider.locale).toBe('es');
415
+ expect(provider.resolve('alternar')).toBe('toggle');
416
+ });
417
+
418
+ it('should support disabling English fallback', () => {
419
+ const provider = createKeywordProvider(es, 'es', {
420
+ allowEnglishFallback: false,
421
+ });
422
+
423
+ // Spanish should still work
424
+ expect(provider.resolve('alternar')).toBe('toggle');
425
+ // English should NOT work (no fallback)
426
+ expect(provider.resolve('toggle')).toBeUndefined();
427
+ });
428
+ });
429
+
430
+ describe('createEnglishProvider', () => {
431
+ it('should create English-only provider', () => {
432
+ const provider = createEnglishProvider();
433
+ expect(provider.locale).toBe('en');
434
+ expect(provider.resolve('toggle')).toBe('toggle');
435
+ expect(provider.resolve('on')).toBe('on');
436
+ expect(provider.resolve('alternar')).toBeUndefined();
437
+ });
438
+ });
439
+ });
@@ -0,0 +1,37 @@
1
+ // packages/i18n/src/parser/pt.ts
2
+
3
+ import { pt } from '../dictionaries/pt';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Portuguese (Português) keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Portuguese:
11
+ * - `em clique alternar .active` → parses as `on click toggle .active`
12
+ * - `se verdadeiro então registrar "Olá"` → parses as `if true then log "Hello"`
13
+ *
14
+ * Portuguese is an SVO language with:
15
+ * - High mutual intelligibility with Spanish
16
+ * - Prepositions (like English and Spanish)
17
+ * - Gender agreement (simplified for hyperscript)
18
+ * - Fusional morphology
19
+ *
20
+ * Direct translation is supported between Portuguese and Spanish,
21
+ * avoiding the English pivot for more natural translations.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { ptKeywords } from '@lokascript/i18n/parser/pt';
26
+ * import { Parser } from '@lokascript/core';
27
+ *
28
+ * const parser = new Parser({ keywords: ptKeywords });
29
+ * parser.parse('em clique alternar .active');
30
+ * ```
31
+ */
32
+ export const ptKeywords: KeywordProvider = createKeywordProvider(pt, 'pt', {
33
+ allowEnglishFallback: true,
34
+ });
35
+
36
+ // Re-export for convenience
37
+ export { pt as ptDictionary } from '../dictionaries/pt';
@@ -0,0 +1,37 @@
1
+ // packages/i18n/src/parser/qu.ts
2
+
3
+ import { qu } from '../dictionaries/qu';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Quechua (Runasimi) keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Quechua:
11
+ * - `ñitiy-pi yapay #count-ta` → parses as `on click increment #count`
12
+ *
13
+ * Quechua is an SOV language with:
14
+ * - Agglutinative/polysynthetic morphology
15
+ * - Extensive suffix system (case markers: -ta, -man, -pi, -manta, -wan)
16
+ * - Postpositions (unlike Spanish which uses prepositions)
17
+ * - Object-Verb word order
18
+ * - Evidentiality markers (not used in hyperscript)
19
+ *
20
+ * The grammar transformer handles suffix joining via hyphen notation
21
+ * (e.g., "#count-ta" attaches the accusative marker).
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { quKeywords } from '@lokascript/i18n/parser/qu';
26
+ * import { Parser } from '@lokascript/core';
27
+ *
28
+ * const parser = new Parser({ keywords: quKeywords });
29
+ * parser.parse('ñitiy-pi yapay #count-ta');
30
+ * ```
31
+ */
32
+ export const quKeywords: KeywordProvider = createKeywordProvider(qu, 'qu', {
33
+ allowEnglishFallback: true,
34
+ });
35
+
36
+ // Re-export for convenience
37
+ export { qu as quDictionary } from '../dictionaries/qu';
@@ -0,0 +1,37 @@
1
+ // packages/i18n/src/parser/sw.ts
2
+
3
+ import { sw } from '../dictionaries/sw';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Swahili (Kiswahili) keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Swahili:
11
+ * - `kwenye bonyeza badilisha .active` → parses as `on click toggle .active`
12
+ * - `kama kweli basi andika "Habari"` → parses as `if true then log "Hello"`
13
+ *
14
+ * Swahili is an SVO Bantu language with:
15
+ * - Agglutinative verb morphology
16
+ * - Noun class system (simplified for hyperscript keywords)
17
+ * - Prepositions (unlike many other African languages)
18
+ * - Subject-verb agreement (not relevant for hyperscript)
19
+ *
20
+ * Swahili is widely spoken in East Africa and serves as a lingua franca
21
+ * for millions of speakers.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { swKeywords } from '@lokascript/i18n/parser/sw';
26
+ * import { Parser } from '@lokascript/core';
27
+ *
28
+ * const parser = new Parser({ keywords: swKeywords });
29
+ * parser.parse('kwenye bonyeza badilisha .active');
30
+ * ```
31
+ */
32
+ export const swKeywords: KeywordProvider = createKeywordProvider(sw, 'sw', {
33
+ allowEnglishFallback: true,
34
+ });
35
+
36
+ // Re-export for convenience
37
+ export { sw as swDictionary } from '../dictionaries/sw';
@@ -0,0 +1,38 @@
1
+ // packages/i18n/src/parser/tr.ts
2
+
3
+ import { tr } from '../dictionaries/tr';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Turkish keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Turkish:
11
+ * - `üzerinde tıklama değiştir .active` → parses as `on click toggle .active`
12
+ * - `eğer doğru ise kaydet "merhaba"` → parses as `if true then log "hello"`
13
+ *
14
+ * English keywords are also accepted (mixed mode), so:
15
+ * - `üzerinde click değiştir .active` also works (Turkish `üzerinde` + English `click`)
16
+ *
17
+ * Turkish is a useful test case because:
18
+ * - SOV word order (Subject-Object-Verb)
19
+ * - Agglutinative morphology with suffixes
20
+ * - Vowel harmony rules
21
+ * - Latin script with special characters (ı, ğ, ş, ç, ö, ü)
22
+ * - Tests parser's handling of extended Latin characters
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { trKeywords } from '@lokascript/i18n/parser/tr';
27
+ * import { Parser } from '@lokascript/core';
28
+ *
29
+ * const parser = new Parser({ keywords: trKeywords });
30
+ * parser.parse('üzerinde tıklama değiştir .active');
31
+ * ```
32
+ */
33
+ export const trKeywords: KeywordProvider = createKeywordProvider(tr, 'tr', {
34
+ allowEnglishFallback: true,
35
+ });
36
+
37
+ // Re-export for convenience
38
+ export { tr as trDictionary } from '../dictionaries/tr';