@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
package/src/parser/id.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// packages/i18n/src/parser/id.ts
|
|
2
|
+
|
|
3
|
+
import { id } from '../dictionaries/id';
|
|
4
|
+
import { createKeywordProvider } from './create-provider';
|
|
5
|
+
import type { KeywordProvider } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Indonesian (Bahasa Indonesia) keyword provider for the hyperscript parser.
|
|
9
|
+
*
|
|
10
|
+
* Enables parsing hyperscript written in Indonesian:
|
|
11
|
+
* - `pada klik ganti .active` → parses as `on click toggle .active`
|
|
12
|
+
* - `jika benar lalu log "Halo"` → parses as `if true then log "Hello"`
|
|
13
|
+
*
|
|
14
|
+
* Indonesian is an SVO language with:
|
|
15
|
+
* - Agglutinative morphology (prefixes/suffixes)
|
|
16
|
+
* - No grammatical gender
|
|
17
|
+
* - Prepositions (like English)
|
|
18
|
+
* - Relatively simple syntax
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { idKeywords } from '@lokascript/i18n/parser/id';
|
|
23
|
+
* import { Parser } from '@lokascript/core';
|
|
24
|
+
*
|
|
25
|
+
* const parser = new Parser({ keywords: idKeywords });
|
|
26
|
+
* parser.parse('pada klik ganti .active');
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const idKeywords: KeywordProvider = createKeywordProvider(id, 'id', {
|
|
30
|
+
allowEnglishFallback: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Re-export for convenience
|
|
34
|
+
export { id as idDictionary } from '../dictionaries/id';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// packages/i18n/src/parser/index.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parser integration for multilingual hyperscript.
|
|
5
|
+
*
|
|
6
|
+
* This module provides KeywordProvider implementations that enable
|
|
7
|
+
* the hyperscript parser to understand non-English keywords.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Spanish
|
|
12
|
+
* import { esKeywords } from '@lokascript/i18n/parser';
|
|
13
|
+
* const parser = new Parser({ keywords: esKeywords });
|
|
14
|
+
* parser.parse('en clic alternar .active');
|
|
15
|
+
*
|
|
16
|
+
* // Japanese
|
|
17
|
+
* import { jaKeywords } from '@lokascript/i18n/parser';
|
|
18
|
+
* const parser = new Parser({ keywords: jaKeywords });
|
|
19
|
+
* parser.parse('クリック で 切り替え .active');
|
|
20
|
+
*
|
|
21
|
+
* // Custom locale
|
|
22
|
+
* import { createKeywordProvider } from '@lokascript/i18n/parser';
|
|
23
|
+
* import { fr } from '@lokascript/i18n/dictionaries';
|
|
24
|
+
* const frKeywords = createKeywordProvider(fr, 'fr');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Types
|
|
29
|
+
export type { KeywordProvider, KeywordProviderOptions } from './types';
|
|
30
|
+
|
|
31
|
+
// Factory
|
|
32
|
+
export { createKeywordProvider, createEnglishProvider } from './create-provider';
|
|
33
|
+
export { ENGLISH_COMMANDS, ENGLISH_KEYWORDS, UNIVERSAL_ENGLISH_KEYWORDS } from './create-provider';
|
|
34
|
+
|
|
35
|
+
// Locale packs
|
|
36
|
+
export { esKeywords, esDictionary } from './es';
|
|
37
|
+
export { jaKeywords, jaDictionary } from './ja';
|
|
38
|
+
export { frKeywords, frDictionary } from './fr';
|
|
39
|
+
export { deKeywords, deDictionary } from './de';
|
|
40
|
+
export { arKeywords, arDictionary } from './ar';
|
|
41
|
+
export { koKeywords, koDictionary } from './ko';
|
|
42
|
+
export { zhKeywords, zhDictionary } from './zh';
|
|
43
|
+
export { trKeywords, trDictionary } from './tr';
|
|
44
|
+
export { idKeywords, idDictionary } from './id';
|
|
45
|
+
export { quKeywords, quDictionary } from './qu';
|
|
46
|
+
export { swKeywords, swDictionary } from './sw';
|
|
47
|
+
export { ptKeywords, ptDictionary } from './pt';
|
|
48
|
+
|
|
49
|
+
// Locale management
|
|
50
|
+
export { LocaleManager, detectBrowserLocale } from './locale-manager';
|
package/src/parser/ja.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// packages/i18n/src/parser/ja.ts
|
|
2
|
+
|
|
3
|
+
import { ja } from '../dictionaries/ja';
|
|
4
|
+
import { createKeywordProvider } from './create-provider';
|
|
5
|
+
import type { KeywordProvider } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Japanese keyword provider for the hyperscript parser.
|
|
9
|
+
*
|
|
10
|
+
* Enables parsing hyperscript written in Japanese:
|
|
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 (English `click` + Japanese `で`, `切り替え`)
|
|
16
|
+
*
|
|
17
|
+
* Japanese is a useful test case because:
|
|
18
|
+
* - SOV word order (subject-object-verb) vs English SVO
|
|
19
|
+
* - No articles (a, an, the)
|
|
20
|
+
* - Unicode characters (tests tokenizer robustness)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { jaKeywords } from '@lokascript/i18n/parser/ja';
|
|
25
|
+
* import { Parser } from '@lokascript/core';
|
|
26
|
+
*
|
|
27
|
+
* const parser = new Parser({ keywords: jaKeywords });
|
|
28
|
+
* parser.parse('クリック で 切り替え .active');
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const jaKeywords: KeywordProvider = createKeywordProvider(ja, 'ja', {
|
|
32
|
+
allowEnglishFallback: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Re-export for convenience
|
|
36
|
+
export { ja as jaDictionary } from '../dictionaries/ja';
|
package/src/parser/ko.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// packages/i18n/src/parser/ko.ts
|
|
2
|
+
|
|
3
|
+
import { ko } from '../dictionaries/ko';
|
|
4
|
+
import { createKeywordProvider } from './create-provider';
|
|
5
|
+
import type { KeywordProvider } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Korean keyword provider for the hyperscript parser.
|
|
9
|
+
*
|
|
10
|
+
* Enables parsing hyperscript written in Korean:
|
|
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 (Korean `에` + English `click`)
|
|
16
|
+
*
|
|
17
|
+
* Korean is a useful test case because:
|
|
18
|
+
* - SOV word order (Subject-Object-Verb)
|
|
19
|
+
* - Hangul script (syllabic blocks)
|
|
20
|
+
* - Agglutinative morphology with particles
|
|
21
|
+
* - Tests parser's Unicode handling with Korean characters
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { koKeywords } from '@lokascript/i18n/parser/ko';
|
|
26
|
+
* import { Parser } from '@lokascript/core';
|
|
27
|
+
*
|
|
28
|
+
* const parser = new Parser({ keywords: koKeywords });
|
|
29
|
+
* parser.parse('에 클릭 토글 .active');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const koKeywords: KeywordProvider = createKeywordProvider(ko, 'ko', {
|
|
33
|
+
allowEnglishFallback: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Re-export for convenience
|
|
37
|
+
export { ko as koDictionary } from '../dictionaries/ko';
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// packages/i18n/src/parser/locale-manager.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
+
import { LocaleManager, detectBrowserLocale } from './locale-manager';
|
|
5
|
+
import { esKeywords } from './es';
|
|
6
|
+
import { jaKeywords } from './ja';
|
|
7
|
+
import { frKeywords } from './fr';
|
|
8
|
+
import { deKeywords } from './de';
|
|
9
|
+
import { arKeywords } from './ar';
|
|
10
|
+
|
|
11
|
+
describe('LocaleManager', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
LocaleManager.reset();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('register / unregister', () => {
|
|
17
|
+
it('should register a locale provider', () => {
|
|
18
|
+
LocaleManager.register('es', esKeywords);
|
|
19
|
+
expect(LocaleManager.has('es')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should unregister a locale provider', () => {
|
|
23
|
+
LocaleManager.register('es', esKeywords);
|
|
24
|
+
LocaleManager.unregister('es');
|
|
25
|
+
expect(LocaleManager.has('es')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle case-insensitive locale codes', () => {
|
|
29
|
+
LocaleManager.register('ES', esKeywords);
|
|
30
|
+
expect(LocaleManager.has('es')).toBe(true);
|
|
31
|
+
expect(LocaleManager.has('ES')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('setDefault / getDefault', () => {
|
|
36
|
+
it('should have en as default initially', () => {
|
|
37
|
+
expect(LocaleManager.getDefault()).toBe('en');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should set default locale', () => {
|
|
41
|
+
LocaleManager.register('es', esKeywords);
|
|
42
|
+
LocaleManager.setDefault('es');
|
|
43
|
+
expect(LocaleManager.getDefault()).toBe('es');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should throw when setting unregistered locale as default', () => {
|
|
47
|
+
expect(() => LocaleManager.setDefault('xyz')).toThrow("Locale 'xyz' is not registered");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should allow setting en as default without registration', () => {
|
|
51
|
+
LocaleManager.setDefault('en');
|
|
52
|
+
expect(LocaleManager.getDefault()).toBe('en');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('get', () => {
|
|
57
|
+
it('should get English provider by default', () => {
|
|
58
|
+
const provider = LocaleManager.get();
|
|
59
|
+
expect(provider.locale).toBe('en');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should get registered locale provider', () => {
|
|
63
|
+
LocaleManager.register('es', esKeywords);
|
|
64
|
+
const provider = LocaleManager.get('es');
|
|
65
|
+
expect(provider.locale).toBe('es');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should get default locale when no argument provided', () => {
|
|
69
|
+
LocaleManager.register('ja', jaKeywords);
|
|
70
|
+
LocaleManager.setDefault('ja');
|
|
71
|
+
const provider = LocaleManager.get();
|
|
72
|
+
expect(provider.locale).toBe('ja');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw for unregistered locale', () => {
|
|
76
|
+
expect(() => LocaleManager.get('xyz')).toThrow("Locale 'xyz' is not registered");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('has', () => {
|
|
81
|
+
it('should always have en', () => {
|
|
82
|
+
expect(LocaleManager.has('en')).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return false for unregistered locale', () => {
|
|
86
|
+
expect(LocaleManager.has('xyz')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should return true for registered locale', () => {
|
|
90
|
+
LocaleManager.register('fr', frKeywords);
|
|
91
|
+
expect(LocaleManager.has('fr')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('getAvailable', () => {
|
|
96
|
+
it('should include en by default', () => {
|
|
97
|
+
expect(LocaleManager.getAvailable()).toContain('en');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should include registered locales', () => {
|
|
101
|
+
LocaleManager.register('es', esKeywords);
|
|
102
|
+
LocaleManager.register('ja', jaKeywords);
|
|
103
|
+
const available = LocaleManager.getAvailable();
|
|
104
|
+
expect(available).toContain('en');
|
|
105
|
+
expect(available).toContain('es');
|
|
106
|
+
expect(available).toContain('ja');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('registerAll', () => {
|
|
111
|
+
it('should register multiple locales at once', () => {
|
|
112
|
+
LocaleManager.registerAll({
|
|
113
|
+
es: esKeywords,
|
|
114
|
+
ja: jaKeywords,
|
|
115
|
+
fr: frKeywords,
|
|
116
|
+
de: deKeywords,
|
|
117
|
+
ar: arKeywords,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(LocaleManager.has('es')).toBe(true);
|
|
121
|
+
expect(LocaleManager.has('ja')).toBe(true);
|
|
122
|
+
expect(LocaleManager.has('fr')).toBe(true);
|
|
123
|
+
expect(LocaleManager.has('de')).toBe(true);
|
|
124
|
+
expect(LocaleManager.has('ar')).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('reset', () => {
|
|
129
|
+
it('should clear all registered locales and reset default', () => {
|
|
130
|
+
LocaleManager.register('es', esKeywords);
|
|
131
|
+
LocaleManager.setDefault('es');
|
|
132
|
+
|
|
133
|
+
LocaleManager.reset();
|
|
134
|
+
|
|
135
|
+
expect(LocaleManager.has('es')).toBe(false);
|
|
136
|
+
expect(LocaleManager.getDefault()).toBe('en');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('Integration: keyword resolution', () => {
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
LocaleManager.registerAll({
|
|
143
|
+
es: esKeywords,
|
|
144
|
+
ja: jaKeywords,
|
|
145
|
+
fr: frKeywords,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should resolve keywords for different locales', () => {
|
|
150
|
+
// Spanish
|
|
151
|
+
const es = LocaleManager.get('es');
|
|
152
|
+
expect(es.resolve('alternar')).toBe('toggle');
|
|
153
|
+
|
|
154
|
+
// Japanese
|
|
155
|
+
const ja = LocaleManager.get('ja');
|
|
156
|
+
expect(ja.resolve('切り替え')).toBe('toggle');
|
|
157
|
+
|
|
158
|
+
// French
|
|
159
|
+
const fr = LocaleManager.get('fr');
|
|
160
|
+
expect(fr.resolve('basculer')).toBe('toggle');
|
|
161
|
+
|
|
162
|
+
// English
|
|
163
|
+
const en = LocaleManager.get('en');
|
|
164
|
+
expect(en.resolve('toggle')).toBe('toggle');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should support switching default locale dynamically', () => {
|
|
168
|
+
// Start with English
|
|
169
|
+
expect(LocaleManager.get().resolve('toggle')).toBe('toggle');
|
|
170
|
+
|
|
171
|
+
// Switch to Spanish
|
|
172
|
+
LocaleManager.setDefault('es');
|
|
173
|
+
expect(LocaleManager.get().resolve('alternar')).toBe('toggle');
|
|
174
|
+
|
|
175
|
+
// Switch to Japanese
|
|
176
|
+
LocaleManager.setDefault('ja');
|
|
177
|
+
expect(LocaleManager.get().resolve('切り替え')).toBe('toggle');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('detectBrowserLocale', () => {
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
LocaleManager.reset();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return English provider when navigator is undefined', () => {
|
|
188
|
+
// In Node.js environment, navigator is undefined
|
|
189
|
+
const provider = detectBrowserLocale();
|
|
190
|
+
expect(provider.locale).toBe('en');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return English when no matching locale is registered', () => {
|
|
194
|
+
// No locales registered, should fall back to English
|
|
195
|
+
const provider = detectBrowserLocale();
|
|
196
|
+
expect(provider.locale).toBe('en');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// packages/i18n/src/parser/locale-manager.ts
|
|
2
|
+
|
|
3
|
+
import type { KeywordProvider } from './types';
|
|
4
|
+
import { createEnglishProvider } from './create-provider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* LocaleManager - Centralized locale configuration for hyperscript parsing.
|
|
8
|
+
*
|
|
9
|
+
* Provides utilities for:
|
|
10
|
+
* - Setting/getting the default locale
|
|
11
|
+
* - Creating locale-aware parsers
|
|
12
|
+
* - Runtime locale switching
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { LocaleManager } from '@lokascript/i18n/parser';
|
|
17
|
+
* import { esKeywords, frKeywords } from '@lokascript/i18n/parser';
|
|
18
|
+
*
|
|
19
|
+
* // Register available locales
|
|
20
|
+
* LocaleManager.register('es', esKeywords);
|
|
21
|
+
* LocaleManager.register('fr', frKeywords);
|
|
22
|
+
*
|
|
23
|
+
* // Set default locale
|
|
24
|
+
* LocaleManager.setDefault('es');
|
|
25
|
+
*
|
|
26
|
+
* // Get keyword provider for parsing
|
|
27
|
+
* const provider = LocaleManager.get(); // Returns esKeywords
|
|
28
|
+
* const frProvider = LocaleManager.get('fr'); // Returns frKeywords
|
|
29
|
+
*
|
|
30
|
+
* // Use with parser
|
|
31
|
+
* const parser = new Parser({ keywords: LocaleManager.get() });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class LocaleManager {
|
|
35
|
+
private static providers = new Map<string, KeywordProvider>();
|
|
36
|
+
private static defaultLocale: string = 'en';
|
|
37
|
+
private static englishProvider: KeywordProvider = createEnglishProvider();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register a locale provider.
|
|
41
|
+
*
|
|
42
|
+
* @param locale - The locale code (e.g., 'es', 'ja', 'fr')
|
|
43
|
+
* @param provider - The KeywordProvider for this locale
|
|
44
|
+
*/
|
|
45
|
+
static register(locale: string, provider: KeywordProvider): void {
|
|
46
|
+
LocaleManager.providers.set(locale.toLowerCase(), provider);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Unregister a locale provider.
|
|
51
|
+
*
|
|
52
|
+
* @param locale - The locale code to remove
|
|
53
|
+
*/
|
|
54
|
+
static unregister(locale: string): void {
|
|
55
|
+
LocaleManager.providers.delete(locale.toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set the default locale.
|
|
60
|
+
*
|
|
61
|
+
* @param locale - The locale code to use as default
|
|
62
|
+
* @throws Error if the locale is not registered
|
|
63
|
+
*/
|
|
64
|
+
static setDefault(locale: string): void {
|
|
65
|
+
const normalizedLocale = locale.toLowerCase();
|
|
66
|
+
if (normalizedLocale !== 'en' && !LocaleManager.providers.has(normalizedLocale)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Locale '${locale}' is not registered. ` +
|
|
69
|
+
`Available locales: ${LocaleManager.getAvailable().join(', ')}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
LocaleManager.defaultLocale = normalizedLocale;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the current default locale.
|
|
77
|
+
*
|
|
78
|
+
* @returns The default locale code
|
|
79
|
+
*/
|
|
80
|
+
static getDefault(): string {
|
|
81
|
+
return LocaleManager.defaultLocale;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get a keyword provider for a locale.
|
|
86
|
+
*
|
|
87
|
+
* @param locale - The locale code (optional, defaults to the default locale)
|
|
88
|
+
* @returns The KeywordProvider for the locale
|
|
89
|
+
* @throws Error if the locale is not registered
|
|
90
|
+
*/
|
|
91
|
+
static get(locale?: string): KeywordProvider {
|
|
92
|
+
const targetLocale = (locale || LocaleManager.defaultLocale).toLowerCase();
|
|
93
|
+
|
|
94
|
+
// English is always available
|
|
95
|
+
if (targetLocale === 'en') {
|
|
96
|
+
return LocaleManager.englishProvider;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const provider = LocaleManager.providers.get(targetLocale);
|
|
100
|
+
if (!provider) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Locale '${targetLocale}' is not registered. ` +
|
|
103
|
+
`Available locales: ${LocaleManager.getAvailable().join(', ')}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return provider;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a locale is registered.
|
|
112
|
+
*
|
|
113
|
+
* @param locale - The locale code to check
|
|
114
|
+
* @returns True if the locale is available
|
|
115
|
+
*/
|
|
116
|
+
static has(locale: string): boolean {
|
|
117
|
+
const normalizedLocale = locale.toLowerCase();
|
|
118
|
+
return normalizedLocale === 'en' || LocaleManager.providers.has(normalizedLocale);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all available locale codes.
|
|
123
|
+
*
|
|
124
|
+
* @returns Array of locale codes
|
|
125
|
+
*/
|
|
126
|
+
static getAvailable(): string[] {
|
|
127
|
+
return ['en', ...Array.from(LocaleManager.providers.keys())];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reset the LocaleManager to its initial state.
|
|
132
|
+
* Useful for testing.
|
|
133
|
+
*/
|
|
134
|
+
static reset(): void {
|
|
135
|
+
LocaleManager.providers.clear();
|
|
136
|
+
LocaleManager.defaultLocale = 'en';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Register all built-in locales at once.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* import { LocaleManager } from '@lokascript/i18n/parser';
|
|
145
|
+
* import { esKeywords, jaKeywords, frKeywords, deKeywords, arKeywords } from '@lokascript/i18n/parser';
|
|
146
|
+
*
|
|
147
|
+
* LocaleManager.registerAll({
|
|
148
|
+
* es: esKeywords,
|
|
149
|
+
* ja: jaKeywords,
|
|
150
|
+
* fr: frKeywords,
|
|
151
|
+
* de: deKeywords,
|
|
152
|
+
* ar: arKeywords,
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
static registerAll(providers: Record<string, KeywordProvider>): void {
|
|
157
|
+
for (const [locale, provider] of Object.entries(providers)) {
|
|
158
|
+
LocaleManager.register(locale, provider);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Quick utility to detect the browser's preferred locale and return
|
|
165
|
+
* a matching provider, falling back to English.
|
|
166
|
+
*
|
|
167
|
+
* @returns The KeywordProvider for the browser's locale or English
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { detectBrowserLocale } from '@lokascript/i18n/parser';
|
|
172
|
+
* import { Parser } from '@lokascript/core';
|
|
173
|
+
*
|
|
174
|
+
* // Auto-detect and use browser locale
|
|
175
|
+
* const parser = new Parser({ keywords: detectBrowserLocale() });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function detectBrowserLocale(): KeywordProvider {
|
|
179
|
+
if (typeof navigator === 'undefined') {
|
|
180
|
+
return LocaleManager.get('en');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Get browser languages in order of preference
|
|
184
|
+
const languages = navigator.languages || [navigator.language];
|
|
185
|
+
|
|
186
|
+
for (const lang of languages) {
|
|
187
|
+
// Extract the base language code (e.g., 'es-MX' -> 'es')
|
|
188
|
+
const baseLocale = lang.split('-')[0].toLowerCase();
|
|
189
|
+
|
|
190
|
+
if (LocaleManager.has(baseLocale)) {
|
|
191
|
+
return LocaleManager.get(baseLocale);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fallback to English
|
|
196
|
+
return LocaleManager.get('en');
|
|
197
|
+
}
|