@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,515 @@
1
+ /**
2
+ * Dictionary Derivation from Semantic Profiles
3
+ *
4
+ * Creates Dictionary objects from LanguageProfile keywords,
5
+ * eliminating keyword duplication between packages.
6
+ *
7
+ * The semantic profile is the single source of truth for keyword translations.
8
+ * This adapter extracts the `primary` value from each KeywordTranslation.
9
+ */
10
+
11
+ import type { Dictionary, DictionaryCategory } from '../types';
12
+
13
+ /**
14
+ * KeywordTranslation interface (matches @lokascript/semantic)
15
+ */
16
+ interface KeywordTranslation {
17
+ readonly primary: string;
18
+ readonly alternatives?: string[];
19
+ readonly normalized?: string;
20
+ }
21
+
22
+ /**
23
+ * LanguageProfile interface (subset needed for derivation)
24
+ */
25
+ interface LanguageProfile {
26
+ readonly code: string;
27
+ readonly keywords: Record<string, KeywordTranslation>;
28
+ readonly references?: Record<string, string>;
29
+ }
30
+
31
+ // =============================================================================
32
+ // Keyword Category Mappings
33
+ // =============================================================================
34
+
35
+ /**
36
+ * Keywords that belong to the 'commands' category.
37
+ */
38
+ const COMMAND_KEYWORDS = [
39
+ // Event handling
40
+ 'on',
41
+ 'tell',
42
+ 'trigger',
43
+ 'send',
44
+ // DOM manipulation
45
+ 'take',
46
+ 'put',
47
+ 'set',
48
+ 'get',
49
+ 'add',
50
+ 'remove',
51
+ 'toggle',
52
+ 'hide',
53
+ 'show',
54
+ // Control flow
55
+ 'if',
56
+ 'unless',
57
+ 'repeat',
58
+ 'for',
59
+ 'while',
60
+ 'until',
61
+ 'continue',
62
+ 'break',
63
+ 'halt',
64
+ // Async
65
+ 'wait',
66
+ 'fetch',
67
+ 'call',
68
+ 'return',
69
+ // Other commands
70
+ 'make',
71
+ 'log',
72
+ 'throw',
73
+ 'catch',
74
+ 'measure',
75
+ 'transition',
76
+ // Data commands
77
+ 'increment',
78
+ 'decrement',
79
+ 'bind',
80
+ 'default',
81
+ 'persist',
82
+ // Navigation
83
+ 'go',
84
+ 'pushUrl',
85
+ 'replaceUrl',
86
+ // Utility
87
+ 'copy',
88
+ 'pick',
89
+ 'beep',
90
+ // Advanced
91
+ 'js',
92
+ 'async',
93
+ 'render',
94
+ // Animation
95
+ 'swap',
96
+ 'morph',
97
+ 'settle',
98
+ // Content
99
+ 'append',
100
+ 'prepend',
101
+ 'clone',
102
+ // Control
103
+ 'exit',
104
+ // Behaviors
105
+ 'install',
106
+ 'behavior',
107
+ 'init',
108
+ // Focus
109
+ 'focus',
110
+ 'blur',
111
+ ] as const;
112
+
113
+ /**
114
+ * Keywords that belong to the 'modifiers' category.
115
+ */
116
+ const MODIFIER_KEYWORDS = [
117
+ 'to',
118
+ 'from',
119
+ 'into',
120
+ 'with',
121
+ 'at',
122
+ 'in',
123
+ 'of',
124
+ 'as',
125
+ 'by',
126
+ 'before',
127
+ 'after',
128
+ 'over',
129
+ 'under',
130
+ 'between',
131
+ 'through',
132
+ 'without',
133
+ ] as const;
134
+
135
+ /**
136
+ * Keywords that belong to the 'logical' category.
137
+ */
138
+ const LOGICAL_KEYWORDS = [
139
+ 'and',
140
+ 'or',
141
+ 'not',
142
+ 'is',
143
+ 'exists',
144
+ 'matches',
145
+ 'contains',
146
+ 'includes',
147
+ 'equals',
148
+ 'then',
149
+ 'else',
150
+ 'otherwise',
151
+ 'end',
152
+ ] as const;
153
+
154
+ /**
155
+ * Keywords that belong to the 'expressions' category.
156
+ */
157
+ const EXPRESSION_KEYWORDS = [
158
+ // Positional
159
+ 'first',
160
+ 'last',
161
+ 'next',
162
+ 'previous',
163
+ 'prev',
164
+ 'random',
165
+ // DOM traversal
166
+ 'closest',
167
+ 'parent',
168
+ 'children',
169
+ 'within',
170
+ // Emptiness/existence
171
+ 'no',
172
+ 'empty',
173
+ 'some',
174
+ ] as const;
175
+
176
+ // =============================================================================
177
+ // Derivation Functions
178
+ // =============================================================================
179
+
180
+ /**
181
+ * Extract primary translations for a list of keywords from the profile.
182
+ * Returns a Record<string, string> mapping English key to translated value.
183
+ */
184
+ function extractCategory(
185
+ keywords: Record<string, KeywordTranslation>,
186
+ keys: readonly string[]
187
+ ): Record<string, string> {
188
+ const result: Record<string, string> = {};
189
+ for (const key of keys) {
190
+ const translation = keywords[key];
191
+ if (translation) {
192
+ result[key] = translation.primary;
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+
198
+ /**
199
+ * Extract references from the profile (me, it, you, etc.).
200
+ */
201
+ function extractReferences(references: Record<string, string> | undefined): Record<string, string> {
202
+ if (!references) return {};
203
+ return { ...references };
204
+ }
205
+
206
+ /**
207
+ * Merge base dictionary with profile-derived values.
208
+ * Profile values take precedence (they're the source of truth).
209
+ */
210
+ function mergeWithFallback(
211
+ derived: Record<string, string>,
212
+ fallback: Record<string, string>
213
+ ): Record<string, string> {
214
+ return { ...fallback, ...derived };
215
+ }
216
+
217
+ // =============================================================================
218
+ // Fallback Dictionaries
219
+ // =============================================================================
220
+
221
+ /**
222
+ * Event names - these are typically not in profiles since they're
223
+ * DOM standard events. Localization is optional.
224
+ */
225
+ const EVENT_FALLBACK: Record<string, string> = {
226
+ click: 'click',
227
+ dblclick: 'dblclick',
228
+ mousedown: 'mousedown',
229
+ mouseup: 'mouseup',
230
+ mouseenter: 'mouseenter',
231
+ mouseleave: 'mouseleave',
232
+ mouseover: 'mouseover',
233
+ mouseout: 'mouseout',
234
+ mousemove: 'mousemove',
235
+ keydown: 'keydown',
236
+ keyup: 'keyup',
237
+ keypress: 'keypress',
238
+ focus: 'focus',
239
+ blur: 'blur',
240
+ change: 'change',
241
+ input: 'input',
242
+ submit: 'submit',
243
+ reset: 'reset',
244
+ load: 'load',
245
+ unload: 'unload',
246
+ resize: 'resize',
247
+ scroll: 'scroll',
248
+ touchstart: 'touchstart',
249
+ touchend: 'touchend',
250
+ touchmove: 'touchmove',
251
+ touchcancel: 'touchcancel',
252
+ };
253
+
254
+ /**
255
+ * Temporal keywords - time units.
256
+ */
257
+ const TEMPORAL_FALLBACK: Record<string, string> = {
258
+ seconds: 'seconds',
259
+ second: 'second',
260
+ milliseconds: 'milliseconds',
261
+ millisecond: 'millisecond',
262
+ minutes: 'minutes',
263
+ minute: 'minute',
264
+ hours: 'hours',
265
+ hour: 'hour',
266
+ ms: 'ms',
267
+ s: 's',
268
+ min: 'min',
269
+ h: 'h',
270
+ };
271
+
272
+ /**
273
+ * Value keywords - literals and references.
274
+ */
275
+ const VALUES_FALLBACK: Record<string, string> = {
276
+ true: 'true',
277
+ false: 'false',
278
+ null: 'null',
279
+ undefined: 'undefined',
280
+ element: 'element',
281
+ window: 'window',
282
+ document: 'document',
283
+ value: 'value',
284
+ };
285
+
286
+ /**
287
+ * Attribute keywords.
288
+ */
289
+ const ATTRIBUTES_FALLBACK: Record<string, string> = {
290
+ class: 'class',
291
+ classes: 'classes',
292
+ style: 'style',
293
+ styles: 'styles',
294
+ attribute: 'attribute',
295
+ attributes: 'attributes',
296
+ property: 'property',
297
+ properties: 'properties',
298
+ };
299
+
300
+ /**
301
+ * Expression keywords fallback.
302
+ */
303
+ const EXPRESSIONS_FALLBACK: Record<string, string> = {
304
+ at: 'at',
305
+ 'starts with': 'starts with',
306
+ 'ends with': 'ends with',
307
+ };
308
+
309
+ // =============================================================================
310
+ // Main Derivation Function
311
+ // =============================================================================
312
+
313
+ /**
314
+ * Options for dictionary derivation.
315
+ */
316
+ export interface DeriveOptions {
317
+ /**
318
+ * Override specific categories with custom values.
319
+ * Useful for categories not fully covered by the profile.
320
+ */
321
+ overrides?: Partial<Dictionary>;
322
+
323
+ /**
324
+ * Whether to include English fallbacks for missing entries.
325
+ * Default: true
326
+ */
327
+ includeFallbacks?: boolean;
328
+ }
329
+
330
+ /**
331
+ * Derive a Dictionary from a LanguageProfile.
332
+ *
333
+ * This is the main adapter function that creates dictionaries from
334
+ * semantic profiles, eliminating keyword duplication.
335
+ *
336
+ * @param profile - The language profile to derive from
337
+ * @param options - Derivation options
338
+ * @returns A complete Dictionary object
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * import { japaneseProfile } from '@lokascript/semantic';
343
+ * import { deriveFromProfile } from './derive';
344
+ *
345
+ * export const ja = deriveFromProfile(japaneseProfile);
346
+ * ```
347
+ */
348
+ export function deriveFromProfile(
349
+ profile: LanguageProfile,
350
+ options: DeriveOptions = {}
351
+ ): Dictionary {
352
+ const { overrides = {}, includeFallbacks = true } = options;
353
+ const { keywords, references } = profile;
354
+
355
+ // Extract from profile keywords
356
+ const derivedCommands = extractCategory(keywords, COMMAND_KEYWORDS);
357
+ const derivedModifiers = extractCategory(keywords, MODIFIER_KEYWORDS);
358
+ const derivedLogical = extractCategory(keywords, LOGICAL_KEYWORDS);
359
+ const derivedExpressions = extractCategory(keywords, EXPRESSION_KEYWORDS);
360
+
361
+ // Extract references (me, it, you, etc.) and add to values
362
+ const derivedReferences = extractReferences(references);
363
+
364
+ // Build the dictionary with fallbacks
365
+ const dictionary: Dictionary = {
366
+ commands: includeFallbacks ? mergeWithFallback(derivedCommands, {}) : derivedCommands,
367
+
368
+ modifiers: includeFallbacks ? mergeWithFallback(derivedModifiers, {}) : derivedModifiers,
369
+
370
+ events: includeFallbacks ? { ...EVENT_FALLBACK } : {},
371
+
372
+ logical: includeFallbacks ? mergeWithFallback(derivedLogical, {}) : derivedLogical,
373
+
374
+ temporal: includeFallbacks ? { ...TEMPORAL_FALLBACK } : {},
375
+
376
+ values: includeFallbacks
377
+ ? mergeWithFallback(derivedReferences, VALUES_FALLBACK)
378
+ : derivedReferences,
379
+
380
+ attributes: includeFallbacks ? { ...ATTRIBUTES_FALLBACK } : {},
381
+
382
+ expressions: includeFallbacks
383
+ ? mergeWithFallback(derivedExpressions, EXPRESSIONS_FALLBACK)
384
+ : derivedExpressions,
385
+ };
386
+
387
+ // Apply overrides (for language-specific customizations)
388
+ if (overrides.commands) {
389
+ dictionary.commands = { ...dictionary.commands, ...overrides.commands };
390
+ }
391
+ if (overrides.modifiers) {
392
+ dictionary.modifiers = { ...dictionary.modifiers, ...overrides.modifiers };
393
+ }
394
+ if (overrides.events) {
395
+ dictionary.events = { ...dictionary.events, ...overrides.events };
396
+ }
397
+ if (overrides.logical) {
398
+ dictionary.logical = { ...dictionary.logical, ...overrides.logical };
399
+ }
400
+ if (overrides.temporal) {
401
+ dictionary.temporal = { ...dictionary.temporal, ...overrides.temporal };
402
+ }
403
+ if (overrides.values) {
404
+ dictionary.values = { ...dictionary.values, ...overrides.values };
405
+ }
406
+ if (overrides.attributes) {
407
+ dictionary.attributes = { ...dictionary.attributes, ...overrides.attributes };
408
+ }
409
+ if (overrides.expressions) {
410
+ dictionary.expressions = { ...dictionary.expressions, ...overrides.expressions };
411
+ }
412
+
413
+ return dictionary;
414
+ }
415
+
416
+ /**
417
+ * Create an English dictionary (identity mapping).
418
+ * Special case since English keywords map to themselves.
419
+ */
420
+ export function createEnglishDictionary(): Dictionary {
421
+ const identity = (keys: readonly string[]): Record<string, string> => {
422
+ const result: Record<string, string> = {};
423
+ for (const key of keys) {
424
+ result[key] = key;
425
+ }
426
+ return result;
427
+ };
428
+
429
+ return {
430
+ commands: identity(COMMAND_KEYWORDS),
431
+ modifiers: identity(MODIFIER_KEYWORDS),
432
+ events: { ...EVENT_FALLBACK },
433
+ logical: identity(LOGICAL_KEYWORDS),
434
+ temporal: { ...TEMPORAL_FALLBACK },
435
+ values: {
436
+ ...VALUES_FALLBACK,
437
+ it: 'it',
438
+ its: 'its',
439
+ me: 'me',
440
+ my: 'my',
441
+ myself: 'myself',
442
+ you: 'you',
443
+ your: 'your',
444
+ yourself: 'yourself',
445
+ target: 'target',
446
+ detail: 'detail',
447
+ event: 'event',
448
+ body: 'body',
449
+ result: 'result',
450
+ },
451
+ attributes: { ...ATTRIBUTES_FALLBACK },
452
+ expressions: {
453
+ ...identity(EXPRESSION_KEYWORDS),
454
+ ...EXPRESSIONS_FALLBACK,
455
+ },
456
+ };
457
+ }
458
+
459
+ // =============================================================================
460
+ // Validation Utilities
461
+ // =============================================================================
462
+
463
+ /**
464
+ * Compare a derived dictionary against an original to find missing entries.
465
+ * Useful for validating that derivation is complete.
466
+ */
467
+ export function validateDictionary(
468
+ derived: Dictionary,
469
+ original: Dictionary
470
+ ): { missing: Record<DictionaryCategory, string[]>; coverage: number } {
471
+ const categories: DictionaryCategory[] = [
472
+ 'commands',
473
+ 'modifiers',
474
+ 'events',
475
+ 'logical',
476
+ 'temporal',
477
+ 'values',
478
+ 'attributes',
479
+ 'expressions',
480
+ ];
481
+
482
+ const missing: Record<DictionaryCategory, string[]> = {
483
+ commands: [],
484
+ modifiers: [],
485
+ events: [],
486
+ logical: [],
487
+ temporal: [],
488
+ values: [],
489
+ attributes: [],
490
+ expressions: [],
491
+ };
492
+
493
+ let totalOriginal = 0;
494
+ let totalDerived = 0;
495
+
496
+ for (const category of categories) {
497
+ const origKeys = Object.keys(original[category]);
498
+ const derivedKeys = new Set(Object.keys(derived[category]));
499
+
500
+ totalOriginal += origKeys.length;
501
+
502
+ for (const key of origKeys) {
503
+ if (derivedKeys.has(key)) {
504
+ totalDerived++;
505
+ } else {
506
+ missing[category].push(key);
507
+ }
508
+ }
509
+ }
510
+
511
+ return {
512
+ missing,
513
+ coverage: totalOriginal > 0 ? totalDerived / totalOriginal : 1,
514
+ };
515
+ }