@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,229 @@
1
+ // packages/i18n/src/parser/analyze-conflicts.test.ts
2
+ /**
3
+ * Analyzes dictionaries for parsing conflicts.
4
+ *
5
+ * A conflict occurs when the same locale word maps to different English words
6
+ * across different categories (e.g., 'de' in Spanish maps to both 'from' and 'of').
7
+ *
8
+ * The priority system in create-provider.ts handles these by giving priority to:
9
+ * commands > logical > events > values > temporal > modifiers > attributes
10
+ */
11
+
12
+ import { describe, it, expect } from 'vitest';
13
+ import { es } from '../dictionaries/es';
14
+ import { ja } from '../dictionaries/ja';
15
+ import { fr } from '../dictionaries/fr';
16
+ import { de } from '../dictionaries/de';
17
+ import { ar } from '../dictionaries/ar';
18
+ import { ko } from '../dictionaries/ko';
19
+ import { zh } from '../dictionaries/zh';
20
+ import { tr } from '../dictionaries/tr';
21
+ import type { Dictionary } from '../types';
22
+
23
+ interface Conflict {
24
+ localeWord: string;
25
+ mappings: Array<{ english: string; category: string }>;
26
+ }
27
+
28
+ function analyzeConflicts(dictionary: Dictionary, localeName: string): Conflict[] {
29
+ const conflicts: Conflict[] = [];
30
+ const wordMap = new Map<string, Array<{ english: string; category: string }>>();
31
+
32
+ const categories = [
33
+ { name: 'commands', data: dictionary.commands },
34
+ { name: 'modifiers', data: dictionary.modifiers },
35
+ { name: 'events', data: dictionary.events },
36
+ { name: 'logical', data: dictionary.logical },
37
+ { name: 'temporal', data: dictionary.temporal },
38
+ { name: 'values', data: dictionary.values },
39
+ { name: 'attributes', data: dictionary.attributes },
40
+ ];
41
+
42
+ for (const { name, data } of categories) {
43
+ if (!data) continue;
44
+ for (const [english, locale] of Object.entries(data)) {
45
+ const normalized = locale.toLowerCase();
46
+ if (!wordMap.has(normalized)) {
47
+ wordMap.set(normalized, []);
48
+ }
49
+ wordMap.get(normalized)!.push({ english: english.toLowerCase(), category: name });
50
+ }
51
+ }
52
+
53
+ // Find words with multiple mappings
54
+ for (const [localeWord, mappings] of wordMap) {
55
+ if (mappings.length > 1) {
56
+ // Check if they map to different English words
57
+ const uniqueEnglish = new Set(mappings.map(m => m.english));
58
+ if (uniqueEnglish.size > 1) {
59
+ conflicts.push({ localeWord, mappings });
60
+ }
61
+ }
62
+ }
63
+
64
+ return conflicts;
65
+ }
66
+
67
+ describe('Dictionary Conflict Analysis', () => {
68
+ describe('Spanish (es)', () => {
69
+ it('should identify conflicts', () => {
70
+ const conflicts = analyzeConflicts(es, 'es');
71
+ console.log('\n=== Spanish Conflicts ===');
72
+ for (const c of conflicts) {
73
+ console.log(` '${c.localeWord}' maps to:`);
74
+ for (const m of c.mappings) {
75
+ console.log(` - '${m.english}' (${m.category})`);
76
+ }
77
+ }
78
+
79
+ // Known conflicts: 'en' (on/in/into), 'de' (from/of)
80
+ // These are handled by priority - commands win
81
+ expect(conflicts.length).toBeGreaterThan(0);
82
+
83
+ // Verify 'en' conflict exists
84
+ const enConflict = conflicts.find(c => c.localeWord === 'en');
85
+ expect(enConflict).toBeDefined();
86
+ });
87
+ });
88
+
89
+ describe('Japanese (ja)', () => {
90
+ it('should identify conflicts', () => {
91
+ const conflicts = analyzeConflicts(ja, 'ja');
92
+ console.log('\n=== Japanese Conflicts ===');
93
+ for (const c of conflicts) {
94
+ console.log(` '${c.localeWord}' maps to:`);
95
+ for (const m of c.mappings) {
96
+ console.log(` - '${m.english}' (${m.category})`);
97
+ }
98
+ }
99
+
100
+ // Document any conflicts found
101
+ if (conflicts.length === 0) {
102
+ console.log(' No conflicts found!');
103
+ }
104
+ });
105
+ });
106
+
107
+ describe('French (fr)', () => {
108
+ it('should identify conflicts', () => {
109
+ const conflicts = analyzeConflicts(fr, 'fr');
110
+ console.log('\n=== French Conflicts ===');
111
+ for (const c of conflicts) {
112
+ console.log(` '${c.localeWord}' maps to:`);
113
+ for (const m of c.mappings) {
114
+ console.log(` - '${m.english}' (${m.category})`);
115
+ }
116
+ }
117
+
118
+ // 'à' maps to both 'to' and 'at', 'de' maps to 'from' and 'of'
119
+ const aConflict = conflicts.find(c => c.localeWord === 'à');
120
+ expect(aConflict).toBeDefined();
121
+ });
122
+ });
123
+
124
+ describe('German (de)', () => {
125
+ it('should identify conflicts', () => {
126
+ const conflicts = analyzeConflicts(de, 'de');
127
+ console.log('\n=== German Conflicts ===');
128
+ for (const c of conflicts) {
129
+ console.log(` '${c.localeWord}' maps to:`);
130
+ for (const m of c.mappings) {
131
+ console.log(` - '${m.english}' (${m.category})`);
132
+ }
133
+ }
134
+
135
+ // Document any conflicts
136
+ if (conflicts.length === 0) {
137
+ console.log(' No conflicts found!');
138
+ }
139
+ });
140
+ });
141
+
142
+ describe('Arabic (ar)', () => {
143
+ it('should identify conflicts', () => {
144
+ const conflicts = analyzeConflicts(ar, 'ar');
145
+ console.log('\n=== Arabic Conflicts ===');
146
+ for (const c of conflicts) {
147
+ console.log(` '${c.localeWord}' maps to:`);
148
+ for (const m of c.mappings) {
149
+ console.log(` - '${m.english}' (${m.category})`);
150
+ }
151
+ }
152
+
153
+ // Document any conflicts
154
+ if (conflicts.length === 0) {
155
+ console.log(' No conflicts found!');
156
+ }
157
+ });
158
+ });
159
+
160
+ describe('Korean (ko)', () => {
161
+ it('should identify conflicts', () => {
162
+ const conflicts = analyzeConflicts(ko, 'ko');
163
+ console.log('\n=== Korean Conflicts ===');
164
+ for (const c of conflicts) {
165
+ console.log(` '${c.localeWord}' maps to:`);
166
+ for (const m of c.mappings) {
167
+ console.log(` - '${m.english}' (${m.category})`);
168
+ }
169
+ }
170
+
171
+ // Korean has notable conflicts: '에' (on/to/at), '동안' (for/while), '아니면' (unless/else)
172
+ const aeConflict = conflicts.find(c => c.localeWord === '에');
173
+ expect(aeConflict).toBeDefined();
174
+ });
175
+ });
176
+
177
+ describe('Chinese (zh)', () => {
178
+ it('should identify conflicts', () => {
179
+ const conflicts = analyzeConflicts(zh, 'zh');
180
+ console.log('\n=== Chinese Conflicts ===');
181
+ for (const c of conflicts) {
182
+ console.log(` '${c.localeWord}' maps to:`);
183
+ for (const m of c.mappings) {
184
+ console.log(` - '${m.english}' (${m.category})`);
185
+ }
186
+ }
187
+
188
+ // Chinese has notable conflicts: '当' (on/while), '获取' (take/get/fetch)
189
+ const dangConflict = conflicts.find(c => c.localeWord === '当');
190
+ expect(dangConflict).toBeDefined();
191
+ });
192
+ });
193
+
194
+ describe('Turkish (tr)', () => {
195
+ it('should identify conflicts', () => {
196
+ const conflicts = analyzeConflicts(tr, 'tr');
197
+ console.log('\n=== Turkish Conflicts ===');
198
+ for (const c of conflicts) {
199
+ console.log(` '${c.localeWord}' maps to:`);
200
+ for (const m of c.mappings) {
201
+ console.log(` - '${m.english}' (${m.category})`);
202
+ }
203
+ }
204
+
205
+ // Document any conflicts
206
+ if (conflicts.length === 0) {
207
+ console.log(' No conflicts found!');
208
+ }
209
+ });
210
+ });
211
+
212
+ describe('Priority Resolution', () => {
213
+ it('should explain how conflicts are resolved', () => {
214
+ // This test documents the priority system
215
+ console.log('\n=== Conflict Resolution Strategy ===');
216
+ console.log('Priority order (highest to lowest):');
217
+ console.log(' 1. commands - core actions like toggle, put, set');
218
+ console.log(' 2. logical - operators like and, or, then, else');
219
+ console.log(' 3. events - DOM events like click, focus');
220
+ console.log(' 4. values - literals like true, false, me, it');
221
+ console.log(' 5. temporal - time units like seconds, ms');
222
+ console.log(' 6. modifiers - prepositions like to, from, with');
223
+ console.log(' 7. attributes - DOM properties like class, style');
224
+ console.log('\nExample: Spanish "en" maps to both "on" (command) and "in" (modifier)');
225
+ console.log(' → Resolution: "en" resolves to "on" because commands have priority');
226
+ expect(true).toBe(true);
227
+ });
228
+ });
229
+ });
@@ -0,0 +1,40 @@
1
+ // packages/i18n/src/parser/ar.ts
2
+
3
+ import { ar } from '../dictionaries/ar';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Arabic keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Arabic:
11
+ * - `على نقر بدل .active` → parses as `on click toggle .active`
12
+ * - `إذا صحيح ثم سجل "مرحبا"` → parses as `if true then log "hello"`
13
+ *
14
+ * English keywords are also accepted (mixed mode), so:
15
+ * - `على click بدل .active` also works (Arabic `على` + English `click`)
16
+ *
17
+ * Arabic is a crucial test case because:
18
+ * - Right-to-left (RTL) script
19
+ * - Different character set (Unicode Arabic block)
20
+ * - Space-separated words with diacritics
21
+ * - Tests parser's Unicode handling robustly
22
+ *
23
+ * Note: The parser handles RTL text correctly because tokenization
24
+ * is based on whitespace/operator boundaries, not visual direction.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { arKeywords } from '@lokascript/i18n/parser/ar';
29
+ * import { Parser } from '@lokascript/core';
30
+ *
31
+ * const parser = new Parser({ keywords: arKeywords });
32
+ * parser.parse('على نقر بدل .active');
33
+ * ```
34
+ */
35
+ export const arKeywords: KeywordProvider = createKeywordProvider(ar, 'ar', {
36
+ allowEnglishFallback: true,
37
+ });
38
+
39
+ // Re-export for convenience
40
+ export { ar as arDictionary } from '../dictionaries/ar';
@@ -0,0 +1,309 @@
1
+ // packages/i18n/src/parser/create-provider.ts
2
+
3
+ import type { Dictionary } from '../types';
4
+ import type { KeywordProvider, KeywordProviderOptions } from './types';
5
+ import {
6
+ ENGLISH_COMMANDS,
7
+ ENGLISH_KEYWORDS,
8
+ ENGLISH_MODIFIERS,
9
+ ENGLISH_LOGICAL_KEYWORDS,
10
+ ENGLISH_VALUE_KEYWORDS,
11
+ ENGLISH_EXPRESSION_KEYWORDS,
12
+ UNIVERSAL_ENGLISH_KEYWORDS,
13
+ } from '../constants';
14
+
15
+ /**
16
+ * Creates a KeywordProvider from a dictionary.
17
+ *
18
+ * The provider creates reverse mappings (locale → English) for fast
19
+ * resolution during parsing.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { es } from '../dictionaries/es';
24
+ * export const esKeywords = createKeywordProvider(es, 'es');
25
+ * ```
26
+ */
27
+ export function createKeywordProvider(
28
+ dictionary: Dictionary,
29
+ locale: string,
30
+ options: KeywordProviderOptions = {}
31
+ ): KeywordProvider {
32
+ const { allowEnglishFallback = true } = options;
33
+
34
+ // Build reverse maps: locale keyword → English canonical
35
+ const reverseCommands = new Map<string, string>();
36
+ const reverseModifiers = new Map<string, string>();
37
+ const reverseEvents = new Map<string, string>();
38
+ const reverseLogical = new Map<string, string>();
39
+ const reverseTemporal = new Map<string, string>();
40
+ const reverseValues = new Map<string, string>();
41
+ const reverseAttributes = new Map<string, string>();
42
+ const reverseExpressions = new Map<string, string>();
43
+ const reverseAll = new Map<string, string>();
44
+
45
+ // Forward maps: English → locale keyword
46
+ const forwardAll = new Map<string, string>();
47
+
48
+ // Build reverse mappings from dictionary
49
+ // Note: reverseAll uses priority - first category to claim a locale word wins
50
+ function buildReverseMap(
51
+ category: Record<string, string>,
52
+ reverseMap: Map<string, string>,
53
+ priority: boolean = false
54
+ ): void {
55
+ for (const [english, localeWord] of Object.entries(category)) {
56
+ const normalizedLocale = localeWord.toLowerCase();
57
+ reverseMap.set(normalizedLocale, english.toLowerCase());
58
+ // Only set in reverseAll if not already claimed (priority) or if this is a priority category
59
+ if (priority || !reverseAll.has(normalizedLocale)) {
60
+ reverseAll.set(normalizedLocale, english.toLowerCase());
61
+ }
62
+ forwardAll.set(english.toLowerCase(), localeWord.toLowerCase());
63
+ }
64
+ }
65
+
66
+ // Build all reverse maps
67
+ // Priority order: commands first (highest priority for parsing), then logical, events, values
68
+ // This ensures 'en' (Spanish) → 'on' (command) rather than 'in' (modifier)
69
+ if (dictionary.commands) {
70
+ buildReverseMap(dictionary.commands, reverseCommands, true);
71
+ }
72
+ if (dictionary.logical) {
73
+ buildReverseMap(dictionary.logical, reverseLogical, true);
74
+ }
75
+ if (dictionary.events) {
76
+ buildReverseMap(dictionary.events, reverseEvents);
77
+ }
78
+ if (dictionary.values) {
79
+ buildReverseMap(dictionary.values, reverseValues);
80
+ }
81
+ if (dictionary.temporal) {
82
+ buildReverseMap(dictionary.temporal, reverseTemporal);
83
+ }
84
+ // Modifiers last (lowest priority) - they often conflict with commands
85
+ if (dictionary.modifiers) {
86
+ buildReverseMap(dictionary.modifiers, reverseModifiers);
87
+ }
88
+ if (dictionary.attributes) {
89
+ buildReverseMap(dictionary.attributes, reverseAttributes);
90
+ }
91
+ if (dictionary.expressions) {
92
+ buildReverseMap(dictionary.expressions, reverseExpressions);
93
+ }
94
+
95
+ // Collect all locale commands and keywords for completions
96
+ const localeCommands = new Set(reverseCommands.keys());
97
+ const allLocaleKeywords = new Set(reverseAll.keys());
98
+
99
+ // Add English keywords/commands if fallback is enabled
100
+ if (allowEnglishFallback) {
101
+ for (const cmd of ENGLISH_COMMANDS) {
102
+ localeCommands.add(cmd);
103
+ }
104
+ for (const kw of ENGLISH_KEYWORDS) {
105
+ allLocaleKeywords.add(kw);
106
+ }
107
+ for (const kw of UNIVERSAL_ENGLISH_KEYWORDS) {
108
+ allLocaleKeywords.add(kw);
109
+ }
110
+ }
111
+
112
+ return {
113
+ locale,
114
+
115
+ resolve(token: string): string | undefined {
116
+ const normalized = token.toLowerCase();
117
+
118
+ // Check if it's a locale keyword
119
+ const english = reverseAll.get(normalized);
120
+ if (english !== undefined) {
121
+ return english;
122
+ }
123
+
124
+ // If English fallback is enabled, check if it's already English
125
+ if (allowEnglishFallback) {
126
+ if (ENGLISH_COMMANDS.has(normalized) || ENGLISH_KEYWORDS.has(normalized)) {
127
+ return normalized;
128
+ }
129
+ // Universal keywords (DOM events, etc.) pass through
130
+ if (UNIVERSAL_ENGLISH_KEYWORDS.has(normalized)) {
131
+ return normalized;
132
+ }
133
+ }
134
+
135
+ return undefined;
136
+ },
137
+
138
+ isCommand(token: string): boolean {
139
+ const normalized = token.toLowerCase();
140
+
141
+ // Check locale commands
142
+ if (reverseCommands.has(normalized)) {
143
+ return true;
144
+ }
145
+
146
+ // Check English commands if fallback enabled
147
+ if (allowEnglishFallback && ENGLISH_COMMANDS.has(normalized)) {
148
+ return true;
149
+ }
150
+
151
+ return false;
152
+ },
153
+
154
+ isKeyword(token: string): boolean {
155
+ const normalized = token.toLowerCase();
156
+
157
+ // Check locale keywords (modifiers, logical, temporal, values)
158
+ if (
159
+ reverseModifiers.has(normalized) ||
160
+ reverseLogical.has(normalized) ||
161
+ reverseTemporal.has(normalized) ||
162
+ reverseValues.has(normalized)
163
+ ) {
164
+ return true;
165
+ }
166
+
167
+ // Check English keywords if fallback enabled
168
+ if (allowEnglishFallback && ENGLISH_KEYWORDS.has(normalized)) {
169
+ return true;
170
+ }
171
+
172
+ return false;
173
+ },
174
+
175
+ isEvent(token: string): boolean {
176
+ const normalized = token.toLowerCase();
177
+
178
+ // Check locale events
179
+ if (reverseEvents.has(normalized)) {
180
+ return true;
181
+ }
182
+
183
+ // Universal English events always accepted
184
+ if (UNIVERSAL_ENGLISH_KEYWORDS.has(normalized)) {
185
+ return true;
186
+ }
187
+
188
+ return false;
189
+ },
190
+
191
+ isModifier(token: string): boolean {
192
+ const normalized = token.toLowerCase();
193
+ return (
194
+ reverseModifiers.has(normalized) ||
195
+ (allowEnglishFallback && ENGLISH_MODIFIERS.has(normalized))
196
+ );
197
+ },
198
+
199
+ isLogical(token: string): boolean {
200
+ const normalized = token.toLowerCase();
201
+ return (
202
+ reverseLogical.has(normalized) ||
203
+ (allowEnglishFallback && ENGLISH_LOGICAL_KEYWORDS.has(normalized))
204
+ );
205
+ },
206
+
207
+ isValue(token: string): boolean {
208
+ const normalized = token.toLowerCase();
209
+ return (
210
+ reverseValues.has(normalized) ||
211
+ (allowEnglishFallback && ENGLISH_VALUE_KEYWORDS.has(normalized))
212
+ );
213
+ },
214
+
215
+ isExpression(token: string): boolean {
216
+ const normalized = token.toLowerCase();
217
+ return (
218
+ reverseExpressions.has(normalized) ||
219
+ (allowEnglishFallback && ENGLISH_EXPRESSION_KEYWORDS.has(normalized))
220
+ );
221
+ },
222
+
223
+ getCommands(): string[] {
224
+ return Array.from(localeCommands);
225
+ },
226
+
227
+ getKeywords(): string[] {
228
+ return Array.from(allLocaleKeywords);
229
+ },
230
+
231
+ toLocale(englishKeyword: string): string | undefined {
232
+ return forwardAll.get(englishKeyword.toLowerCase());
233
+ },
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Creates a default English-only keyword provider.
239
+ * This is used when no locale is specified.
240
+ */
241
+ export function createEnglishProvider(): KeywordProvider {
242
+ return {
243
+ locale: 'en',
244
+
245
+ resolve(token: string): string | undefined {
246
+ const normalized = token.toLowerCase();
247
+ if (
248
+ ENGLISH_COMMANDS.has(normalized) ||
249
+ ENGLISH_KEYWORDS.has(normalized) ||
250
+ UNIVERSAL_ENGLISH_KEYWORDS.has(normalized)
251
+ ) {
252
+ return normalized;
253
+ }
254
+ return undefined;
255
+ },
256
+
257
+ isCommand(token: string): boolean {
258
+ return ENGLISH_COMMANDS.has(token.toLowerCase());
259
+ },
260
+
261
+ isKeyword(token: string): boolean {
262
+ return ENGLISH_KEYWORDS.has(token.toLowerCase());
263
+ },
264
+
265
+ isEvent(token: string): boolean {
266
+ return UNIVERSAL_ENGLISH_KEYWORDS.has(token.toLowerCase());
267
+ },
268
+
269
+ isModifier(token: string): boolean {
270
+ return ENGLISH_MODIFIERS.has(token.toLowerCase());
271
+ },
272
+
273
+ isLogical(token: string): boolean {
274
+ return ENGLISH_LOGICAL_KEYWORDS.has(token.toLowerCase());
275
+ },
276
+
277
+ isValue(token: string): boolean {
278
+ return ENGLISH_VALUE_KEYWORDS.has(token.toLowerCase());
279
+ },
280
+
281
+ isExpression(token: string): boolean {
282
+ return ENGLISH_EXPRESSION_KEYWORDS.has(token.toLowerCase());
283
+ },
284
+
285
+ getCommands(): string[] {
286
+ return Array.from(ENGLISH_COMMANDS);
287
+ },
288
+
289
+ getKeywords(): string[] {
290
+ return Array.from(ENGLISH_KEYWORDS);
291
+ },
292
+
293
+ toLocale(_englishKeyword: string): string | undefined {
294
+ // English provider returns the keyword as-is
295
+ return _englishKeyword.toLowerCase();
296
+ },
297
+ };
298
+ }
299
+
300
+ // Re-export the English keyword sets from constants for use in core
301
+ export {
302
+ ENGLISH_COMMANDS,
303
+ ENGLISH_KEYWORDS,
304
+ ENGLISH_MODIFIERS,
305
+ ENGLISH_LOGICAL_KEYWORDS,
306
+ ENGLISH_VALUE_KEYWORDS,
307
+ ENGLISH_EXPRESSION_KEYWORDS,
308
+ UNIVERSAL_ENGLISH_KEYWORDS,
309
+ } from '../constants';
@@ -0,0 +1,36 @@
1
+ // packages/i18n/src/parser/de.ts
2
+
3
+ import { de } from '../dictionaries/de';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * German keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in German:
11
+ * - `bei klick umschalten .active` → parses as `on click toggle .active`
12
+ * - `wenn wahr dann protokollieren "hallo"` → parses as `if true then log "hello"`
13
+ *
14
+ * English keywords are also accepted (mixed mode), so:
15
+ * - `bei click umschalten .active` also works (German `bei` + English `click`)
16
+ *
17
+ * German is a useful test case because:
18
+ * - Compound words (Zusammensetzungen) are common
19
+ * - Different word order in clauses (V2 in main, V-final in subordinate)
20
+ * - Case system (nominative, accusative, dative, genitive)
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { deKeywords } from '@lokascript/i18n/parser/de';
25
+ * import { Parser } from '@lokascript/core';
26
+ *
27
+ * const parser = new Parser({ keywords: deKeywords });
28
+ * parser.parse('bei klick umschalten .active');
29
+ * ```
30
+ */
31
+ export const deKeywords: KeywordProvider = createKeywordProvider(de, 'de', {
32
+ allowEnglishFallback: true,
33
+ });
34
+
35
+ // Re-export for convenience
36
+ export { de as deDictionary } from '../dictionaries/de';
@@ -0,0 +1,31 @@
1
+ // packages/i18n/src/parser/es.ts
2
+
3
+ import { es } from '../dictionaries/es';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * Spanish keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in Spanish:
11
+ * - `en clic alternar .active` → parses as `on click toggle .active`
12
+ * - `si verdadero entonces registrar "hola"` → parses as `if true then log "hello"`
13
+ *
14
+ * English keywords are also accepted (mixed mode), so:
15
+ * - `en click alternar .active` also works (Spanish `en` + English `click`)
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { esKeywords } from '@lokascript/i18n/parser/es';
20
+ * import { Parser } from '@lokascript/core';
21
+ *
22
+ * const parser = new Parser({ keywords: esKeywords });
23
+ * parser.parse('en clic alternar .active');
24
+ * ```
25
+ */
26
+ export const esKeywords: KeywordProvider = createKeywordProvider(es, 'es', {
27
+ allowEnglishFallback: true,
28
+ });
29
+
30
+ // Re-export for convenience
31
+ export { es as esDictionary } from '../dictionaries/es';
@@ -0,0 +1,31 @@
1
+ // packages/i18n/src/parser/fr.ts
2
+
3
+ import { fr } from '../dictionaries/fr';
4
+ import { createKeywordProvider } from './create-provider';
5
+ import type { KeywordProvider } from './types';
6
+
7
+ /**
8
+ * French keyword provider for the hyperscript parser.
9
+ *
10
+ * Enables parsing hyperscript written in French:
11
+ * - `sur clic basculer .active` → parses as `on click toggle .active`
12
+ * - `si vrai alors enregistrer "bonjour"` → parses as `if true then log "hello"`
13
+ *
14
+ * English keywords are also accepted (mixed mode), so:
15
+ * - `sur click basculer .active` also works (French `sur` + English `click`)
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { frKeywords } from '@lokascript/i18n/parser/fr';
20
+ * import { Parser } from '@lokascript/core';
21
+ *
22
+ * const parser = new Parser({ keywords: frKeywords });
23
+ * parser.parse('sur clic basculer .active');
24
+ * ```
25
+ */
26
+ export const frKeywords: KeywordProvider = createKeywordProvider(fr, 'fr', {
27
+ allowEnglishFallback: true,
28
+ });
29
+
30
+ // Re-export for convenience
31
+ export { fr as frDictionary } from '../dictionaries/fr';