@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.
- package/README.md +286 -0
- package/dist/browser.cjs +7669 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +50 -0
- package/dist/browser.d.ts +50 -0
- package/dist/browser.js +7592 -0
- package/dist/browser.js.map +1 -0
- package/dist/hyperfixi-i18n.min.js +2 -0
- package/dist/hyperfixi-i18n.min.js.map +1 -0
- package/dist/hyperfixi-i18n.mjs +8558 -0
- package/dist/hyperfixi-i18n.mjs.map +1 -0
- package/dist/index.cjs +14205 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +947 -0
- package/dist/index.d.ts +947 -0
- package/dist/index.js +14095 -0
- package/dist/index.js.map +1 -0
- package/dist/transformer-Ckask-yw.d.cts +1041 -0
- package/dist/transformer-Ckask-yw.d.ts +1041 -0
- package/package.json +84 -0
- package/src/browser.ts +122 -0
- package/src/compatibility/browser-tests/grammar-demo.spec.ts +169 -0
- package/src/constants.ts +366 -0
- package/src/dictionaries/ar.ts +233 -0
- package/src/dictionaries/bn.ts +156 -0
- package/src/dictionaries/de.ts +233 -0
- package/src/dictionaries/derive.ts +515 -0
- package/src/dictionaries/en.ts +237 -0
- package/src/dictionaries/es.ts +233 -0
- package/src/dictionaries/fr.ts +233 -0
- package/src/dictionaries/hi.ts +270 -0
- package/src/dictionaries/id.ts +233 -0
- package/src/dictionaries/index.ts +238 -0
- package/src/dictionaries/it.ts +233 -0
- package/src/dictionaries/ja.ts +233 -0
- package/src/dictionaries/ko.ts +233 -0
- package/src/dictionaries/ms.ts +276 -0
- package/src/dictionaries/pl.ts +239 -0
- package/src/dictionaries/pt.ts +237 -0
- package/src/dictionaries/qu.ts +233 -0
- package/src/dictionaries/ru.ts +270 -0
- package/src/dictionaries/sw.ts +233 -0
- package/src/dictionaries/th.ts +156 -0
- package/src/dictionaries/tl.ts +276 -0
- package/src/dictionaries/tr.ts +233 -0
- package/src/dictionaries/uk.ts +270 -0
- package/src/dictionaries/vi.ts +210 -0
- package/src/dictionaries/zh.ts +233 -0
- package/src/enhanced-i18n.test.ts +454 -0
- package/src/enhanced-i18n.ts +713 -0
- package/src/examples/new-languages.ts +326 -0
- package/src/formatting.test.ts +213 -0
- package/src/formatting.ts +416 -0
- package/src/grammar/direct-mappings.ts +353 -0
- package/src/grammar/grammar.test.ts +1053 -0
- package/src/grammar/index.ts +59 -0
- package/src/grammar/profiles/index.ts +860 -0
- package/src/grammar/transformer.ts +1318 -0
- package/src/grammar/types.ts +630 -0
- package/src/index.ts +202 -0
- package/src/new-languages.test.ts +389 -0
- package/src/parser/analyze-conflicts.test.ts +229 -0
- package/src/parser/ar.ts +40 -0
- package/src/parser/create-provider.ts +309 -0
- package/src/parser/de.ts +36 -0
- package/src/parser/es.ts +31 -0
- package/src/parser/fr.ts +31 -0
- package/src/parser/id.ts +34 -0
- package/src/parser/index.ts +50 -0
- package/src/parser/ja.ts +36 -0
- package/src/parser/ko.ts +37 -0
- package/src/parser/locale-manager.test.ts +198 -0
- package/src/parser/locale-manager.ts +197 -0
- package/src/parser/parser-integration.test.ts +439 -0
- package/src/parser/pt.ts +37 -0
- package/src/parser/qu.ts +37 -0
- package/src/parser/sw.ts +37 -0
- package/src/parser/tr.ts +38 -0
- package/src/parser/types.ts +113 -0
- package/src/parser/zh.ts +38 -0
- package/src/plugins/vite.ts +224 -0
- package/src/plugins/webpack.ts +124 -0
- package/src/pluralization.test.ts +197 -0
- package/src/pluralization.ts +393 -0
- package/src/runtime.ts +441 -0
- package/src/ssr-integration.ts +225 -0
- package/src/test-setup.ts +195 -0
- package/src/translation-validation.test.ts +171 -0
- package/src/translator.test.ts +252 -0
- package/src/translator.ts +297 -0
- package/src/types.ts +209 -0
- package/src/utils/locale.ts +190 -0
- package/src/utils/tokenizer-adapter.ts +469 -0
- package/src/utils/tokenizer.ts +19 -0
- package/src/validators/index.ts +174 -0
- 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
|
+
}
|