@digitaldefiance/i18n-lib 1.0.22 → 1.0.24
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 +135 -6
- package/dist/context.d.ts +1 -1
- package/dist/default-config.d.ts +31 -0
- package/dist/default-config.js +67 -0
- package/dist/i18n-engine.d.ts +20 -5
- package/dist/i18n-engine.js +55 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/package.json +52 -0
- package/dist/src/active-context.d.ts +29 -0
- package/dist/src/active-context.js +2 -0
- package/dist/src/context-manager.d.ts +33 -0
- package/dist/src/context-manager.js +61 -0
- package/dist/src/context.d.ts +43 -0
- package/dist/src/context.js +69 -0
- package/dist/src/currency-code.d.ts +19 -0
- package/dist/src/currency-code.js +36 -0
- package/dist/src/currency-format.d.ts +10 -0
- package/dist/src/currency-format.js +2 -0
- package/dist/src/currency.d.ts +11 -0
- package/dist/src/currency.js +48 -0
- package/dist/src/enum-registry.d.ts +35 -0
- package/dist/src/enum-registry.js +67 -0
- package/dist/src/i18n-engine.d.ts +156 -0
- package/dist/src/i18n-engine.js +267 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +32 -0
- package/dist/src/template.d.ts +12 -0
- package/dist/src/template.js +30 -0
- package/dist/src/timezone.d.ts +11 -0
- package/dist/src/timezone.js +22 -0
- package/dist/src/types.d.ts +78 -0
- package/dist/src/types.js +14 -0
- package/dist/src/utils.d.ts +41 -0
- package/dist/src/utils.js +85 -0
- package/package.json +2 -2
- package/dist/global-active-context.d.ts +0 -22
- package/dist/global-active-context.js +0 -73
package/README.md
CHANGED
|
@@ -5,12 +5,15 @@ A comprehensive TypeScript internationalization library with enum translation su
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Type-Safe Translations**: Full TypeScript support with generic types for strings and languages
|
|
8
|
+
- **Configuration Validation**: Automatic validation ensures all languages have complete string collections
|
|
9
|
+
- **Localized Error Messages**: Error messages can be translated using the engine's own translation system
|
|
8
10
|
- **Enum Translation Registry**: Translate enum values with complete type safety
|
|
9
11
|
- **Template Processing**: Advanced template system with `{{EnumName.EnumKey}}` patterns and variable replacement
|
|
10
12
|
- **Context Management**: Admin vs user translation contexts with automatic language switching
|
|
11
13
|
- **Singleton Pattern**: Efficient instance management with named instances
|
|
12
14
|
- **Currency Formatting**: Built-in currency formatting utilities with locale support
|
|
13
15
|
- **Fallback System**: Graceful degradation when translations are missing
|
|
16
|
+
- **Extensible Configuration**: Module augmentation support for layered library extension
|
|
14
17
|
- **Zero Dependencies**: Lightweight with no external dependencies
|
|
15
18
|
|
|
16
19
|
## Installation
|
|
@@ -22,7 +25,7 @@ npm install @digitaldefiance/i18n-lib
|
|
|
22
25
|
## Quick Start
|
|
23
26
|
|
|
24
27
|
```typescript
|
|
25
|
-
import { I18nEngine, I18nConfig, createContext } from '@digitaldefiance/i18n-lib';
|
|
28
|
+
import { I18nEngine, I18nConfig, createContext, CurrencyCode, Timezone } from '@digitaldefiance/i18n-lib';
|
|
26
29
|
|
|
27
30
|
// Define your enums
|
|
28
31
|
enum MyStrings {
|
|
@@ -49,12 +52,15 @@ const config: I18nConfig<MyStrings, MyLanguages> = {
|
|
|
49
52
|
}
|
|
50
53
|
},
|
|
51
54
|
defaultLanguage: MyLanguages.English,
|
|
52
|
-
|
|
55
|
+
defaultTranslationContext: 'user',
|
|
56
|
+
defaultCurrencyCode: new CurrencyCode('USD'),
|
|
53
57
|
languageCodes: {
|
|
54
58
|
[MyLanguages.English]: 'en',
|
|
55
59
|
[MyLanguages.Spanish]: 'es'
|
|
56
60
|
},
|
|
57
|
-
languages: Object.values(MyLanguages)
|
|
61
|
+
languages: Object.values(MyLanguages),
|
|
62
|
+
timezone: new Timezone('UTC'),
|
|
63
|
+
adminTimezone: new Timezone('UTC')
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
// Create engine
|
|
@@ -216,7 +222,7 @@ I18nEngine.removeInstance('main');
|
|
|
216
222
|
#### Translation Methods
|
|
217
223
|
- `translate(key, vars?, language?, fallbackLanguage?)` - Translate string with optional variables
|
|
218
224
|
- `translateEnum(enumObj, value, language)` - Translate enum value
|
|
219
|
-
- `t(templateString, language?, ...vars)` - Process template with
|
|
225
|
+
- `t(templateString, language?, ...vars)` - Process template string with `{{EnumName.EnumKey}}` patterns
|
|
220
226
|
|
|
221
227
|
#### Registration
|
|
222
228
|
- `registerEnum(enumObj, translations, enumName)` - Register enum translations
|
|
@@ -266,16 +272,19 @@ type EnumLanguageTranslation<T extends string | number, TLanguage extends string
|
|
|
266
272
|
[L in TLanguage]: EnumTranslation<T>;
|
|
267
273
|
}>;
|
|
268
274
|
|
|
269
|
-
interface I18nConfig<TStringKey, TLanguage, TConstants?,
|
|
275
|
+
interface I18nConfig<TStringKey, TLanguage, TConstants?, TTranslationContext?> {
|
|
270
276
|
stringNames: TStringKey[];
|
|
271
277
|
strings: MasterStringsCollection<TStringKey, TLanguage>;
|
|
272
278
|
defaultLanguage: TLanguage;
|
|
273
|
-
|
|
279
|
+
defaultTranslationContext: TTranslationContext;
|
|
280
|
+
defaultCurrencyCode: CurrencyCode;
|
|
274
281
|
languageCodes: LanguageCodeCollection<TLanguage>;
|
|
275
282
|
languages: TLanguage[];
|
|
276
283
|
constants?: TConstants;
|
|
277
284
|
enumName?: string;
|
|
278
285
|
enumObj?: Record<string, TStringKey>;
|
|
286
|
+
timezone: Timezone;
|
|
287
|
+
adminTimezone: Timezone;
|
|
279
288
|
}
|
|
280
289
|
```
|
|
281
290
|
|
|
@@ -293,6 +302,124 @@ yarn test enum-registry.spec.ts
|
|
|
293
302
|
yarn test i18n-engine.spec.ts
|
|
294
303
|
```
|
|
295
304
|
|
|
305
|
+
## Extensible Configuration
|
|
306
|
+
|
|
307
|
+
The library supports layered extension across multiple libraries using TypeScript module augmentation:
|
|
308
|
+
|
|
309
|
+
### Base Library Setup
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// In your base library
|
|
313
|
+
import { DefaultStringKey, DefaultLanguage } from '@digitaldefiance/i18n-lib';
|
|
314
|
+
|
|
315
|
+
enum MyLibStringKey {
|
|
316
|
+
Feature_Save = 'feature_save',
|
|
317
|
+
Feature_Load = 'feature_load',
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Extend the global configuration
|
|
321
|
+
declare global {
|
|
322
|
+
namespace I18n {
|
|
323
|
+
interface Config {
|
|
324
|
+
StringKey: DefaultStringKey | MyLibStringKey;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Extending Another Library
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// In a library that extends the base library
|
|
334
|
+
import { DefaultStringKey } from '@digitaldefiance/i18n-lib';
|
|
335
|
+
import { MyLibStringKey } from 'my-base-lib';
|
|
336
|
+
|
|
337
|
+
enum AdvancedStringKey {
|
|
338
|
+
Advanced_Export = 'advanced_export',
|
|
339
|
+
Advanced_Import = 'advanced_import',
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Extend with union types
|
|
343
|
+
declare global {
|
|
344
|
+
namespace I18n {
|
|
345
|
+
interface Config {
|
|
346
|
+
StringKey: DefaultStringKey | MyLibStringKey | AdvancedStringKey;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Final Application
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// In your final application
|
|
356
|
+
import { StringKey, Language, getI18nEngine } from 'my-extended-lib';
|
|
357
|
+
|
|
358
|
+
// All string keys from all layers are now available
|
|
359
|
+
const engine = getI18nEngine();
|
|
360
|
+
const translation = engine.translate('advanced_export' as StringKey);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Default Configuration Helper
|
|
364
|
+
|
|
365
|
+
Use the default configuration helper for quick setup:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { getDefaultI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
369
|
+
|
|
370
|
+
// Create engine with default configuration
|
|
371
|
+
const engine = getDefaultI18nEngine(
|
|
372
|
+
{ APP_NAME: 'MyApp' }, // constants
|
|
373
|
+
new Timezone('America/New_York'), // user timezone
|
|
374
|
+
new Timezone('UTC') // admin timezone
|
|
375
|
+
);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Configuration Validation
|
|
379
|
+
|
|
380
|
+
The engine automatically validates configurations during construction to ensure completeness:
|
|
381
|
+
|
|
382
|
+
### Validation Rules
|
|
383
|
+
|
|
384
|
+
1. **All languages in `languageCodes` must have corresponding `strings` collections**
|
|
385
|
+
2. **All `stringNames` must be present in every language's string collection**
|
|
386
|
+
3. **The `defaultLanguage` must have a string collection**
|
|
387
|
+
|
|
388
|
+
### Localized Error Messages
|
|
389
|
+
|
|
390
|
+
Validation errors can be localized by including error message keys in your configuration:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
enum MyStrings {
|
|
394
|
+
Welcome = 'welcome',
|
|
395
|
+
// Error message keys
|
|
396
|
+
Error_MissingStringCollectionTemplate = 'error_missing_string_collection_template',
|
|
397
|
+
Error_MissingTranslationTemplate = 'error_missing_translation_template',
|
|
398
|
+
Error_DefaultLanguageNoCollectionTemplate = 'error_default_language_no_collection_template'
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const config: I18nConfig<MyStrings, MyLanguages> = {
|
|
402
|
+
stringNames: Object.values(MyStrings),
|
|
403
|
+
strings: {
|
|
404
|
+
[MyLanguages.English]: {
|
|
405
|
+
[MyStrings.Welcome]: 'Welcome!',
|
|
406
|
+
[MyStrings.Error_MissingStringCollectionTemplate]: 'Missing translations for language: {language}',
|
|
407
|
+
[MyStrings.Error_MissingTranslationTemplate]: 'Key \'{key}\' not found in {language}',
|
|
408
|
+
[MyStrings.Error_DefaultLanguageNoCollectionTemplate]: 'Default language \'{language}\' has no translations'
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
// ... rest of config
|
|
412
|
+
};
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Fallback Error Messages
|
|
416
|
+
|
|
417
|
+
If localized error messages aren't provided, the engine falls back to English templates:
|
|
418
|
+
|
|
419
|
+
- `Missing string collection for language: {language}`
|
|
420
|
+
- `Missing translation for key '{key}' in language '{language}'`
|
|
421
|
+
- `Default language '{language}' has no string collection`
|
|
422
|
+
|
|
296
423
|
## Best Practices
|
|
297
424
|
|
|
298
425
|
1. **Complete Translations**: EnumTranslation requires all enum values to be translated
|
|
@@ -300,6 +427,8 @@ yarn test i18n-engine.spec.ts
|
|
|
300
427
|
3. **Context Separation**: Use different contexts for admin and user interfaces
|
|
301
428
|
4. **Instance Management**: Use named instances for different parts of your application
|
|
302
429
|
5. **Error Handling**: Handle missing translations gracefully with fallback languages
|
|
430
|
+
6. **Layered Extension**: Use union types when extending configurations across libraries
|
|
431
|
+
7. **Default Configuration**: Use `getDefaultI18nEngine()` for standard setups
|
|
303
432
|
|
|
304
433
|
## License
|
|
305
434
|
|
package/dist/context.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { I18nContext, LanguageContext } from './types';
|
|
|
10
10
|
* @param defaultAdminTimezone - The default admin timezone (defaults to UTC)
|
|
11
11
|
* @returns A new I18nContext instance
|
|
12
12
|
*/
|
|
13
|
-
export declare function createContext<TLanguage extends string,
|
|
13
|
+
export declare function createContext<TLanguage extends string, TTranslationContext extends string = LanguageContext, TContext extends I18nContext<TLanguage, TTranslationContext> = I18nContext<TLanguage, TTranslationContext>>(defaultLanguage: TLanguage, defaultContext: TTranslationContext, defaultCurrencyCode?: CurrencyCode, defaultTimezone?: Timezone, defaultAdminTimezone?: Timezone): TContext;
|
|
14
14
|
/**
|
|
15
15
|
* Sets the language for the given I18n context.
|
|
16
16
|
* @param context - The I18n context to modify
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { I18nEngine } from './i18n-engine';
|
|
2
|
+
import { Timezone } from './timezone';
|
|
3
|
+
import { I18nContext, LanguageCodeCollection } from './types';
|
|
4
|
+
export declare enum DefaultStringKey {
|
|
5
|
+
Common_Test = "common_test"
|
|
6
|
+
}
|
|
7
|
+
export declare enum DefaultLanguage {
|
|
8
|
+
EnglishUS = "English (US)",
|
|
9
|
+
EnglishUK = "English (UK)",
|
|
10
|
+
French = "Fran\u00E7ais",
|
|
11
|
+
MandarinChinese = "\u4E2D\u6587",
|
|
12
|
+
Spanish = "Espa\u00F1ol",
|
|
13
|
+
Ukrainian = "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0438\u0439"
|
|
14
|
+
}
|
|
15
|
+
export declare const DefaultLanguageCodes: LanguageCodeCollection<DefaultLanguage>;
|
|
16
|
+
declare global {
|
|
17
|
+
namespace I18n {
|
|
18
|
+
interface Config {
|
|
19
|
+
StringKey: DefaultStringKey;
|
|
20
|
+
Language: DefaultLanguage;
|
|
21
|
+
LanguageCodes: LanguageCodeCollection<DefaultLanguage>;
|
|
22
|
+
engine: I18nEngine<I18n.Config['StringKey'], I18n.Config['Language'], Record<any, any>, string>;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export type StringKey = I18n.Config['StringKey'];
|
|
27
|
+
export type Language = I18n.Config['Language'];
|
|
28
|
+
export type Engine = I18n.Config['engine'];
|
|
29
|
+
export type LanguageCodes = I18n.Config['LanguageCodes'];
|
|
30
|
+
export declare const getI18nEngine: () => Engine;
|
|
31
|
+
export declare const getDefaultI18nEngine: <TConstants extends Record<string, any>, TTranslationContext extends string, TContext extends I18nContext<DefaultLanguage, TTranslationContext>>(constants: TConstants, timezone?: Timezone, adminTimezone?: Timezone) => I18nEngine<DefaultStringKey, DefaultLanguage, TConstants, TTranslationContext, TContext>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDefaultI18nEngine = exports.getI18nEngine = exports.DefaultLanguageCodes = exports.DefaultLanguage = exports.DefaultStringKey = void 0;
|
|
4
|
+
const currency_code_1 = require("./currency-code");
|
|
5
|
+
const i18n_engine_1 = require("./i18n-engine");
|
|
6
|
+
const timezone_1 = require("./timezone");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
// Default enum types that can be augmented by consumers
|
|
9
|
+
var DefaultStringKey;
|
|
10
|
+
(function (DefaultStringKey) {
|
|
11
|
+
DefaultStringKey["Common_Test"] = "common_test";
|
|
12
|
+
})(DefaultStringKey || (exports.DefaultStringKey = DefaultStringKey = {}));
|
|
13
|
+
var DefaultLanguage;
|
|
14
|
+
(function (DefaultLanguage) {
|
|
15
|
+
DefaultLanguage["EnglishUS"] = "English (US)";
|
|
16
|
+
DefaultLanguage["EnglishUK"] = "English (UK)";
|
|
17
|
+
DefaultLanguage["French"] = "Fran\u00E7ais";
|
|
18
|
+
DefaultLanguage["MandarinChinese"] = "\u4E2D\u6587";
|
|
19
|
+
DefaultLanguage["Spanish"] = "Espa\u00F1ol";
|
|
20
|
+
DefaultLanguage["Ukrainian"] = "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0438\u0439";
|
|
21
|
+
})(DefaultLanguage || (exports.DefaultLanguage = DefaultLanguage = {}));
|
|
22
|
+
exports.DefaultLanguageCodes = {
|
|
23
|
+
[DefaultLanguage.EnglishUS]: 'en',
|
|
24
|
+
[DefaultLanguage.EnglishUK]: 'en-GB',
|
|
25
|
+
[DefaultLanguage.French]: 'fr',
|
|
26
|
+
[DefaultLanguage.MandarinChinese]: 'zh-CN',
|
|
27
|
+
[DefaultLanguage.Spanish]: 'es',
|
|
28
|
+
[DefaultLanguage.Ukrainian]: 'uk',
|
|
29
|
+
};
|
|
30
|
+
// Singleton instance that uses the augmented types
|
|
31
|
+
const getI18nEngine = () => i18n_engine_1.I18nEngine.getInstance();
|
|
32
|
+
exports.getI18nEngine = getI18nEngine;
|
|
33
|
+
const getConfig = (constants, timezone, adminTimezone) => ({
|
|
34
|
+
strings: {
|
|
35
|
+
[DefaultLanguage.EnglishUS]: {
|
|
36
|
+
[DefaultStringKey.Common_Test]: 'Test',
|
|
37
|
+
},
|
|
38
|
+
[DefaultLanguage.EnglishUK]: {
|
|
39
|
+
[DefaultStringKey.Common_Test]: 'Test',
|
|
40
|
+
},
|
|
41
|
+
[DefaultLanguage.French]: {
|
|
42
|
+
[DefaultStringKey.Common_Test]: 'Test',
|
|
43
|
+
},
|
|
44
|
+
[DefaultLanguage.MandarinChinese]: {
|
|
45
|
+
[DefaultStringKey.Common_Test]: '测试',
|
|
46
|
+
},
|
|
47
|
+
[DefaultLanguage.Spanish]: {
|
|
48
|
+
[DefaultStringKey.Common_Test]: 'Prueba',
|
|
49
|
+
},
|
|
50
|
+
[DefaultLanguage.Ukrainian]: {
|
|
51
|
+
[DefaultStringKey.Common_Test]: 'Тест',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
stringNames: Object.values(DefaultStringKey),
|
|
55
|
+
defaultLanguage: DefaultLanguage.EnglishUS,
|
|
56
|
+
defaultTranslationContext: 'user',
|
|
57
|
+
defaultCurrencyCode: new currency_code_1.CurrencyCode(types_1.DefaultCurrencyCode),
|
|
58
|
+
languageCodes: exports.DefaultLanguageCodes,
|
|
59
|
+
languages: Object.values(DefaultLanguage),
|
|
60
|
+
constants: constants,
|
|
61
|
+
enumName: 'DefaultStringKey',
|
|
62
|
+
enumObj: DefaultStringKey,
|
|
63
|
+
timezone: timezone ?? new timezone_1.Timezone('UTC'),
|
|
64
|
+
adminTimezone: adminTimezone ?? new timezone_1.Timezone('UTC'),
|
|
65
|
+
});
|
|
66
|
+
const getDefaultI18nEngine = (constants, timezone, adminTimezone) => new i18n_engine_1.I18nEngine(getConfig(constants, timezone, adminTimezone), 'user');
|
|
67
|
+
exports.getDefaultI18nEngine = getDefaultI18nEngine;
|
package/dist/i18n-engine.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { EnumLanguageTranslation, I18nConfig, I18nContext } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Internationalization engine class
|
|
5
5
|
*/
|
|
6
|
-
export declare class I18nEngine<TStringKey extends string, TLanguage extends string, TConstants extends Record<string, any> = Record<string, any>,
|
|
6
|
+
export declare class I18nEngine<TStringKey extends string, TLanguage extends string, TConstants extends Record<string, any> = Record<string, any>, TTranslationContext extends string = string, TContext extends I18nContext<TLanguage, TTranslationContext> = I18nContext<TLanguage, TTranslationContext>> {
|
|
7
7
|
/**
|
|
8
8
|
* Registry for enum translations
|
|
9
9
|
*/
|
|
@@ -11,7 +11,7 @@ export declare class I18nEngine<TStringKey extends string, TLanguage extends str
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration for the i18n engine
|
|
13
13
|
*/
|
|
14
|
-
readonly config: I18nConfig<TStringKey, TLanguage, TConstants,
|
|
14
|
+
readonly config: I18nConfig<TStringKey, TLanguage, TConstants, TTranslationContext>;
|
|
15
15
|
/**
|
|
16
16
|
* Static instances for semi-singleton pattern
|
|
17
17
|
*/
|
|
@@ -38,7 +38,7 @@ export declare class I18nEngine<TStringKey extends string, TLanguage extends str
|
|
|
38
38
|
* @param key Optional instance key for the semi-singleton pattern
|
|
39
39
|
* @throws Error if an instance with the same key already exists
|
|
40
40
|
*/
|
|
41
|
-
constructor(config: I18nConfig<TStringKey, TLanguage, TConstants,
|
|
41
|
+
constructor(config: I18nConfig<TStringKey, TLanguage, TConstants, TTranslationContext>, key?: string, newContext?: () => TContext);
|
|
42
42
|
/**
|
|
43
43
|
* Gets an instance of the I18nEngine by key. If no key is provided, the default instance is returned.
|
|
44
44
|
* @param key The key of the instance to retrieve
|
|
@@ -68,12 +68,12 @@ export declare class I18nEngine<TStringKey extends string, TLanguage extends str
|
|
|
68
68
|
* Gets the global context for translations
|
|
69
69
|
* @returns The global context object
|
|
70
70
|
*/
|
|
71
|
-
get context():
|
|
71
|
+
get context(): TContext;
|
|
72
72
|
/**
|
|
73
73
|
* Sets the global context for translations (used if no context is provided) for this instance
|
|
74
74
|
* @param context The context to set
|
|
75
75
|
*/
|
|
76
|
-
set context(context: Partial<
|
|
76
|
+
set context(context: Partial<TContext>);
|
|
77
77
|
/**
|
|
78
78
|
* Translates a string key into the specified language, replacing any variables as needed.
|
|
79
79
|
* @param key The string key to translate
|
|
@@ -153,4 +153,19 @@ export declare class I18nEngine<TStringKey extends string, TLanguage extends str
|
|
|
153
153
|
* @internal
|
|
154
154
|
*/
|
|
155
155
|
static removeInstance(key?: string): void;
|
|
156
|
+
/**
|
|
157
|
+
* Static error message templates for validation
|
|
158
|
+
*/
|
|
159
|
+
private static readonly ErrorTemplates;
|
|
160
|
+
/**
|
|
161
|
+
* Validates the configuration to ensure all languages have string collections
|
|
162
|
+
* and all string keys are provided for each language
|
|
163
|
+
* @param config The configuration to validate
|
|
164
|
+
* @throws Error if validation fails
|
|
165
|
+
*/
|
|
166
|
+
private validateConfig;
|
|
167
|
+
/**
|
|
168
|
+
* Gets validation error message, trying translation first, falling back to template
|
|
169
|
+
*/
|
|
170
|
+
private getValidationError;
|
|
156
171
|
}
|
package/dist/i18n-engine.js
CHANGED
|
@@ -15,10 +15,11 @@ class I18nEngine {
|
|
|
15
15
|
* @param key Optional instance key for the semi-singleton pattern
|
|
16
16
|
* @throws Error if an instance with the same key already exists
|
|
17
17
|
*/
|
|
18
|
-
constructor(config, key) {
|
|
18
|
+
constructor(config, key, newContext = () => (0, context_1.createContext)(config.defaultLanguage, config.defaultTranslationContext, config.defaultCurrencyCode, config.timezone, config.adminTimezone)) {
|
|
19
|
+
this.validateConfig(config);
|
|
19
20
|
this.config = config;
|
|
20
21
|
this.enumRegistry = new enum_registry_1.EnumTranslationRegistry();
|
|
21
|
-
this._context = (
|
|
22
|
+
this._context = newContext();
|
|
22
23
|
const instanceKey = key || I18nEngine.DefaultInstanceKey;
|
|
23
24
|
if (I18nEngine._instances.has(instanceKey)) {
|
|
24
25
|
const existing = I18nEngine._instances.get(instanceKey);
|
|
@@ -247,9 +248,52 @@ class I18nEngine {
|
|
|
247
248
|
I18nEngine._instances.delete(instanceKey);
|
|
248
249
|
if (I18nEngine._defaultKey === instanceKey) {
|
|
249
250
|
const nextKey = I18nEngine._instances.keys().next().value;
|
|
250
|
-
I18nEngine._defaultKey =
|
|
251
|
+
I18nEngine._defaultKey =
|
|
252
|
+
I18nEngine._instances.size > 0 && nextKey ? nextKey : null;
|
|
251
253
|
}
|
|
252
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Validates the configuration to ensure all languages have string collections
|
|
257
|
+
* and all string keys are provided for each language
|
|
258
|
+
* @param config The configuration to validate
|
|
259
|
+
* @throws Error if validation fails
|
|
260
|
+
*/
|
|
261
|
+
validateConfig(config) {
|
|
262
|
+
// Check that all languages in languageCodes have string collections
|
|
263
|
+
for (const language of Object.keys(config.languageCodes)) {
|
|
264
|
+
if (!config.strings[language]) {
|
|
265
|
+
throw new Error(this.getValidationError('Error_MissingStringCollectionTemplate', { language }, config));
|
|
266
|
+
}
|
|
267
|
+
// Check that all string keys are provided for this language
|
|
268
|
+
const strings = config.strings[language];
|
|
269
|
+
for (const stringKey of config.stringNames) {
|
|
270
|
+
if (!strings[stringKey]) {
|
|
271
|
+
throw new Error(this.getValidationError('Error_MissingTranslationTemplate', { key: stringKey, language }, config));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Check that default language exists
|
|
276
|
+
if (!config.strings[config.defaultLanguage]) {
|
|
277
|
+
throw new Error(this.getValidationError('Error_DefaultLanguageNoCollectionTemplate', { language: config.defaultLanguage }, config));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Gets validation error message, trying translation first, falling back to template
|
|
282
|
+
*/
|
|
283
|
+
getValidationError(key, vars, config) {
|
|
284
|
+
try {
|
|
285
|
+
const strings = config.strings[config.defaultLanguage];
|
|
286
|
+
if (strings?.[key]) {
|
|
287
|
+
return (0, utils_1.replaceVariables)(strings[key], vars, config.constants);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch { }
|
|
291
|
+
// Fallback to static templates
|
|
292
|
+
const template = key.includes('MissingStringCollection') ? I18nEngine.ErrorTemplates.MissingStringCollection :
|
|
293
|
+
key.includes('MissingTranslation') ? I18nEngine.ErrorTemplates.MissingTranslation :
|
|
294
|
+
I18nEngine.ErrorTemplates.DefaultLanguageNoCollection;
|
|
295
|
+
return (0, utils_1.replaceVariables)(template, vars);
|
|
296
|
+
}
|
|
253
297
|
}
|
|
254
298
|
exports.I18nEngine = I18nEngine;
|
|
255
299
|
/**
|
|
@@ -264,3 +308,11 @@ I18nEngine._defaultKey = null;
|
|
|
264
308
|
* Default instance key if none is provided
|
|
265
309
|
*/
|
|
266
310
|
I18nEngine.DefaultInstanceKey = 'default';
|
|
311
|
+
/**
|
|
312
|
+
* Static error message templates for validation
|
|
313
|
+
*/
|
|
314
|
+
I18nEngine.ErrorTemplates = {
|
|
315
|
+
MissingStringCollection: 'Missing string collection for language: {language}',
|
|
316
|
+
MissingTranslation: 'Missing translation for key \'{key}\' in language \'{language}\'',
|
|
317
|
+
DefaultLanguageNoCollection: 'Default language \'{language}\' has no string collection'
|
|
318
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ __exportStar(require("./currency"), exports);
|
|
|
22
22
|
__exportStar(require("./currency-code"), exports);
|
|
23
23
|
__exportStar(require("./currency-format"), exports);
|
|
24
24
|
__exportStar(require("./enum-registry"), exports);
|
|
25
|
+
__exportStar(require("./default-config"), exports);
|
|
25
26
|
__exportStar(require("./i18n-engine"), exports);
|
|
26
27
|
__exportStar(require("./template"), exports);
|
|
27
28
|
__exportStar(require("./timezone"), exports);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@digitaldefiance/i18n-lib",
|
|
3
|
+
"version": "1.0.23",
|
|
4
|
+
"description": "Generic i18n library with enum translation support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "yarn tsc",
|
|
9
|
+
"test": "yarn jest --detectOpenHandles",
|
|
10
|
+
"lint": "eslint src/**/*.ts tests/**/*.ts",
|
|
11
|
+
"lint:fix": "eslint src/**/*.ts tests/**/*.ts --fix",
|
|
12
|
+
"prettier:check": "prettier --check 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}'",
|
|
13
|
+
"prettier:fix": "prettier --write 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}'",
|
|
14
|
+
"format": "yarn prettier:fix && yarn lint:fix",
|
|
15
|
+
"prepublishOnly": "yarn build",
|
|
16
|
+
"publish:public": "npm publish --access public"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/jest": "^29.0.0",
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
|
21
|
+
"@typescript-eslint/parser": "^8.31.1",
|
|
22
|
+
"eslint": "^9.8.0",
|
|
23
|
+
"eslint-config-prettier": "^10.1.2",
|
|
24
|
+
"eslint-plugin-import": "^2.32.0",
|
|
25
|
+
"eslint-plugin-prettier": "^5.3.1",
|
|
26
|
+
"jest": "^29.0.0",
|
|
27
|
+
"jest-util": "^30.0.5",
|
|
28
|
+
"prettier": "^2.6.2",
|
|
29
|
+
"prettier-plugin-organize-imports": "^4.1.0",
|
|
30
|
+
"ts-jest": "^29.0.0",
|
|
31
|
+
"typescript": "^5.9.2"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"keywords": [
|
|
38
|
+
"i18n",
|
|
39
|
+
"internationalization",
|
|
40
|
+
"typescript",
|
|
41
|
+
"enum",
|
|
42
|
+
"translation"
|
|
43
|
+
],
|
|
44
|
+
"author": "Digital Defiance",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"packageManager": "yarn@4.10.3",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"moment": "^2.30.1",
|
|
49
|
+
"moment-timezone": "^0.6.0"
|
|
50
|
+
},
|
|
51
|
+
"type": "commonjs"
|
|
52
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LanguageContext } from './types';
|
|
2
|
+
import { Timezone } from './timezone';
|
|
3
|
+
import { CurrencyCode } from './currency-code';
|
|
4
|
+
export interface IActiveContext<TLanguage extends string> {
|
|
5
|
+
/**
|
|
6
|
+
* The default language for the user facing application
|
|
7
|
+
*/
|
|
8
|
+
language: TLanguage;
|
|
9
|
+
/**
|
|
10
|
+
* The default language for the admin interface
|
|
11
|
+
*/
|
|
12
|
+
adminLanguage: TLanguage;
|
|
13
|
+
/**
|
|
14
|
+
* The default currency code for the user facing application
|
|
15
|
+
*/
|
|
16
|
+
currencyCode: CurrencyCode;
|
|
17
|
+
/**
|
|
18
|
+
* The default language context for the current context
|
|
19
|
+
*/
|
|
20
|
+
currentContext: LanguageContext;
|
|
21
|
+
/**
|
|
22
|
+
* The default timezone for the user facing application
|
|
23
|
+
*/
|
|
24
|
+
timezone: Timezone;
|
|
25
|
+
/**
|
|
26
|
+
* The default timezone for the admin interface
|
|
27
|
+
*/
|
|
28
|
+
adminTimezone: Timezone;
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context change management for i18n systems
|
|
3
|
+
*/
|
|
4
|
+
export type ContextChangeListener<T = any> = (property: string, oldValue: T, newValue: T) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Manages context changes and notifies listeners.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ContextManager<TContext extends Record<string, any>> {
|
|
9
|
+
protected listeners: ContextChangeListener[];
|
|
10
|
+
/**
|
|
11
|
+
* Adds a listener to be notified of context changes.
|
|
12
|
+
* @param listener - The listener function to add
|
|
13
|
+
*/
|
|
14
|
+
addListener(listener: ContextChangeListener): void;
|
|
15
|
+
/**
|
|
16
|
+
* Removes a listener from the notification list.
|
|
17
|
+
* @param listener - The listener function to remove
|
|
18
|
+
*/
|
|
19
|
+
removeListener(listener: ContextChangeListener): void;
|
|
20
|
+
/**
|
|
21
|
+
* Notifies all listeners of a context change.
|
|
22
|
+
* @param property - The property that changed
|
|
23
|
+
* @param oldValue - The old value of the property
|
|
24
|
+
* @param newValue - The new value of the property
|
|
25
|
+
*/
|
|
26
|
+
notifyChange<K extends keyof TContext>(property: K, oldValue: TContext[K], newValue: TContext[K]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Creates a proxy for the given context to automatically notify listeners on changes.
|
|
29
|
+
* @param context - The context object to proxy
|
|
30
|
+
* @returns A proxied version of the context object
|
|
31
|
+
*/
|
|
32
|
+
createProxy(context: TContext): TContext;
|
|
33
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Manages context changes and notifies listeners.
|
|
6
|
+
*/
|
|
7
|
+
class ContextManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.listeners = [];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Adds a listener to be notified of context changes.
|
|
13
|
+
* @param listener - The listener function to add
|
|
14
|
+
*/
|
|
15
|
+
addListener(listener) {
|
|
16
|
+
this.listeners.push(listener);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Removes a listener from the notification list.
|
|
20
|
+
* @param listener - The listener function to remove
|
|
21
|
+
*/
|
|
22
|
+
removeListener(listener) {
|
|
23
|
+
const index = this.listeners.indexOf(listener);
|
|
24
|
+
if (index > -1) {
|
|
25
|
+
this.listeners.splice(index, 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Notifies all listeners of a context change.
|
|
30
|
+
* @param property - The property that changed
|
|
31
|
+
* @param oldValue - The old value of the property
|
|
32
|
+
* @param newValue - The new value of the property
|
|
33
|
+
*/
|
|
34
|
+
notifyChange(property, oldValue, newValue) {
|
|
35
|
+
this.listeners.forEach((listener) => {
|
|
36
|
+
try {
|
|
37
|
+
listener(property, oldValue, newValue);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Error in context change listener:', error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a proxy for the given context to automatically notify listeners on changes.
|
|
46
|
+
* @param context - The context object to proxy
|
|
47
|
+
* @returns A proxied version of the context object
|
|
48
|
+
*/
|
|
49
|
+
createProxy(context) {
|
|
50
|
+
const manager = this;
|
|
51
|
+
return new Proxy(context, {
|
|
52
|
+
set(target, property, value) {
|
|
53
|
+
const oldValue = target[property];
|
|
54
|
+
target[property] = value;
|
|
55
|
+
manager.notifyChange(property, oldValue, value);
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.ContextManager = ContextManager;
|