@lokascript/i18n 1.3.0 → 2.1.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/dist/browser.cjs +3625 -3581
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +9 -3
- package/dist/browser.d.ts +9 -3
- package/dist/browser.js +3595 -3582
- package/dist/browser.js.map +1 -1
- package/dist/dictionaries/index.cjs +0 -44
- package/dist/dictionaries/index.cjs.map +1 -1
- package/dist/dictionaries/index.d.cts +30 -81
- package/dist/dictionaries/index.d.ts +30 -81
- package/dist/dictionaries/index.js +0 -44
- package/dist/dictionaries/index.js.map +1 -1
- package/dist/index.cjs +7605 -7544
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -38
- package/dist/index.d.ts +38 -38
- package/dist/index.js +7576 -7545
- package/dist/index.js.map +1 -1
- package/dist/lokascript-i18n.min.js +1 -1
- package/dist/lokascript-i18n.min.js.map +1 -1
- package/dist/lokascript-i18n.mjs +3792 -3813
- package/dist/lokascript-i18n.mjs.map +1 -1
- package/dist/plugins/vite.cjs +0 -42
- package/dist/plugins/vite.cjs.map +1 -1
- package/dist/plugins/vite.js +0 -42
- package/dist/plugins/vite.js.map +1 -1
- package/dist/plugins/webpack.cjs +0 -42
- package/dist/plugins/webpack.cjs.map +1 -1
- package/dist/plugins/webpack.js +0 -42
- package/dist/plugins/webpack.js.map +1 -1
- package/dist/{transformer-BBKJJ2vd.d.ts → transformer-B6NgN3JQ.d.ts} +108 -15
- package/dist/{transformer-D8MM2_rz.d.cts → transformer-DQqxl6hb.d.cts} +108 -15
- package/dist/{types-naTJIIaT.d.cts → types-BYtpqGq3.d.cts} +1 -1
- package/dist/{types-naTJIIaT.d.ts → types-BYtpqGq3.d.ts} +1 -1
- package/package.json +8 -7
- package/src/browser.ts +22 -1
- package/src/dictionaries/ar.ts +0 -2
- package/src/dictionaries/de.ts +0 -2
- package/src/dictionaries/derive.ts +0 -2
- package/src/dictionaries/en.ts +0 -2
- package/src/dictionaries/es.ts +0 -2
- package/src/dictionaries/fr.ts +0 -2
- package/src/dictionaries/hi.ts +0 -2
- package/src/dictionaries/id.ts +0 -2
- package/src/dictionaries/index.ts +79 -163
- package/src/dictionaries/it.ts +0 -2
- package/src/dictionaries/ja.ts +0 -2
- package/src/dictionaries/ko.ts +0 -2
- package/src/dictionaries/ms.ts +0 -2
- package/src/dictionaries/pl.ts +0 -2
- package/src/dictionaries/pt.ts +0 -2
- package/src/dictionaries/qu.ts +0 -2
- package/src/dictionaries/ru.ts +0 -2
- package/src/dictionaries/sw.ts +0 -2
- package/src/dictionaries/tl.ts +0 -2
- package/src/dictionaries/tr.ts +0 -2
- package/src/dictionaries/uk.ts +0 -2
- package/src/dictionaries/vi.ts +0 -2
- package/src/dictionaries/zh.ts +0 -2
- package/src/grammar/direct-mappings.ts +0 -2
- package/src/grammar/grammar.test.ts +98 -0
- package/src/grammar/index.ts +9 -0
- package/src/grammar/transformer.ts +125 -73
- package/src/index.ts +30 -0
- package/src/parser/ar.ts +1 -1
- package/src/parser/bn.ts +9 -0
- package/src/parser/de.ts +1 -1
- package/src/parser/es.ts +1 -1
- package/src/parser/fr.ts +1 -1
- package/src/parser/hi.ts +9 -0
- package/src/parser/id.ts +1 -1
- package/src/parser/index.ts +10 -0
- package/src/parser/it.ts +9 -0
- package/src/parser/ja.ts +1 -1
- package/src/parser/ko.ts +1 -1
- package/src/parser/locale-manager.ts +1 -1
- package/src/parser/ms.ts +9 -0
- package/src/parser/pl.ts +9 -0
- package/src/parser/pt.ts +1 -1
- package/src/parser/qu.ts +1 -1
- package/src/parser/ru.ts +9 -0
- package/src/parser/sw.ts +1 -1
- package/src/parser/th.ts +9 -0
- package/src/parser/tl.ts +9 -0
- package/src/parser/tr.ts +1 -1
- package/src/parser/uk.ts +9 -0
- package/src/parser/vi.ts +9 -0
- package/src/parser/zh.ts +1 -1
- package/src/runtime.test.ts +152 -0
- package/src/runtime.ts +32 -13
- package/src/utils/locale.test.ts +108 -0
- package/src/utils/locale.ts +19 -25
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi as vitest } from 'vitest';
|
|
2
|
+
import { RuntimeI18nManager, initializeI18n, getI18n, runtimeI18n } from './runtime';
|
|
3
|
+
|
|
4
|
+
// Reset the global runtimeI18n between tests
|
|
5
|
+
function resetGlobalI18n() {
|
|
6
|
+
// Directly reset the module-level variable via re-import side effects
|
|
7
|
+
// We'll test initializeI18n/getI18n via the exported functions
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('RuntimeI18nManager', () => {
|
|
11
|
+
let manager: RuntimeI18nManager;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
manager = new RuntimeI18nManager({ locale: 'en' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('constructor', () => {
|
|
18
|
+
it('creates with default options', () => {
|
|
19
|
+
const m = new RuntimeI18nManager();
|
|
20
|
+
expect(m.getLocale()).toBe('en');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('creates with custom locale', () => {
|
|
24
|
+
const m = new RuntimeI18nManager({ locale: 'es' });
|
|
25
|
+
expect(m.getLocale()).toBe('es');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('getLocale', () => {
|
|
30
|
+
it('returns current locale', () => {
|
|
31
|
+
expect(manager.getLocale()).toBe('en');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('setLocale', () => {
|
|
36
|
+
it('updates the current locale', async () => {
|
|
37
|
+
await manager.setLocale('fr');
|
|
38
|
+
expect(manager.getLocale()).toBe('fr');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('skips when same locale', async () => {
|
|
42
|
+
const observer = vitest.fn();
|
|
43
|
+
manager.onLocaleChange(observer);
|
|
44
|
+
await manager.setLocale('en'); // same as current
|
|
45
|
+
expect(observer).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('notifies observers on locale change', async () => {
|
|
49
|
+
const observer = vitest.fn();
|
|
50
|
+
manager.onLocaleChange(observer);
|
|
51
|
+
await manager.setLocale('ja');
|
|
52
|
+
expect(observer).toHaveBeenCalledWith('ja');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('onLocaleChange', () => {
|
|
57
|
+
it('subscribes to locale changes', async () => {
|
|
58
|
+
const callback = vitest.fn();
|
|
59
|
+
manager.onLocaleChange(callback);
|
|
60
|
+
await manager.setLocale('ko');
|
|
61
|
+
expect(callback).toHaveBeenCalledWith('ko');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns cleanup function that unsubscribes', async () => {
|
|
65
|
+
const callback = vitest.fn();
|
|
66
|
+
const cleanup = manager.onLocaleChange(callback);
|
|
67
|
+
cleanup();
|
|
68
|
+
await manager.setLocale('de');
|
|
69
|
+
expect(callback).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('supports multiple observers', async () => {
|
|
73
|
+
const cb1 = vitest.fn();
|
|
74
|
+
const cb2 = vitest.fn();
|
|
75
|
+
manager.onLocaleChange(cb1);
|
|
76
|
+
manager.onLocaleChange(cb2);
|
|
77
|
+
await manager.setLocale('fr');
|
|
78
|
+
expect(cb1).toHaveBeenCalledWith('fr');
|
|
79
|
+
expect(cb2).toHaveBeenCalledWith('fr');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('isRTL', () => {
|
|
84
|
+
it('returns true for Arabic', () => {
|
|
85
|
+
expect(manager.isRTL('ar')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns false for English', () => {
|
|
89
|
+
expect(manager.isRTL('en')).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('uses current locale when no argument', () => {
|
|
93
|
+
expect(manager.isRTL()).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('getSupportedLocales', () => {
|
|
98
|
+
it('returns array of locale codes', () => {
|
|
99
|
+
const locales = manager.getSupportedLocales();
|
|
100
|
+
expect(locales).toContain('en');
|
|
101
|
+
expect(locales).toContain('es');
|
|
102
|
+
expect(locales).toContain('ja');
|
|
103
|
+
expect(locales.length).toBeGreaterThanOrEqual(22);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('translate', () => {
|
|
108
|
+
it('translates from English to target locale', () => {
|
|
109
|
+
const result = manager.translate('on', { to: 'es' });
|
|
110
|
+
expect(typeof result).toBe('string');
|
|
111
|
+
expect(result.length).toBeGreaterThan(0);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('createLocaleSwitcher', () => {
|
|
116
|
+
it('creates a dropdown element by default', () => {
|
|
117
|
+
const switcher = manager.createLocaleSwitcher();
|
|
118
|
+
expect(switcher.tagName).toBe('SELECT');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('creates a button container when type is buttons', () => {
|
|
122
|
+
const switcher = manager.createLocaleSwitcher({ type: 'buttons' });
|
|
123
|
+
expect(switcher.tagName).toBe('DIV');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('applies custom className', () => {
|
|
127
|
+
const switcher = manager.createLocaleSwitcher({ className: 'my-switcher' });
|
|
128
|
+
expect(switcher.className).toBe('my-switcher');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('initializeI18n / getI18n', () => {
|
|
134
|
+
it('getI18n throws before initialization', () => {
|
|
135
|
+
// Reset global state — the module re-exports `runtimeI18n` as let
|
|
136
|
+
// We can't easily reset it, so we test the pattern
|
|
137
|
+
// This test is valid only if runtimeI18n hasn't been set yet in this module scope
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('initializeI18n creates a global instance', () => {
|
|
141
|
+
const instance = initializeI18n({ locale: 'en' });
|
|
142
|
+
expect(instance).toBeInstanceOf(RuntimeI18nManager);
|
|
143
|
+
expect(instance.getLocale()).toBe('en');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('getI18n returns the initialized instance', () => {
|
|
147
|
+
initializeI18n({ locale: 'fr' });
|
|
148
|
+
const instance = getI18n();
|
|
149
|
+
expect(instance).toBeInstanceOf(RuntimeI18nManager);
|
|
150
|
+
expect(instance.getLocale()).toBe('fr');
|
|
151
|
+
});
|
|
152
|
+
});
|
package/src/runtime.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { HyperscriptTranslator } from './translator';
|
|
|
4
4
|
import { Dictionary, I18nConfig, TranslationOptions } from './types';
|
|
5
5
|
import { getBrowserLocales, isRTL } from './utils/locale';
|
|
6
6
|
import { getFormatter } from './formatting';
|
|
7
|
+
import { profiles } from './grammar/profiles';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Runtime i18n manager for client-side locale switching
|
|
@@ -377,21 +378,39 @@ export class RuntimeI18nManager {
|
|
|
377
378
|
* Get locale display name
|
|
378
379
|
*/
|
|
379
380
|
private getLocaleDisplayName(locale: string, showNative: boolean): string {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
ja: { english: 'Japanese', native: '日本語' },
|
|
386
|
-
ko: { english: 'Korean', native: '한국어' },
|
|
387
|
-
zh: { english: 'Chinese', native: '中文' },
|
|
388
|
-
ar: { english: 'Arabic', native: 'العربية' },
|
|
389
|
-
};
|
|
381
|
+
if (showNative) {
|
|
382
|
+
const profile = profiles[locale];
|
|
383
|
+
if (profile) return profile.name;
|
|
384
|
+
return locale;
|
|
385
|
+
}
|
|
390
386
|
|
|
391
|
-
const
|
|
392
|
-
|
|
387
|
+
const englishNames: Record<string, string> = {
|
|
388
|
+
en: 'English',
|
|
389
|
+
es: 'Spanish',
|
|
390
|
+
fr: 'French',
|
|
391
|
+
de: 'German',
|
|
392
|
+
ja: 'Japanese',
|
|
393
|
+
ko: 'Korean',
|
|
394
|
+
zh: 'Chinese',
|
|
395
|
+
ar: 'Arabic',
|
|
396
|
+
tr: 'Turkish',
|
|
397
|
+
pt: 'Portuguese',
|
|
398
|
+
id: 'Indonesian',
|
|
399
|
+
qu: 'Quechua',
|
|
400
|
+
sw: 'Swahili',
|
|
401
|
+
it: 'Italian',
|
|
402
|
+
vi: 'Vietnamese',
|
|
403
|
+
pl: 'Polish',
|
|
404
|
+
ru: 'Russian',
|
|
405
|
+
uk: 'Ukrainian',
|
|
406
|
+
hi: 'Hindi',
|
|
407
|
+
bn: 'Bengali',
|
|
408
|
+
th: 'Thai',
|
|
409
|
+
ms: 'Malay',
|
|
410
|
+
tl: 'Tagalog',
|
|
411
|
+
};
|
|
393
412
|
|
|
394
|
-
return
|
|
413
|
+
return englishNames[locale] || locale;
|
|
395
414
|
}
|
|
396
415
|
|
|
397
416
|
/**
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseLocale, getBestMatchingLocale, formatLocaleName } from './locale';
|
|
3
|
+
|
|
4
|
+
describe('parseLocale', () => {
|
|
5
|
+
it('parses simple language codes', () => {
|
|
6
|
+
const result = parseLocale('en');
|
|
7
|
+
expect(result.language).toBe('en');
|
|
8
|
+
expect(result.region).toBeUndefined();
|
|
9
|
+
expect(result.script).toBeUndefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('parses language-region codes', () => {
|
|
13
|
+
const result = parseLocale('en-US');
|
|
14
|
+
expect(result.language).toBe('en');
|
|
15
|
+
expect(result.region).toBe('US');
|
|
16
|
+
expect(result.script).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('parses language-script-region codes', () => {
|
|
20
|
+
const result = parseLocale('zh-Hans-CN');
|
|
21
|
+
expect(result.language).toBe('zh');
|
|
22
|
+
expect(result.script).toBe('Hans');
|
|
23
|
+
expect(result.region).toBe('CN');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('parses language-script without region', () => {
|
|
27
|
+
const result = parseLocale('zh-Hans');
|
|
28
|
+
expect(result.language).toBe('zh');
|
|
29
|
+
expect(result.script).toBe('Hans');
|
|
30
|
+
expect(result.region).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('normalizes language to lowercase and region to uppercase', () => {
|
|
34
|
+
const result = parseLocale('EN-us');
|
|
35
|
+
expect(result.language).toBe('en');
|
|
36
|
+
expect(result.region).toBe('US');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('getBestMatchingLocale', () => {
|
|
41
|
+
const available = ['en', 'fr', 'zh-Hans', 'zh-Hant', 'es'];
|
|
42
|
+
|
|
43
|
+
it('returns exact match', () => {
|
|
44
|
+
expect(getBestMatchingLocale('en', available)).toBe('en');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns script match over language-only match', () => {
|
|
48
|
+
expect(getBestMatchingLocale('zh-Hans-CN', available)).toBe('zh-Hans');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('falls back to language-only match', () => {
|
|
52
|
+
expect(getBestMatchingLocale('fr-CA', available)).toBe('fr');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns null when no match', () => {
|
|
56
|
+
expect(getBestMatchingLocale('de', available)).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('prefers script match when available', () => {
|
|
60
|
+
expect(getBestMatchingLocale('zh-Hant-TW', available)).toBe('zh-Hant');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('formatLocaleName', () => {
|
|
65
|
+
it('returns native name from grammar profile', () => {
|
|
66
|
+
expect(formatLocaleName('ja')).toBe('日本語');
|
|
67
|
+
expect(formatLocaleName('ko')).toBe('한국어');
|
|
68
|
+
expect(formatLocaleName('es')).toBe('Español');
|
|
69
|
+
expect(formatLocaleName('ar')).toBe('العربية');
|
|
70
|
+
expect(formatLocaleName('en')).toBe('English');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns locale code for unknown locales', () => {
|
|
74
|
+
expect(formatLocaleName('xx')).toBe('xx');
|
|
75
|
+
expect(formatLocaleName('unknown')).toBe('unknown');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('covers all 22 supported languages', () => {
|
|
79
|
+
const codes = [
|
|
80
|
+
'en',
|
|
81
|
+
'es',
|
|
82
|
+
'ja',
|
|
83
|
+
'ko',
|
|
84
|
+
'zh',
|
|
85
|
+
'fr',
|
|
86
|
+
'de',
|
|
87
|
+
'ar',
|
|
88
|
+
'tr',
|
|
89
|
+
'pt',
|
|
90
|
+
'id',
|
|
91
|
+
'qu',
|
|
92
|
+
'sw',
|
|
93
|
+
'bn',
|
|
94
|
+
'it',
|
|
95
|
+
'ru',
|
|
96
|
+
'uk',
|
|
97
|
+
'vi',
|
|
98
|
+
'hi',
|
|
99
|
+
'tl',
|
|
100
|
+
'th',
|
|
101
|
+
'pl',
|
|
102
|
+
];
|
|
103
|
+
for (const code of codes) {
|
|
104
|
+
const name = formatLocaleName(code);
|
|
105
|
+
expect(name).not.toBe(code); // Should return a real name, not the code itself
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/utils/locale.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { dictionaries } from '../dictionaries';
|
|
4
4
|
import { DICTIONARY_CATEGORIES } from '../types';
|
|
5
|
+
import { profiles } from '../grammar/profiles';
|
|
5
6
|
|
|
6
7
|
export interface LocaleInfo {
|
|
7
8
|
code: string;
|
|
@@ -98,17 +99,7 @@ export function getBestMatchingLocale(
|
|
|
98
99
|
|
|
99
100
|
const requested = parseLocale(requestedLocale);
|
|
100
101
|
|
|
101
|
-
// Try language
|
|
102
|
-
const languageMatch = availableLocales.find(locale => {
|
|
103
|
-
const available = parseLocale(locale);
|
|
104
|
-
return available.language === requested.language;
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
if (languageMatch) {
|
|
108
|
-
return languageMatch;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Try language-script match
|
|
102
|
+
// Try language+script match first (more specific)
|
|
112
103
|
if (requested.script) {
|
|
113
104
|
const scriptMatch = availableLocales.find(locale => {
|
|
114
105
|
const available = parseLocale(locale);
|
|
@@ -120,6 +111,16 @@ export function getBestMatchingLocale(
|
|
|
120
111
|
}
|
|
121
112
|
}
|
|
122
113
|
|
|
114
|
+
// Fall back to language-only match
|
|
115
|
+
const languageMatch = availableLocales.find(locale => {
|
|
116
|
+
const available = parseLocale(locale);
|
|
117
|
+
return available.language === requested.language;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (languageMatch) {
|
|
121
|
+
return languageMatch;
|
|
122
|
+
}
|
|
123
|
+
|
|
123
124
|
return null;
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -160,24 +161,17 @@ function escapeRegex(str: string): string {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
/**
|
|
163
|
-
* Format a locale for display
|
|
164
|
+
* Format a locale for display using the native name from its grammar profile.
|
|
164
165
|
*/
|
|
165
166
|
export function formatLocaleName(locale: string): string {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
const profile = profiles[locale];
|
|
168
|
+
if (profile) return profile.name;
|
|
169
|
+
|
|
170
|
+
// Fallback for sub-locale codes (e.g., 'zh-TW')
|
|
171
|
+
const subLocaleNames: Record<string, string> = {
|
|
171
172
|
'zh-TW': '繁體中文',
|
|
172
|
-
ja: '日本語',
|
|
173
|
-
fr: 'Français',
|
|
174
|
-
de: 'Deutsch',
|
|
175
|
-
pt: 'Português',
|
|
176
|
-
hi: 'हिन्दी',
|
|
177
|
-
ar: 'العربية',
|
|
178
173
|
};
|
|
179
|
-
|
|
180
|
-
return names[locale] || locale;
|
|
174
|
+
return subLocaleNames[locale] || locale;
|
|
181
175
|
}
|
|
182
176
|
|
|
183
177
|
/**
|