@digitaldefiance/i18n-lib 1.3.11 → 1.3.13
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 +8 -0
- package/package.json +12 -27
- package/src/active-context.ts +30 -0
- package/src/component-definition.ts +11 -0
- package/src/component-registration.ts +13 -0
- package/src/component-registry.ts +392 -0
- package/src/context-error-type.ts +3 -0
- package/src/context-error.ts +16 -0
- package/src/context-manager.ts +71 -0
- package/src/context.ts +90 -0
- package/src/core-i18n.ts +609 -0
- package/src/core-string-key.ts +49 -0
- package/src/create-translation-adapter.ts +47 -0
- package/src/currency-code.ts +35 -0
- package/{dist/currency-format.d.ts → src/currency-format.ts} +5 -4
- package/src/currency.ts +52 -0
- package/src/default-config.ts +199 -0
- package/src/enum-registry.ts +138 -0
- package/src/global-active-context.ts +255 -0
- package/src/handleable.ts +79 -0
- package/src/i-global-active-context.ts +59 -0
- package/src/i-handleable-error-options.ts +6 -0
- package/src/i-handleable.ts +5 -0
- package/src/i18n-config.ts +29 -0
- package/{dist/i18n-context.d.ts → src/i18n-context.ts} +7 -6
- package/src/i18n-engine.ts +491 -0
- package/{dist/index.d.ts → src/index.ts} +10 -1
- package/{dist/language-codes.d.ts → src/language-codes.ts} +23 -11
- package/src/language-definition.ts +13 -0
- package/src/language-registry.ts +292 -0
- package/src/plugin-i18n-engine.ts +520 -0
- package/src/plugin-translatable-generic-error.ts +106 -0
- package/src/plugin-translatable-handleable-generic.ts +60 -0
- package/src/plugin-typed-handleable.ts +77 -0
- package/src/registry-config.ts +15 -0
- package/src/registry-error-type.ts +12 -0
- package/src/registry-error.ts +74 -0
- package/src/strict-types.ts +35 -0
- package/src/template.ts +63 -0
- package/src/timezone.ts +20 -0
- package/src/translatable.ts +15 -0
- package/src/translation-engine.ts +8 -0
- package/src/translation-request.ts +12 -0
- package/src/translation-response.ts +8 -0
- package/src/typed-error.ts +384 -0
- package/src/typed-handleable.ts +70 -0
- package/{dist/types.d.ts → src/types.ts} +75 -20
- package/src/unified-translator.ts +96 -0
- package/src/utils.ts +213 -0
- package/src/validation-config.ts +11 -0
- package/src/validation-result.ts +12 -0
- package/dist/active-context.d.ts +0 -29
- package/dist/active-context.js +0 -2
- package/dist/component-definition.d.ts +0 -11
- package/dist/component-definition.js +0 -2
- package/dist/component-registration.d.ts +0 -9
- package/dist/component-registration.js +0 -2
- package/dist/component-registry.d.ts +0 -68
- package/dist/component-registry.js +0 -245
- package/dist/context-error-type.d.ts +0 -3
- package/dist/context-error-type.js +0 -7
- package/dist/context-error.d.ts +0 -6
- package/dist/context-error.js +0 -15
- package/dist/context-manager.d.ts +0 -33
- package/dist/context-manager.js +0 -61
- package/dist/context.d.ts +0 -44
- package/dist/context.js +0 -69
- package/dist/core-i18n.d.ts +0 -62
- package/dist/core-i18n.js +0 -477
- package/dist/core-string-key.d.ts +0 -42
- package/dist/core-string-key.js +0 -50
- package/dist/create-translation-adapter.d.ts +0 -20
- package/dist/create-translation-adapter.js +0 -36
- package/dist/currency-code.d.ts +0 -19
- package/dist/currency-code.js +0 -36
- package/dist/currency-format.js +0 -2
- package/dist/currency.d.ts +0 -11
- package/dist/currency.js +0 -48
- package/dist/default-config.d.ts +0 -32
- package/dist/default-config.js +0 -101
- package/dist/enum-registry.d.ts +0 -44
- package/dist/enum-registry.js +0 -100
- package/dist/global-active-context.d.ts +0 -50
- package/dist/global-active-context.js +0 -177
- package/dist/handleable.d.ts +0 -13
- package/dist/handleable.js +0 -56
- package/dist/i-global-active-context.d.ts +0 -22
- package/dist/i-global-active-context.js +0 -2
- package/dist/i-handleable-error-options.d.ts +0 -6
- package/dist/i-handleable-error-options.js +0 -2
- package/dist/i-handleable.d.ts +0 -5
- package/dist/i-handleable.js +0 -2
- package/dist/i18n-config.d.ts +0 -20
- package/dist/i18n-config.js +0 -2
- package/dist/i18n-context.js +0 -2
- package/dist/i18n-engine.d.ts +0 -178
- package/dist/i18n-engine.js +0 -338
- package/dist/index.js +0 -83
- package/dist/language-codes.js +0 -31
- package/dist/language-definition.d.ts +0 -13
- package/dist/language-definition.js +0 -2
- package/dist/language-registry.d.ts +0 -113
- package/dist/language-registry.js +0 -216
- package/dist/plugin-i18n-engine.d.ts +0 -146
- package/dist/plugin-i18n-engine.js +0 -360
- package/dist/plugin-translatable-generic-error.d.ts +0 -29
- package/dist/plugin-translatable-generic-error.js +0 -66
- package/dist/plugin-translatable-handleable-generic.d.ts +0 -28
- package/dist/plugin-translatable-handleable-generic.js +0 -40
- package/dist/plugin-typed-handleable.d.ts +0 -14
- package/dist/plugin-typed-handleable.js +0 -45
- package/dist/registry-config.d.ts +0 -14
- package/dist/registry-config.js +0 -2
- package/dist/registry-error-type.d.ts +0 -12
- package/dist/registry-error-type.js +0 -16
- package/dist/registry-error.d.ts +0 -18
- package/dist/registry-error.js +0 -45
- package/dist/strict-types.d.ts +0 -18
- package/dist/strict-types.js +0 -17
- package/dist/template.d.ts +0 -12
- package/dist/template.js +0 -30
- package/dist/timezone.d.ts +0 -11
- package/dist/timezone.js +0 -22
- package/dist/translatable-generic-error.d.ts +0 -29
- package/dist/translatable-generic-error.js +0 -66
- package/dist/translatable-handleable-generic.d.ts +0 -28
- package/dist/translatable-handleable-generic.js +0 -40
- package/dist/translatable.d.ts +0 -5
- package/dist/translatable.js +0 -11
- package/dist/translation-engine.d.ts +0 -8
- package/dist/translation-engine.js +0 -2
- package/dist/translation-request.d.ts +0 -9
- package/dist/translation-request.js +0 -2
- package/dist/translation-response.d.ts +0 -8
- package/dist/translation-response.js +0 -2
- package/dist/typed-error.d.ts +0 -72
- package/dist/typed-error.js +0 -251
- package/dist/typed-handleable.d.ts +0 -14
- package/dist/typed-handleable.js +0 -40
- package/dist/types.js +0 -18
- package/dist/unified-translator.d.ts +0 -30
- package/dist/unified-translator.js +0 -68
- package/dist/utils.d.ts +0 -64
- package/dist/utils.js +0 -130
- package/dist/validation-config.d.ts +0 -11
- package/dist/validation-config.js +0 -2
- package/dist/validation-result.d.ts +0 -12
- package/dist/validation-result.js +0 -2
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { createContext } from './context';
|
|
2
|
+
import { EnumTranslationRegistry } from './enum-registry';
|
|
3
|
+
import { I18nConfig } from './i18n-config';
|
|
4
|
+
import { I18nContext } from './i18n-context';
|
|
5
|
+
import { createTemplateProcessor } from './template';
|
|
6
|
+
import { EnumLanguageTranslation, LanguageContextSpace } from './types';
|
|
7
|
+
import { isTemplate, replaceVariables } from './utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Internationalization engine class
|
|
11
|
+
*/
|
|
12
|
+
export class I18nEngine<
|
|
13
|
+
TStringKey extends string,
|
|
14
|
+
TLanguage extends string,
|
|
15
|
+
TConstants extends Record<string, any> = Record<string, any>,
|
|
16
|
+
TContext extends I18nContext<TLanguage> = I18nContext<
|
|
17
|
+
TLanguage
|
|
18
|
+
>,
|
|
19
|
+
> {
|
|
20
|
+
/**
|
|
21
|
+
* Registry for enum translations
|
|
22
|
+
*/
|
|
23
|
+
protected _enumRegistry: EnumTranslationRegistry<TStringKey, TLanguage>;
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for the i18n engine
|
|
26
|
+
*/
|
|
27
|
+
public readonly config: I18nConfig<
|
|
28
|
+
TStringKey,
|
|
29
|
+
TLanguage,
|
|
30
|
+
TConstants
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Static instances for semi-singleton pattern
|
|
35
|
+
*/
|
|
36
|
+
private static _instances = new Map<string, I18nEngine<any, any, any, any>>();
|
|
37
|
+
/**
|
|
38
|
+
* Default instance key (first created instance)
|
|
39
|
+
*/
|
|
40
|
+
private static _defaultKey: string | null = null;
|
|
41
|
+
/**
|
|
42
|
+
* Default instance key if none is provided
|
|
43
|
+
*/
|
|
44
|
+
protected static readonly DefaultInstanceKey = 'default';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Global context for translations (used if no context is provided) for this instance
|
|
48
|
+
*/
|
|
49
|
+
private _context: TContext;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default template processor instance
|
|
53
|
+
*/
|
|
54
|
+
public readonly t: (
|
|
55
|
+
str: string,
|
|
56
|
+
language?: TLanguage,
|
|
57
|
+
...otherVars: Record<string, string | number>[]
|
|
58
|
+
) => string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a new I18nEngine instance
|
|
62
|
+
* @param config The i18n configuration
|
|
63
|
+
* @param key Optional instance key for the semi-singleton pattern
|
|
64
|
+
* @throws Error if an instance with the same key already exists
|
|
65
|
+
*/
|
|
66
|
+
constructor(
|
|
67
|
+
config: I18nConfig<TStringKey, TLanguage, TConstants>,
|
|
68
|
+
key?: string,
|
|
69
|
+
newContext: () => TContext = () =>
|
|
70
|
+
createContext(
|
|
71
|
+
config.defaultLanguage,
|
|
72
|
+
config.defaultTranslationContext,
|
|
73
|
+
config.defaultCurrencyCode,
|
|
74
|
+
config.timezone,
|
|
75
|
+
config.adminTimezone,
|
|
76
|
+
) as TContext,
|
|
77
|
+
) {
|
|
78
|
+
this.validateConfig(config);
|
|
79
|
+
this.config = config;
|
|
80
|
+
this._enumRegistry = new EnumTranslationRegistry<TStringKey, TLanguage>(
|
|
81
|
+
Object.keys(config.strings) as TLanguage[],
|
|
82
|
+
(key: string, vars?: Record<string, any>) =>
|
|
83
|
+
this.safeTranslate(key as TStringKey, vars),
|
|
84
|
+
);
|
|
85
|
+
this._context = newContext();
|
|
86
|
+
|
|
87
|
+
const instanceKey = key || I18nEngine.DefaultInstanceKey;
|
|
88
|
+
if (I18nEngine._instances.has(instanceKey)) {
|
|
89
|
+
const existing = I18nEngine._instances.get(instanceKey)!;
|
|
90
|
+
throw new Error(
|
|
91
|
+
existing.translate('Error_InstanceAlreadyExistsTemplate' as any, {
|
|
92
|
+
key: instanceKey,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
I18nEngine._instances.set(instanceKey, this);
|
|
97
|
+
if (!I18nEngine._defaultKey) {
|
|
98
|
+
I18nEngine._defaultKey = instanceKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Initialize the default template processor
|
|
102
|
+
this.t = createTemplateProcessor(
|
|
103
|
+
this.config.enumObj || ({} as Record<string, TStringKey>),
|
|
104
|
+
(
|
|
105
|
+
key: TStringKey,
|
|
106
|
+
vars?: Record<string, string | number>,
|
|
107
|
+
language?: TLanguage,
|
|
108
|
+
) => this.translate(key, vars, language),
|
|
109
|
+
this.config.enumName || 'StringKey',
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gets an instance of the I18nEngine by key. If no key is provided, the default instance is returned.
|
|
115
|
+
* @param key The key of the instance to retrieve
|
|
116
|
+
* @returns The I18nEngine instance
|
|
117
|
+
* @throws Error if the instance with the provided key does not exist
|
|
118
|
+
*/
|
|
119
|
+
public static getInstance<T extends I18nEngine<any, any, any, any>>(
|
|
120
|
+
key?: string,
|
|
121
|
+
): T {
|
|
122
|
+
const instanceKey =
|
|
123
|
+
key || I18nEngine._defaultKey || I18nEngine.DefaultInstanceKey;
|
|
124
|
+
if (!instanceKey || !I18nEngine._instances.has(instanceKey)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
I18nEngine.getErrorMessage('Error_InstanceNotFoundTemplate', {
|
|
127
|
+
key: instanceKey,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return I18nEngine._instances.get(instanceKey) as T;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Gets a translation for the provided error key using the specified instance (or default instance if none is provided).
|
|
136
|
+
* @param errorKey The error key to translate
|
|
137
|
+
* @param vars Variables to replace in the translation string
|
|
138
|
+
* @param instanceKey The key of the I18nEngine instance to use
|
|
139
|
+
* @param language The language to translate to
|
|
140
|
+
* @param fallbackLanguage The fallback language if the translation is not found
|
|
141
|
+
* @returns The translated error message
|
|
142
|
+
*/
|
|
143
|
+
public static getErrorMessage(
|
|
144
|
+
errorKey: string,
|
|
145
|
+
vars?: Record<string, string | number>,
|
|
146
|
+
instanceKey?: string,
|
|
147
|
+
language?: string,
|
|
148
|
+
fallbackLanguage?: string,
|
|
149
|
+
): string {
|
|
150
|
+
try {
|
|
151
|
+
const instance = I18nEngine.getInstance(instanceKey);
|
|
152
|
+
return instance.translate(
|
|
153
|
+
errorKey as any,
|
|
154
|
+
vars,
|
|
155
|
+
language,
|
|
156
|
+
fallbackLanguage,
|
|
157
|
+
);
|
|
158
|
+
} catch {
|
|
159
|
+
return `${errorKey}: ${JSON.stringify(vars || {})}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Throws an error with a translated message using the specified instance (or default instance if none is provided).
|
|
165
|
+
* @param errorKey The error key to translate
|
|
166
|
+
* @param vars Variables to replace in the translation string
|
|
167
|
+
* @param instanceKey The key of the I18nEngine instance to use
|
|
168
|
+
* @throws Error with translated message
|
|
169
|
+
*/
|
|
170
|
+
public static throwError(
|
|
171
|
+
errorKey: string,
|
|
172
|
+
vars?: Record<string, string | number>,
|
|
173
|
+
instanceKey?: string,
|
|
174
|
+
): never {
|
|
175
|
+
throw new Error(I18nEngine.getErrorMessage(errorKey, vars, instanceKey));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Gets the global context for translations
|
|
180
|
+
* @returns The global context object
|
|
181
|
+
*/
|
|
182
|
+
get context(): TContext {
|
|
183
|
+
return this._context;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sets the global context for translations (used if no context is provided) for this instance
|
|
188
|
+
* @param context The context to set
|
|
189
|
+
*/
|
|
190
|
+
set context(context: Partial<TContext>) {
|
|
191
|
+
this._context = { ...this._context, ...context } as TContext;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Gets the enum translation registry for this engine instance
|
|
196
|
+
* @returns The enum translation registry
|
|
197
|
+
*/
|
|
198
|
+
get enumRegistry(): EnumTranslationRegistry<TStringKey, TLanguage> {
|
|
199
|
+
return this._enumRegistry;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Translates a string key into the specified language, replacing any variables as needed.
|
|
204
|
+
* @param key The string key to translate
|
|
205
|
+
* @param vars Variables to replace in the translation string
|
|
206
|
+
* @param language The language to translate to
|
|
207
|
+
* @param fallbackLanguage The fallback language if the translation is not found
|
|
208
|
+
* @returns The translated string
|
|
209
|
+
*/
|
|
210
|
+
translate(
|
|
211
|
+
key: TStringKey,
|
|
212
|
+
vars?: Record<string, string | number>,
|
|
213
|
+
language?: TLanguage,
|
|
214
|
+
fallbackLanguage?: TLanguage,
|
|
215
|
+
): string {
|
|
216
|
+
const lang =
|
|
217
|
+
language ??
|
|
218
|
+
(this._context.currentContext === 'admin'
|
|
219
|
+
? this._context.adminLanguage
|
|
220
|
+
: this._context.language);
|
|
221
|
+
const fallback = fallbackLanguage ?? this.config.defaultLanguage;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const stringValue = this.getString(lang, key);
|
|
225
|
+
let result = isTemplate(key)
|
|
226
|
+
? replaceVariables(stringValue, vars, this.config.constants)
|
|
227
|
+
: stringValue;
|
|
228
|
+
|
|
229
|
+
// Ensure result is always a string
|
|
230
|
+
if (typeof result !== 'string') {
|
|
231
|
+
result = String(result);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
} catch {
|
|
235
|
+
if (lang !== fallback) {
|
|
236
|
+
try {
|
|
237
|
+
const stringValue = this.getString(fallback, key);
|
|
238
|
+
let result = isTemplate(key)
|
|
239
|
+
? replaceVariables(stringValue, vars, this.config.constants)
|
|
240
|
+
: stringValue;
|
|
241
|
+
|
|
242
|
+
// Ensure result is always a string
|
|
243
|
+
if (typeof result !== 'string') {
|
|
244
|
+
result = String(result);
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
} catch {
|
|
248
|
+
return String(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return String(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Translates an enumeration value into the specified language.
|
|
257
|
+
* @param enumObj The enumeration object
|
|
258
|
+
* @param value The enumeration value to translate
|
|
259
|
+
* @param language The language to translate to
|
|
260
|
+
* @returns The translated enumeration value
|
|
261
|
+
*/
|
|
262
|
+
translateEnum<TEnum extends string | number>(
|
|
263
|
+
enumObj: Record<string, TEnum>,
|
|
264
|
+
value: TEnum,
|
|
265
|
+
language: TLanguage,
|
|
266
|
+
): string {
|
|
267
|
+
return this._enumRegistry.translate(enumObj, value, language);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Registers an enumeration and its translations with the engine.
|
|
272
|
+
* @param enumObj The enumeration object
|
|
273
|
+
* @param translations The translations for the enumeration
|
|
274
|
+
* @param enumName The name of the enumeration (for error messages)
|
|
275
|
+
*/
|
|
276
|
+
registerEnum<TEnum extends string | number>(
|
|
277
|
+
enumObj: Record<string, TEnum> | Record<string | number, string | number>,
|
|
278
|
+
translations: EnumLanguageTranslation<TEnum, TLanguage>,
|
|
279
|
+
enumName: string,
|
|
280
|
+
): void {
|
|
281
|
+
this._enumRegistry.register(
|
|
282
|
+
enumObj as Record<string, TEnum>,
|
|
283
|
+
translations,
|
|
284
|
+
enumName,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Safe translation that prevents infinite recursion for error messages
|
|
290
|
+
* @param key The string key to translate
|
|
291
|
+
* @param vars Variables to replace in the translation string
|
|
292
|
+
* @param language The language to translate to
|
|
293
|
+
* @returns The translated string or the key if translation fails
|
|
294
|
+
*/
|
|
295
|
+
public safeTranslate(
|
|
296
|
+
key: TStringKey,
|
|
297
|
+
vars?: Record<string, string | number>,
|
|
298
|
+
language?: TLanguage,
|
|
299
|
+
): string {
|
|
300
|
+
try {
|
|
301
|
+
const lang = language ?? this.config.defaultLanguage;
|
|
302
|
+
const strings = this.config.strings[lang];
|
|
303
|
+
if (!strings?.[key]) return String(key);
|
|
304
|
+
const stringValue = strings[key];
|
|
305
|
+
let result = isTemplate(key)
|
|
306
|
+
? replaceVariables(stringValue, vars, this.config.constants)
|
|
307
|
+
: stringValue;
|
|
308
|
+
|
|
309
|
+
// Ensure result is always a string
|
|
310
|
+
if (typeof result !== 'string') {
|
|
311
|
+
result = String(result);
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
} catch {
|
|
315
|
+
return String(key);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Retrieves the string for the given language and key, throwing an error if not found.
|
|
321
|
+
* @param language The language to get the string for
|
|
322
|
+
* @param key The string key to retrieve
|
|
323
|
+
* @returns The string value
|
|
324
|
+
* @throws Error if the language or string key is not found
|
|
325
|
+
*/
|
|
326
|
+
private getString(language: TLanguage, key: TStringKey): string {
|
|
327
|
+
const strings = this.config.strings[language];
|
|
328
|
+
if (!strings) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
this.safeTranslate('Error_LanguageNotFound' as any, { language }) ||
|
|
331
|
+
`Language not found: ${language}`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const value = strings[key];
|
|
336
|
+
if (!value) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
this.safeTranslate('Error_StringNotFound' as any, { key }) ||
|
|
339
|
+
`String not found: ${key}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Gets the language code for the specified language.
|
|
348
|
+
* @param language The language to get the code for
|
|
349
|
+
* @returns The language code
|
|
350
|
+
*/
|
|
351
|
+
getLanguageCode(language: TLanguage): string {
|
|
352
|
+
return this.config.languageCodes[language] || language;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Gets the language for the specified language code.
|
|
357
|
+
* @param code The language code to look up
|
|
358
|
+
* @returns The language, or undefined if not found
|
|
359
|
+
*/
|
|
360
|
+
getLanguageFromCode(code: string): TLanguage | undefined {
|
|
361
|
+
for (const [lang, langCode] of Object.entries(this.config.languageCodes)) {
|
|
362
|
+
if (langCode === code) {
|
|
363
|
+
return lang as TLanguage;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Gets all language codes.
|
|
371
|
+
* @returns A record of all language codes
|
|
372
|
+
*/
|
|
373
|
+
getAllLanguageCodes(): Record<TLanguage, string> {
|
|
374
|
+
return this.config.languageCodes as Record<TLanguage, string>;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Gets all available languages.
|
|
379
|
+
* @returns An array of all available languages
|
|
380
|
+
*/
|
|
381
|
+
getAvailableLanguages(): TLanguage[] {
|
|
382
|
+
return Object.keys(this.config.strings) as TLanguage[];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Checks if a language is available.
|
|
387
|
+
* @param language The language to check
|
|
388
|
+
* @returns True if the language is available, false otherwise
|
|
389
|
+
*/
|
|
390
|
+
isLanguageAvailable(language: string): language is TLanguage {
|
|
391
|
+
return Object.keys(this.config.strings).includes(language);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Clears all instances (for testing purposes)
|
|
396
|
+
* @internal
|
|
397
|
+
*/
|
|
398
|
+
public static clearInstances(): void {
|
|
399
|
+
I18nEngine._instances.clear();
|
|
400
|
+
I18nEngine._defaultKey = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Removes a specific instance by key
|
|
405
|
+
* @param key The key of the instance to remove
|
|
406
|
+
* @internal
|
|
407
|
+
*/
|
|
408
|
+
public static removeInstance(key?: string): void {
|
|
409
|
+
const instanceKey = key || I18nEngine.DefaultInstanceKey;
|
|
410
|
+
I18nEngine._instances.delete(instanceKey);
|
|
411
|
+
if (I18nEngine._defaultKey === instanceKey) {
|
|
412
|
+
const nextKey = I18nEngine._instances.keys().next().value;
|
|
413
|
+
I18nEngine._defaultKey =
|
|
414
|
+
I18nEngine._instances.size > 0 && nextKey ? nextKey : null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Static error message templates for validation
|
|
420
|
+
*/
|
|
421
|
+
private static readonly ErrorTemplates = {
|
|
422
|
+
MissingStringCollection:
|
|
423
|
+
'Missing string collection for language: {language}',
|
|
424
|
+
MissingTranslation:
|
|
425
|
+
"Missing translation for key '{key}' in language '{language}'",
|
|
426
|
+
DefaultLanguageNoCollection:
|
|
427
|
+
"Default language '{language}' has no string collection",
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Validates the configuration to ensure all languages have string collections
|
|
432
|
+
* and all string keys are provided for each language
|
|
433
|
+
* @param config The configuration to validate
|
|
434
|
+
* @throws Error if validation fails
|
|
435
|
+
*/
|
|
436
|
+
private validateConfig(
|
|
437
|
+
config: I18nConfig<TStringKey, TLanguage, TConstants>,
|
|
438
|
+
): void {
|
|
439
|
+
// Check that default language exists
|
|
440
|
+
if (!config.strings[config.defaultLanguage]) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
this.getValidationError(
|
|
443
|
+
'Error_DefaultLanguageNoCollectionTemplate' as any,
|
|
444
|
+
{ language: config.defaultLanguage },
|
|
445
|
+
config,
|
|
446
|
+
),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check that all string keys are provided for each language that has strings
|
|
451
|
+
for (const language of Object.keys(config.strings) as TLanguage[]) {
|
|
452
|
+
const strings = config.strings[language]!;
|
|
453
|
+
for (const stringKey of config.stringNames) {
|
|
454
|
+
if (!strings[stringKey]) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
this.getValidationError(
|
|
457
|
+
'Error_MissingTranslationTemplate' as any,
|
|
458
|
+
{ key: stringKey, language },
|
|
459
|
+
config,
|
|
460
|
+
),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Gets validation error message, trying translation first, falling back to template
|
|
469
|
+
*/
|
|
470
|
+
private getValidationError(
|
|
471
|
+
key: TStringKey,
|
|
472
|
+
vars: Record<string, any>,
|
|
473
|
+
config: I18nConfig<TStringKey, TLanguage, TConstants>,
|
|
474
|
+
): string {
|
|
475
|
+
try {
|
|
476
|
+
const strings = config.strings[config.defaultLanguage];
|
|
477
|
+
if (strings?.[key]) {
|
|
478
|
+
return replaceVariables(strings[key], vars, config.constants);
|
|
479
|
+
}
|
|
480
|
+
} catch {}
|
|
481
|
+
|
|
482
|
+
// Fallback to static templates
|
|
483
|
+
const template = key.includes('MissingStringCollection')
|
|
484
|
+
? I18nEngine.ErrorTemplates.MissingStringCollection
|
|
485
|
+
: key.includes('MissingTranslation')
|
|
486
|
+
? I18nEngine.ErrorTemplates.MissingTranslation
|
|
487
|
+
: I18nEngine.ErrorTemplates.DefaultLanguageNoCollection;
|
|
488
|
+
|
|
489
|
+
return replaceVariables(template, vars);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Legacy exports (for backwards compatibility)
|
|
1
2
|
export * from './active-context';
|
|
2
3
|
export * from './component-definition';
|
|
3
4
|
export * from './component-registration';
|
|
@@ -46,11 +47,19 @@ export * from './translation-response';
|
|
|
46
47
|
export * from './translatable';
|
|
47
48
|
export * from './create-translation-adapter';
|
|
48
49
|
export * from './unified-translator';
|
|
50
|
+
|
|
51
|
+
// Re-export for convenience
|
|
49
52
|
export { createCoreI18nEngine as createCoreI18n } from './core-i18n';
|
|
50
53
|
export { I18nEngine as I18n } from './i18n-engine';
|
|
51
54
|
export { PluginI18nEngine as PluginI18n } from './plugin-i18n-engine';
|
|
55
|
+
|
|
56
|
+
// Testing utilities
|
|
57
|
+
import { PluginI18nEngine } from './plugin-i18n-engine';
|
|
58
|
+
|
|
52
59
|
/**
|
|
53
60
|
* Reset all I18n engines and clear component registrations
|
|
54
61
|
* Useful for test cleanup
|
|
55
62
|
*/
|
|
56
|
-
export
|
|
63
|
+
export function resetAllI18nEngines(): void {
|
|
64
|
+
PluginI18nEngine.resetAll();
|
|
65
|
+
}
|
|
@@ -3,25 +3,37 @@
|
|
|
3
3
|
* These are provided as constants for convenience, but any string can be used as a language code.
|
|
4
4
|
* Site builders can use these or define their own custom language codes.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
6
|
+
export const LanguageCodes = {
|
|
7
|
+
EN_US: 'en-US',
|
|
8
|
+
EN_GB: 'en-GB',
|
|
9
|
+
FR: 'fr',
|
|
10
|
+
ES: 'es',
|
|
11
|
+
DE: 'de',
|
|
12
|
+
ZH_CN: 'zh-CN',
|
|
13
|
+
JA: 'ja',
|
|
14
|
+
UK: 'uk',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
16
17
|
/**
|
|
17
18
|
* Type representing any language code (string)
|
|
18
19
|
*/
|
|
19
20
|
export type LanguageCode = string;
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Type representing the common language codes provided by this library
|
|
22
24
|
*/
|
|
23
25
|
export type CommonLanguageCode = typeof LanguageCodes[keyof typeof LanguageCodes];
|
|
26
|
+
|
|
24
27
|
/**
|
|
25
28
|
* Helper to get display names for common language codes
|
|
26
29
|
*/
|
|
27
|
-
export
|
|
30
|
+
export const LanguageDisplayNames: Record<CommonLanguageCode, string> = {
|
|
31
|
+
[LanguageCodes.EN_US]: 'English (US)',
|
|
32
|
+
[LanguageCodes.EN_GB]: 'English (UK)',
|
|
33
|
+
[LanguageCodes.FR]: 'Français',
|
|
34
|
+
[LanguageCodes.ES]: 'Español',
|
|
35
|
+
[LanguageCodes.DE]: 'Deutsch',
|
|
36
|
+
[LanguageCodes.ZH_CN]: '中文',
|
|
37
|
+
[LanguageCodes.JA]: '日本語',
|
|
38
|
+
[LanguageCodes.UK]: 'Українська',
|
|
39
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language definition with its properties
|
|
3
|
+
*/
|
|
4
|
+
export interface LanguageDefinition {
|
|
5
|
+
/** Unique identifier for the language */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/** Display name in the native language */
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** ISO language code (e.g., 'en', 'fr', 'zh-CN') */
|
|
10
|
+
readonly code: string;
|
|
11
|
+
/** Whether this is the fallback/default language */
|
|
12
|
+
readonly isDefault?: boolean;
|
|
13
|
+
}
|