@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,393 @@
1
+ // packages/i18n/src/pluralization.ts
2
+
3
+ /**
4
+ * Pluralization rules for different languages
5
+ */
6
+ export type PluralRule = (n: number) => 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
7
+
8
+ /**
9
+ * Pluralization rules based on Unicode CLDR
10
+ */
11
+ export const pluralRules: Record<string, PluralRule> = {
12
+ // English, German, Dutch, Swedish, etc.
13
+ en: n => (n === 1 ? 'one' : 'other'),
14
+ de: n => (n === 1 ? 'one' : 'other'),
15
+ nl: n => (n === 1 ? 'one' : 'other'),
16
+ sv: n => (n === 1 ? 'one' : 'other'),
17
+
18
+ // Spanish, Italian, Portuguese
19
+ es: n => (n === 1 ? 'one' : 'other'),
20
+ it: n => (n === 1 ? 'one' : 'other'),
21
+ pt: n => (n === 1 ? 'one' : 'other'),
22
+
23
+ // French
24
+ fr: n => (n >= 0 && n < 2 ? 'one' : 'other'),
25
+
26
+ // Russian, Polish
27
+ ru: n => {
28
+ if (n % 10 === 1 && n % 100 !== 11) return 'one';
29
+ if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 'few';
30
+ return 'many';
31
+ },
32
+ pl: n => {
33
+ if (n === 1) return 'one';
34
+ if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 'few';
35
+ return 'many';
36
+ },
37
+
38
+ // Arabic
39
+ ar: n => {
40
+ if (n === 0) return 'zero';
41
+ if (n === 1) return 'one';
42
+ if (n === 2) return 'two';
43
+ if (n % 100 >= 3 && n % 100 <= 10) return 'few';
44
+ if (n % 100 >= 11 && n % 100 <= 99) return 'many';
45
+ return 'other';
46
+ },
47
+
48
+ // Chinese, Japanese, Korean (no pluralization)
49
+ zh: () => 'other',
50
+ ja: () => 'other',
51
+ ko: () => 'other',
52
+
53
+ // Turkish
54
+ tr: n => (n === 1 ? 'one' : 'other'),
55
+
56
+ // Indonesian (no pluralization, but has different forms)
57
+ id: () => 'other',
58
+
59
+ // Quechua (simple plural rule)
60
+ qu: n => (n === 1 ? 'one' : 'other'),
61
+
62
+ // Swahili
63
+ sw: n => (n === 1 ? 'one' : 'other'),
64
+
65
+ // Czech
66
+ cs: n => {
67
+ if (n === 1) return 'one';
68
+ if (n >= 2 && n <= 4) return 'few';
69
+ return 'other';
70
+ },
71
+
72
+ // Lithuanian
73
+ lt: n => {
74
+ if (n % 10 === 1 && n % 100 !== 11) return 'one';
75
+ if (n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) return 'few';
76
+ return 'other';
77
+ },
78
+ };
79
+
80
+ /**
81
+ * Plural forms for hyperscript keywords
82
+ */
83
+ export interface PluralForms {
84
+ zero?: string;
85
+ one?: string;
86
+ two?: string;
87
+ few?: string;
88
+ many?: string;
89
+ other: string;
90
+ }
91
+
92
+ /**
93
+ * Get the appropriate plural form for a locale and count
94
+ */
95
+ export function getPlural(locale: string, count: number, forms: PluralForms): string {
96
+ const rule = pluralRules[locale] || pluralRules.en;
97
+ const key = rule(count);
98
+
99
+ return forms[key] || forms.other;
100
+ }
101
+
102
+ /**
103
+ * Plural-aware translation for time expressions
104
+ */
105
+ export const pluralTimeExpressions: Record<string, Record<string, PluralForms>> = {
106
+ en: {
107
+ second: {
108
+ one: 'second',
109
+ other: 'seconds',
110
+ },
111
+ minute: {
112
+ one: 'minute',
113
+ other: 'minutes',
114
+ },
115
+ hour: {
116
+ one: 'hour',
117
+ other: 'hours',
118
+ },
119
+ day: {
120
+ one: 'day',
121
+ other: 'days',
122
+ },
123
+ },
124
+
125
+ es: {
126
+ second: {
127
+ one: 'segundo',
128
+ other: 'segundos',
129
+ },
130
+ minute: {
131
+ one: 'minuto',
132
+ other: 'minutos',
133
+ },
134
+ hour: {
135
+ one: 'hora',
136
+ other: 'horas',
137
+ },
138
+ day: {
139
+ one: 'día',
140
+ other: 'días',
141
+ },
142
+ },
143
+
144
+ fr: {
145
+ second: {
146
+ one: 'seconde',
147
+ other: 'secondes',
148
+ },
149
+ minute: {
150
+ one: 'minute',
151
+ other: 'minutes',
152
+ },
153
+ hour: {
154
+ one: 'heure',
155
+ other: 'heures',
156
+ },
157
+ day: {
158
+ one: 'jour',
159
+ other: 'jours',
160
+ },
161
+ },
162
+
163
+ de: {
164
+ second: {
165
+ one: 'Sekunde',
166
+ other: 'Sekunden',
167
+ },
168
+ minute: {
169
+ one: 'Minute',
170
+ other: 'Minuten',
171
+ },
172
+ hour: {
173
+ one: 'Stunde',
174
+ other: 'Stunden',
175
+ },
176
+ day: {
177
+ one: 'Tag',
178
+ other: 'Tage',
179
+ },
180
+ },
181
+
182
+ ru: {
183
+ second: {
184
+ one: 'секунда',
185
+ few: 'секунды',
186
+ many: 'секунд',
187
+ other: 'секунд',
188
+ },
189
+ minute: {
190
+ one: 'минута',
191
+ few: 'минуты',
192
+ many: 'минут',
193
+ other: 'минут',
194
+ },
195
+ hour: {
196
+ one: 'час',
197
+ few: 'часа',
198
+ many: 'часов',
199
+ other: 'часов',
200
+ },
201
+ day: {
202
+ one: 'день',
203
+ few: 'дня',
204
+ many: 'дней',
205
+ other: 'дней',
206
+ },
207
+ },
208
+
209
+ ar: {
210
+ second: {
211
+ zero: 'ثوانِ',
212
+ one: 'ثانية',
213
+ two: 'ثانيتان',
214
+ few: 'ثوانِ',
215
+ many: 'ثانية',
216
+ other: 'ثانية',
217
+ },
218
+ minute: {
219
+ zero: 'دقائق',
220
+ one: 'دقيقة',
221
+ two: 'دقيقتان',
222
+ few: 'دقائق',
223
+ many: 'دقيقة',
224
+ other: 'دقيقة',
225
+ },
226
+ hour: {
227
+ zero: 'ساعات',
228
+ one: 'ساعة',
229
+ two: 'ساعتان',
230
+ few: 'ساعات',
231
+ many: 'ساعة',
232
+ other: 'ساعة',
233
+ },
234
+ day: {
235
+ zero: 'أيام',
236
+ one: 'يوم',
237
+ two: 'يومان',
238
+ few: 'أيام',
239
+ many: 'يوماً',
240
+ other: 'يوم',
241
+ },
242
+ },
243
+
244
+ tr: {
245
+ second: {
246
+ one: 'saniye',
247
+ other: 'saniye',
248
+ },
249
+ minute: {
250
+ one: 'dakika',
251
+ other: 'dakika',
252
+ },
253
+ hour: {
254
+ one: 'saat',
255
+ other: 'saat',
256
+ },
257
+ day: {
258
+ one: 'gün',
259
+ other: 'gün',
260
+ },
261
+ },
262
+
263
+ id: {
264
+ second: {
265
+ other: 'detik',
266
+ },
267
+ minute: {
268
+ other: 'menit',
269
+ },
270
+ hour: {
271
+ other: 'jam',
272
+ },
273
+ day: {
274
+ other: 'hari',
275
+ },
276
+ },
277
+
278
+ qu: {
279
+ second: {
280
+ one: 'sikundu',
281
+ other: 'sikundukuna',
282
+ },
283
+ minute: {
284
+ one: 'minutu',
285
+ other: 'minutukuna',
286
+ },
287
+ hour: {
288
+ one: 'hora',
289
+ other: 'horakuna',
290
+ },
291
+ day: {
292
+ one: 'p_unchaw',
293
+ other: 'p_unchawkuna',
294
+ },
295
+ },
296
+
297
+ sw: {
298
+ second: {
299
+ one: 'sekunde',
300
+ other: 'sekunde',
301
+ },
302
+ minute: {
303
+ one: 'dakika',
304
+ other: 'dakika',
305
+ },
306
+ hour: {
307
+ one: 'saa',
308
+ other: 'masaa',
309
+ },
310
+ day: {
311
+ one: 'siku',
312
+ other: 'siku',
313
+ },
314
+ },
315
+ };
316
+
317
+ /**
318
+ * Enhanced translator with pluralization support
319
+ */
320
+ export class PluralAwareTranslator {
321
+ /**
322
+ * Translate time expressions with proper pluralization
323
+ */
324
+ static translateTimeExpression(value: number, unit: string, locale: string): string {
325
+ const expressions = pluralTimeExpressions[locale];
326
+ if (!expressions || !expressions[unit]) {
327
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
328
+ }
329
+
330
+ const pluralForm = getPlural(locale, value, expressions[unit]);
331
+ return `${value} ${pluralForm}`;
332
+ }
333
+
334
+ /**
335
+ * Parse and translate plural-aware hyperscript time expressions
336
+ */
337
+ static translateHyperscriptTime(text: string, locale: string): string {
338
+ // Match patterns like "wait 5 seconds", "in 1 minute", etc.
339
+ // Note: Longer alternatives must come first (milliseconds before millisecond, etc.)
340
+ return text.replace(
341
+ /(\d+)\s+(milliseconds|millisecond|seconds|second|minutes|minute|hours|hour|days|day|ms)/gi,
342
+ (_match, value, unit) => {
343
+ const numValue = parseInt(value);
344
+ const lowerUnit = unit.toLowerCase();
345
+
346
+ // Handle milliseconds/ms specially before normalizing
347
+ if (lowerUnit === 'ms' || lowerUnit === 'millisecond' || lowerUnit === 'milliseconds') {
348
+ return this.translateTimeExpression(numValue, 'millisecond', locale);
349
+ }
350
+
351
+ // Normalize by removing trailing 's' for other units
352
+ const normalizedUnit = lowerUnit.replace(/s$/, '');
353
+ return this.translateTimeExpression(numValue, normalizedUnit, locale);
354
+ }
355
+ );
356
+ }
357
+
358
+ /**
359
+ * Get ordinal numbers (1st, 2nd, 3rd) in different languages
360
+ */
361
+ static getOrdinal(n: number, locale: string): string {
362
+ const ordinals: Record<string, (n: number) => string> = {
363
+ en: n => {
364
+ if (n % 100 >= 11 && n % 100 <= 13) return `${n}th`;
365
+ switch (n % 10) {
366
+ case 1:
367
+ return `${n}st`;
368
+ case 2:
369
+ return `${n}nd`;
370
+ case 3:
371
+ return `${n}rd`;
372
+ default:
373
+ return `${n}th`;
374
+ }
375
+ },
376
+ es: n => `${n}º`,
377
+ fr: n => (n === 1 ? `${n}er` : `${n}e`),
378
+ de: n => `${n}.`,
379
+ ru: n => `${n}-й`,
380
+ ar: n => `${n}`,
381
+ zh: n => `第${n}`,
382
+ ja: n => `${n}番目`,
383
+ ko: n => `${n}번째`,
384
+ tr: n => `${n}.`,
385
+ id: n => `ke-${n}`,
386
+ qu: n => `${n}-ñiqin`,
387
+ sw: n => `wa ${n}`,
388
+ };
389
+
390
+ const ordinalFn = ordinals[locale] || ordinals.en;
391
+ return ordinalFn(n);
392
+ }
393
+ }