i18ntk 4.1.0 → 4.2.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/CHANGELOG.md +51 -5
- package/README.md +79 -17
- package/SECURITY.md +10 -4
- package/main/i18ntk-analyze.js +10 -20
- package/main/i18ntk-backup.js +106 -44
- package/main/i18ntk-init.js +153 -157
- package/main/i18ntk-setup.js +36 -13
- package/main/i18ntk-translate.js +169 -21
- package/main/i18ntk-usage.js +272 -103
- package/main/i18ntk-validate.js +38 -31
- package/main/manage/commands/AnalyzeCommand.js +7 -17
- package/main/manage/commands/CommandRouter.js +6 -6
- package/main/manage/commands/TranslateCommand.js +65 -56
- package/main/manage/commands/ValidateCommand.js +34 -26
- package/main/manage/index.js +11 -42
- package/main/manage/managers/InteractiveMenu.js +11 -40
- package/main/manage/services/InitService.js +114 -118
- package/main/manage/services/UsageService.js +244 -85
- package/package.json +21 -14
- package/runtime/enhanced.d.ts +5 -5
- package/runtime/enhanced.js +49 -25
- package/runtime/i18ntk.d.ts +30 -7
- package/runtime/index.d.ts +48 -19
- package/runtime/index.js +175 -90
- package/settings/settings-cli.js +115 -38
- package/settings/settings-manager.js +24 -6
- package/ui-locales/de.json +192 -11
- package/ui-locales/en.json +182 -8
- package/ui-locales/es.json +193 -12
- package/ui-locales/fr.json +189 -8
- package/ui-locales/ja.json +190 -8
- package/ui-locales/ru.json +191 -9
- package/ui-locales/zh.json +194 -9
- package/utils/cli-helper.js +8 -12
- package/utils/config-helper.js +1 -1
- package/utils/config-manager.js +8 -6
- package/utils/localized-confirm.js +55 -0
- package/utils/menu-layout.js +41 -0
- package/utils/report-writer.js +110 -0
- package/utils/security.js +15 -22
- package/utils/translate/api.js +31 -3
- package/utils/translate/placeholder.js +42 -1
- package/utils/translate/report.js +3 -2
- package/utils/translate/safe-network.js +24 -4
- package/utils/usage-insights.js +435 -0
- package/utils/usage-source.js +50 -0
- package/utils/watch-locales.js +1 -8
package/runtime/enhanced.d.ts
CHANGED
|
@@ -72,19 +72,19 @@ export interface LanguageConfig {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Enhanced TypeScript-compatible translation functions
|
|
75
|
-
export function translate<T = string>(key: string, params?: TranslationParams, options?: TranslationOptions): T
|
|
76
|
-
export function translate<T = string>(key: TranslationKey<T>, params?: TranslationParams, options?: TranslationOptions): T
|
|
75
|
+
export function translate<T = string>(key: string, params?: TranslationParams, options?: TranslationOptions): Promise<T>;
|
|
76
|
+
export function translate<T = string>(key: TranslationKey<T>, params?: TranslationParams, options?: TranslationOptions): Promise<T>;
|
|
77
77
|
|
|
78
78
|
export const t: typeof translate;
|
|
79
79
|
|
|
80
80
|
// Type-safe translation with strict typing
|
|
81
|
-
export function tTyped<T = string>(key: string, params?: TranslationParams, options?: TranslationOptions): T
|
|
81
|
+
export function tTyped<T = string>(key: string, params?: TranslationParams, options?: TranslationOptions): Promise<T>;
|
|
82
82
|
|
|
83
83
|
// Translation with encryption support
|
|
84
84
|
export function translateEncrypted<T = string>(key: string, params?: TranslationParams, options?: TranslationOptions): Promise<T>;
|
|
85
85
|
|
|
86
86
|
// Batch translation operations
|
|
87
|
-
export function translateBatch<T = string>(keys: string[], params?: TranslationParams[], options?: TranslationOptions): T[]
|
|
87
|
+
export function translateBatch<T = string>(keys: string[], params?: TranslationParams[], options?: TranslationOptions): Promise<T[]>;
|
|
88
88
|
export function translateBatchEncrypted<T = string>(keys: string[], params?: TranslationParams[], options?: TranslationOptions): Promise<T[]>;
|
|
89
89
|
|
|
90
90
|
// Configuration and initialization
|
|
@@ -219,4 +219,4 @@ export interface MigrationResult {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Export compatibility with existing runtime
|
|
222
|
-
export * from './i18ntk.d.ts';
|
|
222
|
+
export * from './i18ntk.d.ts';
|
package/runtime/enhanced.js
CHANGED
|
@@ -884,26 +884,46 @@ async function initI18nRuntime(options = {}) {
|
|
|
884
884
|
}
|
|
885
885
|
|
|
886
886
|
// Backward compatibility
|
|
887
|
-
function translate(key, params, options) {
|
|
888
|
-
if (!runtimeInstance) {
|
|
889
|
-
runtimeInstance = new I18nEnhancedRuntime();
|
|
890
|
-
}
|
|
891
|
-
return runtimeInstance.translate(key, params, options);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const t = translate;
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
887
|
+
function translate(key, params, options) {
|
|
888
|
+
if (!runtimeInstance) {
|
|
889
|
+
runtimeInstance = new I18nEnhancedRuntime();
|
|
890
|
+
}
|
|
891
|
+
return runtimeInstance.translate(key, params, options);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const t = translate;
|
|
895
|
+
const tTyped = translate;
|
|
896
|
+
|
|
897
|
+
async function translateBatch(keys, paramsArray, options) {
|
|
898
|
+
if (!runtimeInstance) {
|
|
899
|
+
runtimeInstance = new I18nEnhancedRuntime();
|
|
900
|
+
}
|
|
901
|
+
return runtimeInstance.translateBatch(keys, paramsArray, options);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async function translateBatchEncrypted(keys, paramsArray, options) {
|
|
905
|
+
if (!runtimeInstance) {
|
|
906
|
+
runtimeInstance = new I18nEnhancedRuntime();
|
|
907
|
+
}
|
|
908
|
+
return runtimeInstance.translateBatchEncrypted(keys, paramsArray, options);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
async function translateEncrypted(key, params, options) {
|
|
912
|
+
if (!runtimeInstance) {
|
|
913
|
+
runtimeInstance = new I18nEnhancedRuntime();
|
|
914
|
+
}
|
|
915
|
+
return runtimeInstance.translateEncrypted(key, params, options);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Export for both CommonJS and ES modules
|
|
919
|
+
module.exports = {
|
|
920
|
+
initI18nRuntime,
|
|
921
|
+
translate,
|
|
922
|
+
t,
|
|
923
|
+
tTyped,
|
|
924
|
+
translateEncrypted,
|
|
925
|
+
translateBatch,
|
|
926
|
+
translateBatchEncrypted,
|
|
907
927
|
|
|
908
928
|
// TypeScript compatibility exports
|
|
909
929
|
I18nEnhancedRuntime,
|
|
@@ -922,8 +942,12 @@ module.exports = {
|
|
|
922
942
|
};
|
|
923
943
|
|
|
924
944
|
// ES module support
|
|
925
|
-
module.exports.default = {
|
|
926
|
-
initI18nRuntime,
|
|
927
|
-
translate,
|
|
928
|
-
t,
|
|
929
|
-
|
|
945
|
+
module.exports.default = {
|
|
946
|
+
initI18nRuntime,
|
|
947
|
+
translate,
|
|
948
|
+
t,
|
|
949
|
+
tTyped,
|
|
950
|
+
translateEncrypted,
|
|
951
|
+
translateBatch,
|
|
952
|
+
translateBatchEncrypted,
|
|
953
|
+
};
|
package/runtime/i18ntk.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// runtime/i18ntk.d.ts
|
|
2
|
-
// Complete TypeScript definitions for i18ntk internationalization framework
|
|
3
|
-
// Version
|
|
2
|
+
// Complete TypeScript definitions for i18ntk internationalization framework
|
|
3
|
+
// Version 4.2.0 - Enhanced API types plus lightweight runtime compatibility
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Core translation parameters interface
|
|
@@ -445,16 +445,21 @@ export interface I18nRuntime {
|
|
|
445
445
|
/**
|
|
446
446
|
* Basic runtime interface (for backward compatibility)
|
|
447
447
|
*/
|
|
448
|
-
export interface BasicI18nRuntime {
|
|
448
|
+
export interface BasicI18nRuntime {
|
|
449
449
|
/**
|
|
450
450
|
* Translate a key with parameters (synchronous)
|
|
451
451
|
*/
|
|
452
|
-
translate(key: string, params?: TranslationParams): string;
|
|
452
|
+
translate(key: string, params?: TranslationParams, options?: Pick<TranslationOptions, 'language' | 'fallbackLanguage'>): string | number | boolean | null | object | unknown[];
|
|
453
453
|
|
|
454
454
|
/**
|
|
455
455
|
* Alias for translate function (synchronous)
|
|
456
456
|
*/
|
|
457
|
-
t(key: string, params?: TranslationParams): string;
|
|
457
|
+
t(key: string, params?: TranslationParams, options?: Pick<TranslationOptions, 'language' | 'fallbackLanguage'>): string | number | boolean | null | object | unknown[];
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Translate multiple keys synchronously.
|
|
461
|
+
*/
|
|
462
|
+
translateBatch(keys: string[], params?: TranslationParams | TranslationParams[], options?: Pick<TranslationOptions, 'language' | 'fallbackLanguage'>): Array<string | number | boolean | null | object | unknown[]>;
|
|
458
463
|
|
|
459
464
|
/**
|
|
460
465
|
* Set language
|
|
@@ -469,7 +474,25 @@ export interface BasicI18nRuntime {
|
|
|
469
474
|
/**
|
|
470
475
|
* Get available languages (synchronous)
|
|
471
476
|
*/
|
|
472
|
-
getAvailableLanguages(): string[];
|
|
477
|
+
getAvailableLanguages(): string[];
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Clear all cached runtime data, or one language when provided.
|
|
481
|
+
*/
|
|
482
|
+
clearCache(language?: string): void;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Return lightweight cache diagnostics for production observability.
|
|
486
|
+
*/
|
|
487
|
+
getCacheInfo(): {
|
|
488
|
+
language: string;
|
|
489
|
+
fallbackLanguage: string;
|
|
490
|
+
lazy: boolean;
|
|
491
|
+
cachedLanguages: string[];
|
|
492
|
+
manifestLanguages: string[];
|
|
493
|
+
loadedFileCount: number;
|
|
494
|
+
eagerLoadedLanguages: string[];
|
|
495
|
+
};
|
|
473
496
|
|
|
474
497
|
/**
|
|
475
498
|
* Refresh translations
|
|
@@ -487,7 +510,7 @@ export declare function initI18nRuntime(config: I18nConfig): Promise<I18nRuntime
|
|
|
487
510
|
* This is the default export from 'i18ntk/runtime'
|
|
488
511
|
*/
|
|
489
512
|
export declare function initRuntime(options: {
|
|
490
|
-
baseDir
|
|
513
|
+
baseDir?: string;
|
|
491
514
|
language?: string;
|
|
492
515
|
fallbackLanguage?: string;
|
|
493
516
|
keySeparator?: string;
|
package/runtime/index.d.ts
CHANGED
|
@@ -1,31 +1,60 @@
|
|
|
1
1
|
// runtime/index.d.ts
|
|
2
2
|
// Public runtime API types for i18ntk
|
|
3
3
|
|
|
4
|
-
export interface InitOptions {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export interface InitOptions {
|
|
5
|
+
/** Locale base directory. Defaults to config/env resolution when omitted. */
|
|
6
|
+
baseDir?: string;
|
|
7
|
+
language?: string;
|
|
7
8
|
fallbackLanguage?: string;
|
|
8
9
|
keySeparator?: string;
|
|
10
|
+
/** Eagerly cache the active and fallback languages during initialization. */
|
|
9
11
|
preload?: boolean;
|
|
12
|
+
/** Build a key manifest and load matching JSON files on first key access. */
|
|
10
13
|
lazy?: boolean;
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
export type TranslateParams = Record<string, unknown>;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
|
|
16
|
+
export type TranslateParams = Record<string, unknown>;
|
|
17
|
+
export interface TranslationOptions {
|
|
18
|
+
/** Translate this call with a different language without mutating runtime state. */
|
|
19
|
+
language?: string;
|
|
20
|
+
/** Fallback language for this call. */
|
|
21
|
+
fallbackLanguage?: string;
|
|
22
|
+
}
|
|
23
|
+
export type TranslationValue = string | number | boolean | null | object | unknown[];
|
|
24
|
+
export type TranslateBatchParams = TranslateParams | TranslateParams[];
|
|
25
|
+
|
|
26
|
+
export interface RuntimeCacheInfo {
|
|
27
|
+
language: string;
|
|
28
|
+
fallbackLanguage: string;
|
|
29
|
+
lazy: boolean;
|
|
30
|
+
cachedLanguages: string[];
|
|
31
|
+
manifestLanguages: string[];
|
|
32
|
+
loadedFileCount: number;
|
|
33
|
+
eagerLoadedLanguages: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RuntimeInstance {
|
|
37
|
+
t(key: string, params?: TranslateParams, options?: TranslationOptions): TranslationValue;
|
|
38
|
+
translate(key: string, params?: TranslateParams, options?: TranslationOptions): TranslationValue;
|
|
39
|
+
translateBatch(keys: string[], params?: TranslateBatchParams, options?: TranslationOptions): TranslationValue[];
|
|
40
|
+
setLanguage(lang: string): void;
|
|
41
|
+
getLanguage(): string;
|
|
42
|
+
getAvailableLanguages(): string[];
|
|
43
|
+
clearCache(lang?: string): void;
|
|
44
|
+
getCacheInfo(): RuntimeCacheInfo;
|
|
45
|
+
refresh(lang?: string): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function translate(key: string, params?: TranslateParams, options?: TranslationOptions): TranslationValue;
|
|
49
|
+
export const t: typeof translate;
|
|
50
|
+
export function translateBatch(keys: string[], params?: TranslateBatchParams, options?: TranslationOptions): TranslationValue[];
|
|
51
|
+
|
|
52
|
+
export function initRuntime(options?: InitOptions): RuntimeInstance;
|
|
53
|
+
|
|
54
|
+
export function setLanguage(lang: string): void;
|
|
28
55
|
export function getLanguage(): string;
|
|
29
56
|
export function getAvailableLanguages(): string[];
|
|
57
|
+
export function clearCache(lang?: string): void;
|
|
58
|
+
export function getCacheInfo(): RuntimeCacheInfo;
|
|
30
59
|
export function refresh(lang?: string): void;
|
|
31
60
|
export function loadKeyManifest(baseDir?: string): Map<string, string>;
|
package/runtime/index.js
CHANGED
|
@@ -8,14 +8,22 @@ const path = require('path');
|
|
|
8
8
|
const SecurityUtils = require('../utils/security');
|
|
9
9
|
const { envManager } = require('../utils/env-manager');
|
|
10
10
|
|
|
11
|
-
let configManager = null;
|
|
12
|
-
try { configManager = require('../utils/config-manager'); } catch (_) { /* optional */ }
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
let configManager = null;
|
|
12
|
+
try { configManager = require('../utils/config-manager'); } catch (_) { /* optional */ }
|
|
13
|
+
|
|
14
|
+
const SAFE_LANGUAGE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,35}$/;
|
|
15
|
+
|
|
16
|
+
function normalizeLanguageCode(value, fallback = 'en') {
|
|
17
|
+
if (typeof value !== 'string') return fallback;
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return SAFE_LANGUAGE_PATTERN.test(trimmed) ? trimmed : fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createState(options = {}) {
|
|
23
|
+
return {
|
|
24
|
+
baseDir: resolveBaseDir(options.baseDir),
|
|
25
|
+
language: normalizeLanguageCode(options.language, 'en'),
|
|
26
|
+
fallbackLanguage: normalizeLanguageCode(options.fallbackLanguage, 'en'),
|
|
19
27
|
keySeparator: options.keySeparator || '.',
|
|
20
28
|
cache: new Map(),
|
|
21
29
|
lazy: options.lazy === true,
|
|
@@ -47,21 +55,29 @@ function stripBOMAndComments(s) {
|
|
|
47
55
|
return s;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
function readJsonSafe(file) {
|
|
51
|
-
const raw = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
|
|
52
|
-
if (raw === null || raw === undefined) {
|
|
53
|
-
throw new Error(`Unable to read JSON file: ${file}`);
|
|
54
|
-
}
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
throw new Error(`Empty JSON file: ${file}`);
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
return JSON.parse(
|
|
61
|
-
} catch (parseError) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
58
|
+
function readJsonSafe(file) {
|
|
59
|
+
const raw = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
|
|
60
|
+
if (raw === null || raw === undefined) {
|
|
61
|
+
throw new Error(`Unable to read JSON file: ${file}`);
|
|
62
|
+
}
|
|
63
|
+
const withoutBOM = raw.charCodeAt && raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw;
|
|
64
|
+
if (!withoutBOM) {
|
|
65
|
+
throw new Error(`Empty JSON file: ${file}`);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(withoutBOM);
|
|
69
|
+
} catch (parseError) {
|
|
70
|
+
const cleaned = stripBOMAndComments(raw);
|
|
71
|
+
if (!cleaned) {
|
|
72
|
+
throw new Error(`Empty JSON file: ${file}`);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(cleaned);
|
|
76
|
+
} catch (_) {
|
|
77
|
+
throw new Error(`Invalid JSON in file ${file}: ${parseError.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
65
81
|
|
|
66
82
|
function deepMerge(target, source) {
|
|
67
83
|
if (!target || typeof target !== 'object') target = {};
|
|
@@ -177,11 +193,13 @@ function loadKeyManifestFromDir(baseDir) {
|
|
|
177
193
|
}
|
|
178
194
|
|
|
179
195
|
function getLanguagePath(baseDir, lang) {
|
|
196
|
+
lang = normalizeLanguageCode(lang, '');
|
|
197
|
+
if (!lang) return null;
|
|
180
198
|
const langDir = path.join(baseDir, lang);
|
|
181
199
|
const langFile = path.join(baseDir, `${lang}.json`);
|
|
182
|
-
const langDirStat = SecurityUtils.safeStatSync(langDir,
|
|
200
|
+
const langDirStat = SecurityUtils.safeStatSync(langDir, baseDir);
|
|
183
201
|
if (langDirStat && langDirStat.isDirectory()) return langDir;
|
|
184
|
-
const langFileStat = SecurityUtils.safeStatSync(langFile,
|
|
202
|
+
const langFileStat = SecurityUtils.safeStatSync(langFile, baseDir);
|
|
185
203
|
if (langFileStat && langFileStat.isFile()) return langFile;
|
|
186
204
|
return null;
|
|
187
205
|
}
|
|
@@ -220,13 +238,15 @@ function loadFileLazy(runtimeState, filePath, lang) {
|
|
|
220
238
|
return data;
|
|
221
239
|
}
|
|
222
240
|
|
|
223
|
-
function readLanguageFromBase(baseDir, lang) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
241
|
+
function readLanguageFromBase(baseDir, lang) {
|
|
242
|
+
lang = normalizeLanguageCode(lang, '');
|
|
243
|
+
if (!lang) return {};
|
|
244
|
+
const merged = {};
|
|
245
|
+
const langFile = path.join(baseDir, `${lang}.json`);
|
|
246
|
+
const langDir = path.join(baseDir, lang);
|
|
247
|
+
|
|
248
|
+
// Prefer folder if exists, otherwise single file
|
|
249
|
+
const langDirStat = SecurityUtils.safeStatSync(langDir, baseDir);
|
|
230
250
|
if (langDirStat && langDirStat.isDirectory()) {
|
|
231
251
|
const files = listJsonFilesRecursively(langDir, langDir);
|
|
232
252
|
for (const file of files) {
|
|
@@ -237,8 +257,8 @@ function readLanguageFromBase(baseDir, lang) {
|
|
|
237
257
|
// Skip unreadable/invalid files
|
|
238
258
|
}
|
|
239
259
|
}
|
|
240
|
-
} else {
|
|
241
|
-
const langFileStat = SecurityUtils.safeStatSync(langFile,
|
|
260
|
+
} else {
|
|
261
|
+
const langFileStat = SecurityUtils.safeStatSync(langFile, baseDir);
|
|
242
262
|
if (langFileStat && langFileStat.isFile()) {
|
|
243
263
|
try {
|
|
244
264
|
const data = readJsonSafe(langFile);
|
|
@@ -250,9 +270,10 @@ function readLanguageFromBase(baseDir, lang) {
|
|
|
250
270
|
return merged;
|
|
251
271
|
}
|
|
252
272
|
|
|
253
|
-
function getTranslationsForState(runtimeState, lang) {
|
|
254
|
-
if (!runtimeState.baseDir) runtimeState.baseDir = resolveBaseDir();
|
|
255
|
-
|
|
273
|
+
function getTranslationsForState(runtimeState, lang) {
|
|
274
|
+
if (!runtimeState.baseDir) runtimeState.baseDir = resolveBaseDir();
|
|
275
|
+
lang = normalizeLanguageCode(lang, runtimeState.fallbackLanguage || 'en');
|
|
276
|
+
|
|
256
277
|
if (runtimeState.lazy) {
|
|
257
278
|
if (runtimeState.cache.has(lang)) return runtimeState.cache.get(lang);
|
|
258
279
|
runtimeState.cache.set(lang, {});
|
|
@@ -266,12 +287,13 @@ function getTranslationsForState(runtimeState, lang) {
|
|
|
266
287
|
return data;
|
|
267
288
|
}
|
|
268
289
|
|
|
269
|
-
function interpolate(template, params) {
|
|
270
|
-
if (typeof template !== 'string') return template;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.replace(/\{(\w+)\}/g, (m, p1) => (p1 in params ? String(params[p1]) : m))
|
|
274
|
-
}
|
|
290
|
+
function interpolate(template, params) {
|
|
291
|
+
if (typeof template !== 'string') return template;
|
|
292
|
+
params = params && typeof params === 'object' ? params : {};
|
|
293
|
+
return template
|
|
294
|
+
.replace(/\{\{(\w+)\}\}/g, (m, p1) => (p1 in params ? String(params[p1]) : m))
|
|
295
|
+
.replace(/\{(\w+)\}/g, (m, p1) => (p1 in params ? String(params[p1]) : m));
|
|
296
|
+
}
|
|
275
297
|
|
|
276
298
|
// Resolve a dotted key path from an object
|
|
277
299
|
function resolveKey(obj, key, sep = '.', runtimeState = null, lang = null) {
|
|
@@ -360,43 +382,67 @@ function preload(runtimeState, shouldPreload) {
|
|
|
360
382
|
}
|
|
361
383
|
}
|
|
362
384
|
|
|
363
|
-
function createRuntime(runtimeState) {
|
|
364
|
-
const runtimeTranslate = (key, params) => translateWithState(runtimeState, key, params);
|
|
365
|
-
return {
|
|
366
|
-
t: runtimeTranslate,
|
|
367
|
-
translate: runtimeTranslate,
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function translateWithState(runtimeState, key, params = {}) {
|
|
380
|
-
const langData = getTranslationsForState(runtimeState, runtimeState.language);
|
|
381
|
-
let value = resolveKey(langData, key, runtimeState.keySeparator, runtimeState, runtimeState.language);
|
|
385
|
+
function createRuntime(runtimeState) {
|
|
386
|
+
const runtimeTranslate = (key, params, options) => translateWithState(runtimeState, key, params, options);
|
|
387
|
+
return {
|
|
388
|
+
t: runtimeTranslate,
|
|
389
|
+
translate: runtimeTranslate,
|
|
390
|
+
translateBatch: (keys, params, options) => translateBatchWithState(runtimeState, keys, params, options),
|
|
391
|
+
setLanguage: (lang) => setLanguageForState(runtimeState, lang),
|
|
392
|
+
getLanguage: () => getLanguageForState(runtimeState),
|
|
393
|
+
getAvailableLanguages: () => getAvailableLanguagesForState(runtimeState),
|
|
394
|
+
clearCache: (lang) => clearCacheForState(runtimeState, lang),
|
|
395
|
+
getCacheInfo: () => getCacheInfoForState(runtimeState),
|
|
396
|
+
refresh: (lang) => refreshForState(runtimeState, lang),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
382
399
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
400
|
+
function translate(key, params = {}, options = {}) {
|
|
401
|
+
return translateWithState(singletonState, key, params, options);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function translateWithState(runtimeState, key, params = {}, options = {}) {
|
|
405
|
+
params = params && typeof params === 'object' ? params : {};
|
|
406
|
+
options = options && typeof options === 'object' ? options : {};
|
|
407
|
+
|
|
408
|
+
const activeLanguage = normalizeLanguageCode(options.language, runtimeState.language);
|
|
409
|
+
const fallbackLanguage = typeof options.fallbackLanguage === 'string'
|
|
410
|
+
? normalizeLanguageCode(options.fallbackLanguage, '')
|
|
411
|
+
: runtimeState.fallbackLanguage;
|
|
412
|
+
|
|
413
|
+
const langData = getTranslationsForState(runtimeState, activeLanguage);
|
|
414
|
+
let value = resolveKey(langData, key, runtimeState.keySeparator, runtimeState, activeLanguage);
|
|
415
|
+
|
|
416
|
+
if (typeof value === 'undefined' && fallbackLanguage && fallbackLanguage !== activeLanguage) {
|
|
417
|
+
const fbData = getTranslationsForState(runtimeState, fallbackLanguage);
|
|
418
|
+
value = resolveKey(fbData, key, runtimeState.keySeparator, runtimeState, fallbackLanguage);
|
|
386
419
|
}
|
|
387
|
-
|
|
388
|
-
if (typeof value === 'string') return interpolate(value, params);
|
|
389
|
-
return typeof value === 'undefined' ? key : value;
|
|
390
|
-
}
|
|
420
|
+
|
|
421
|
+
if (typeof value === 'string') return interpolate(value, params);
|
|
422
|
+
return typeof value === 'undefined' ? key : value;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function translateBatch(keys, params = {}, options = {}) {
|
|
426
|
+
return translateBatchWithState(singletonState, keys, params, options);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function translateBatchWithState(runtimeState, keys, params = {}, options = {}) {
|
|
430
|
+
if (!Array.isArray(keys)) return [];
|
|
431
|
+
return keys.map((key, index) => {
|
|
432
|
+
const perKeyParams = Array.isArray(params) ? (params[index] || {}) : params;
|
|
433
|
+
return translateWithState(runtimeState, key, perKeyParams, options);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
391
436
|
|
|
392
437
|
function setLanguage(lang) {
|
|
393
438
|
setLanguageForState(singletonState, lang);
|
|
394
439
|
}
|
|
395
440
|
|
|
396
|
-
function setLanguageForState(runtimeState, lang) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
441
|
+
function setLanguageForState(runtimeState, lang) {
|
|
442
|
+
const safeLang = normalizeLanguageCode(lang, '');
|
|
443
|
+
if (!safeLang) return;
|
|
444
|
+
runtimeState.language = safeLang;
|
|
445
|
+
}
|
|
400
446
|
|
|
401
447
|
function getLanguage() {
|
|
402
448
|
return getLanguageForState(singletonState);
|
|
@@ -410,33 +456,69 @@ function getAvailableLanguages() {
|
|
|
410
456
|
return getAvailableLanguagesForState(singletonState);
|
|
411
457
|
}
|
|
412
458
|
|
|
413
|
-
function getAvailableLanguagesForState(runtimeState) {
|
|
459
|
+
function getAvailableLanguagesForState(runtimeState) {
|
|
414
460
|
const langs = new Set();
|
|
415
461
|
if (!runtimeState.baseDir) runtimeState.baseDir = resolveBaseDir();
|
|
416
462
|
if (!SecurityUtils.safeExistsSync(runtimeState.baseDir, path.dirname(runtimeState.baseDir))) return ['en'];
|
|
417
463
|
try {
|
|
418
464
|
for (const entry of fs.readdirSync(runtimeState.baseDir, { withFileTypes: true })) {
|
|
419
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
465
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
|
|
466
|
+
const lang = normalizeLanguageCode(entry.name.replace(/\.json$/i, ''), '');
|
|
467
|
+
if (lang) langs.add(lang);
|
|
468
|
+
} else if (entry.isDirectory()) {
|
|
469
|
+
const lang = normalizeLanguageCode(entry.name, '');
|
|
470
|
+
if (!lang) continue;
|
|
471
|
+
const idx = path.join(runtimeState.baseDir, lang, `${lang}.json`);
|
|
472
|
+
if (SecurityUtils.safeExistsSync(idx, runtimeState.baseDir)) langs.add(lang);
|
|
473
|
+
else langs.add(lang); // be permissive
|
|
474
|
+
}
|
|
427
475
|
}
|
|
428
476
|
} catch (_) {
|
|
429
477
|
// Unreadable directory
|
|
430
478
|
return ['en'];
|
|
431
479
|
}
|
|
432
|
-
return Array.from(langs.size ? langs : new Set(['en']));
|
|
433
|
-
}
|
|
480
|
+
return Array.from(langs.size ? langs : new Set(['en']));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function clearCache(lang) {
|
|
484
|
+
clearCacheForState(singletonState, lang);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function clearCacheForState(runtimeState, lang) {
|
|
488
|
+
if (typeof lang === 'string') {
|
|
489
|
+
refreshForState(runtimeState, lang);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
runtimeState.cache.clear();
|
|
494
|
+
if (runtimeState.keyManifest) runtimeState.keyManifest.clear();
|
|
495
|
+
if (runtimeState.loadedFiles) runtimeState.loadedFiles.clear();
|
|
496
|
+
if (runtimeState.eagerLoadedLanguages) runtimeState.eagerLoadedLanguages.clear();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getCacheInfo() {
|
|
500
|
+
return getCacheInfoForState(singletonState);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function getCacheInfoForState(runtimeState) {
|
|
504
|
+
return {
|
|
505
|
+
language: runtimeState.language,
|
|
506
|
+
fallbackLanguage: runtimeState.fallbackLanguage,
|
|
507
|
+
lazy: runtimeState.lazy === true,
|
|
508
|
+
cachedLanguages: Array.from(runtimeState.cache.keys()).sort(),
|
|
509
|
+
manifestLanguages: runtimeState.keyManifest ? Array.from(runtimeState.keyManifest.keys()).sort() : [],
|
|
510
|
+
loadedFileCount: runtimeState.loadedFiles ? runtimeState.loadedFiles.size : 0,
|
|
511
|
+
eagerLoadedLanguages: runtimeState.eagerLoadedLanguages ? Array.from(runtimeState.eagerLoadedLanguages).sort() : [],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
434
514
|
|
|
435
515
|
function refresh(lang) {
|
|
436
516
|
refreshForState(singletonState, lang);
|
|
437
517
|
}
|
|
438
518
|
|
|
439
519
|
function refreshForState(runtimeState, lang = runtimeState.language) {
|
|
520
|
+
lang = normalizeLanguageCode(lang, '');
|
|
521
|
+
if (!lang) return;
|
|
440
522
|
if (runtimeState.cache.has(lang)) runtimeState.cache.delete(lang);
|
|
441
523
|
if (runtimeState.eagerLoadedLanguages) runtimeState.eagerLoadedLanguages.delete(lang);
|
|
442
524
|
if (runtimeState.keyManifest) runtimeState.keyManifest.delete(lang);
|
|
@@ -451,12 +533,15 @@ function refreshForState(runtimeState, lang = runtimeState.language) {
|
|
|
451
533
|
}
|
|
452
534
|
|
|
453
535
|
module.exports = {
|
|
454
|
-
initRuntime,
|
|
455
|
-
translate,
|
|
456
|
-
t: translate,
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
536
|
+
initRuntime,
|
|
537
|
+
translate,
|
|
538
|
+
t: translate,
|
|
539
|
+
translateBatch,
|
|
540
|
+
setLanguage,
|
|
541
|
+
getLanguage,
|
|
542
|
+
getAvailableLanguages,
|
|
543
|
+
clearCache,
|
|
544
|
+
getCacheInfo,
|
|
460
545
|
refresh,
|
|
461
546
|
loadKeyManifest: (baseDir) => loadKeyManifestFromDir(baseDir || singletonState.baseDir),
|
|
462
547
|
};
|