@digitaldefiance/i18n-lib 1.1.5 → 1.1.7
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 +124 -4
- package/dist/component-registry.js +2 -2
- package/dist/core-i18n.d.ts +1 -0
- package/dist/core-i18n.js +7 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin-i18n-engine.d.ts +4 -0
- package/dist/plugin-i18n-engine.js +21 -0
- package/dist/translatable-generic-error.d.ts +29 -0
- package/dist/translatable-generic-error.js +66 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -423,6 +423,95 @@ For complete documentation on the plugin architecture, see [PLUGIN_ARCHITECTURE.
|
|
|
423
423
|
|
|
424
424
|
## Advanced Features
|
|
425
425
|
|
|
426
|
+
### Translatable Errors
|
|
427
|
+
|
|
428
|
+
The `TranslatableGenericError` class provides a simple way to create errors with translated messages that work across any component:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { TranslatableGenericError, CoreStringKey, CoreLanguage } from '@digitaldefiance/i18n-lib';
|
|
432
|
+
|
|
433
|
+
// Define your error string keys
|
|
434
|
+
enum UserErrorKey {
|
|
435
|
+
UserNotFound = 'userNotFound',
|
|
436
|
+
InvalidCredentials = 'invalidCredentials',
|
|
437
|
+
AccountLocked = 'accountLocked',
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Register your component with translations
|
|
441
|
+
const userErrorComponent = {
|
|
442
|
+
id: 'user-errors',
|
|
443
|
+
name: 'User Errors',
|
|
444
|
+
stringKeys: Object.values(UserErrorKey)
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const registration = {
|
|
448
|
+
component: userErrorComponent,
|
|
449
|
+
strings: {
|
|
450
|
+
en: {
|
|
451
|
+
[UserErrorKey.UserNotFound]: 'User "{username}" not found',
|
|
452
|
+
[UserErrorKey.InvalidCredentials]: 'Invalid credentials provided',
|
|
453
|
+
[UserErrorKey.AccountLocked]: 'Account locked until {unlockTime}'
|
|
454
|
+
},
|
|
455
|
+
fr: {
|
|
456
|
+
[UserErrorKey.UserNotFound]: 'Utilisateur "{username}" introuvable',
|
|
457
|
+
[UserErrorKey.InvalidCredentials]: 'Identifiants invalides fournis',
|
|
458
|
+
[UserErrorKey.AccountLocked]: 'Compte verrouillé jusqu\'à {unlockTime}'
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
i18n.registerComponent(registration);
|
|
464
|
+
|
|
465
|
+
// Throw translatable errors
|
|
466
|
+
throw new TranslatableGenericError(
|
|
467
|
+
'user-errors',
|
|
468
|
+
UserErrorKey.UserNotFound,
|
|
469
|
+
{ username: 'john_doe' },
|
|
470
|
+
'en',
|
|
471
|
+
{ userId: 123 }, // metadata
|
|
472
|
+
'myapp' // engine instance key
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Use with explicit engine instance
|
|
476
|
+
const error = TranslatableGenericError.withEngine(
|
|
477
|
+
i18n,
|
|
478
|
+
'user-errors',
|
|
479
|
+
UserErrorKey.InvalidCredentials,
|
|
480
|
+
undefined,
|
|
481
|
+
'fr'
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// Retranslate errors dynamically
|
|
485
|
+
try {
|
|
486
|
+
// ... code that throws TranslatableGenericError
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (error instanceof TranslatableGenericError) {
|
|
489
|
+
const localizedMessage = error.retranslate(userLanguage, 'myapp');
|
|
490
|
+
sendToUser(localizedMessage);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Use with core strings
|
|
495
|
+
throw new TranslatableGenericError(
|
|
496
|
+
'core',
|
|
497
|
+
CoreStringKey.Error_AccessDenied,
|
|
498
|
+
undefined,
|
|
499
|
+
CoreLanguage.EnglishUS,
|
|
500
|
+
{ requestId: '12345' },
|
|
501
|
+
'myapp'
|
|
502
|
+
);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Key Features:**
|
|
506
|
+
- Works with any registered component and string keys
|
|
507
|
+
- Uses `safeTranslate` for consistent fallback behavior (`[componentId.stringKey]`)
|
|
508
|
+
- Stores error context: stringKey, componentId, language, variables, metadata
|
|
509
|
+
- Supports dynamic retranslation with `retranslate()` method
|
|
510
|
+
- Never throws during construction - always returns a valid error
|
|
511
|
+
- Compatible with both constructor and static factory methods
|
|
512
|
+
|
|
513
|
+
For complete documentation, see [TRANSLATABLE_ERROR_GUIDE.md](./TRANSLATABLE_ERROR_GUIDE.md).
|
|
514
|
+
|
|
426
515
|
### Enum Translation Registry
|
|
427
516
|
|
|
428
517
|
```typescript
|
|
@@ -564,7 +653,7 @@ I18nEngine.removeInstance('main');
|
|
|
564
653
|
|
|
565
654
|
- `new PluginI18nEngine<TLanguages>(languages, config?)` - Create new plugin engine
|
|
566
655
|
- `PluginI18nEngine.createInstance<TLanguages>(key, languages, config?)` - Create named instance
|
|
567
|
-
- `PluginI18nEngine.getInstance<TLanguages>(key?)` - Get existing instance
|
|
656
|
+
- `PluginI18nEngine.getInstance<TLanguages>(key?)` - Get existing instance (throws error if not found)
|
|
568
657
|
|
|
569
658
|
**Component Management**
|
|
570
659
|
|
|
@@ -762,8 +851,9 @@ describe('My tests', () => {
|
|
|
762
851
|
#### Available Cleanup Methods
|
|
763
852
|
|
|
764
853
|
- `PluginI18nEngine.clearAllInstances()` - Remove all engine instances
|
|
765
|
-
- `PluginI18nEngine.removeInstance(key?)` - Remove specific instance by key
|
|
766
|
-
- `PluginI18nEngine.hasInstance(key?)` - Check if instance exists
|
|
854
|
+
- `PluginI18nEngine.removeInstance(key?)` - Remove specific instance by key (returns boolean)
|
|
855
|
+
- `PluginI18nEngine.hasInstance(key?)` - Check if instance exists (returns boolean)
|
|
856
|
+
- `PluginI18nEngine.getInstance(key?)` - Get existing instance (throws RegistryError if not found)
|
|
767
857
|
- `PluginI18nEngine.resetAll()` - Clear instances and component registrations
|
|
768
858
|
- `resetAllI18nEngines()` - Convenience function that calls `resetAll()`
|
|
769
859
|
|
|
@@ -936,10 +1026,40 @@ Part of the DigitalBurnbag project - a secure file sharing and automated protoco
|
|
|
936
1026
|
|
|
937
1027
|
## ChangeLog
|
|
938
1028
|
|
|
1029
|
+
### Version 1.1.7
|
|
1030
|
+
|
|
1031
|
+
- Wed Oct 15 2025 16:13:00 GMT-0700 (Pacific Daylight Time)
|
|
1032
|
+
**Fixed:**
|
|
1033
|
+
- Corrected safeCoreTranslation fallback format to use
|
|
1034
|
+
```plaintext
|
|
1035
|
+
[CoreStringKey.${stringKey}]
|
|
1036
|
+
```
|
|
1037
|
+
- Fixed import issues with DefaultInstanceKey
|
|
1038
|
+
**Added:**
|
|
1039
|
+
- New TranslatableGenericError class for generic translatable errors across any component
|
|
1040
|
+
- CoreI18nComponentId constant export
|
|
1041
|
+
- 130+ new tests for comprehensive coverage
|
|
1042
|
+
- Complete usage documentation
|
|
1043
|
+
**Changed:**
|
|
1044
|
+
- Standardized all fallback formats to use square brackets
|
|
1045
|
+
```plaintext
|
|
1046
|
+
[componentId.stringKey]
|
|
1047
|
+
```
|
|
1048
|
+
- Refactored to use CoreI18nComponentId constant
|
|
1049
|
+
All tests pass and backward compatibility is maintained.
|
|
1050
|
+
|
|
1051
|
+
### Version 1.1.6
|
|
1052
|
+
|
|
1053
|
+
- Tue Oct 14 2025 17:04:00 GMT-0700 (Pacific Daylight Time)
|
|
1054
|
+
- Added missing T function to i18n plugin engine
|
|
1055
|
+
|
|
939
1056
|
### Version 1.1.5
|
|
940
1057
|
|
|
941
1058
|
- Tue Oct 14 2025 14:48:00 GMT-0700 (Pacific Daylight Time)
|
|
942
|
-
- HotFix for GlobalActiveContext
|
|
1059
|
+
- [Current] HotFix for GlobalActiveContext
|
|
1060
|
+
- Fixed getInstance method to throw RegistryError when instance not found instead of auto-creating instances
|
|
1061
|
+
- Improved test reliability and proper error handling for non-existent instances
|
|
1062
|
+
- Updated API documentation to reflect error-throwing behavior
|
|
943
1063
|
|
|
944
1064
|
### Version 1.1.4
|
|
945
1065
|
|
|
@@ -106,9 +106,9 @@ class ComponentRegistry {
|
|
|
106
106
|
if (!translation) {
|
|
107
107
|
throw registry_error_1.RegistryError.createSimple(registry_error_type_1.RegistryErrorType.StringKeyNotFound, `String key '${stringKey}' not found for component '${componentId}' in language '${actualLanguage}'`, { componentId, stringKey, language: actualLanguage });
|
|
108
108
|
}
|
|
109
|
-
// Process variables if the string
|
|
109
|
+
// Process variables if the string key indicates it's a template
|
|
110
110
|
let processedTranslation = translation;
|
|
111
|
-
if (variables && (0, utils_1.isTemplate)(
|
|
111
|
+
if (variables && (0, utils_1.isTemplate)(stringKey)) {
|
|
112
112
|
processedTranslation = (0, utils_1.replaceVariables)(translation, variables);
|
|
113
113
|
}
|
|
114
114
|
return {
|
package/dist/core-i18n.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { PluginI18nEngine } from './plugin-i18n-engine';
|
|
|
11
11
|
* Create default language definitions
|
|
12
12
|
*/
|
|
13
13
|
export declare function createDefaultLanguages(): LanguageDefinition[];
|
|
14
|
+
export declare const CoreI18nComponentId = "core";
|
|
14
15
|
/**
|
|
15
16
|
* Core component definition
|
|
16
17
|
*/
|
package/dist/core-i18n.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Core I18n component with default languages and system strings
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CoreComponentDefinition = void 0;
|
|
6
|
+
exports.CoreComponentDefinition = exports.CoreI18nComponentId = void 0;
|
|
7
7
|
exports.createDefaultLanguages = createDefaultLanguages;
|
|
8
8
|
exports.createCoreComponentStrings = createCoreComponentStrings;
|
|
9
9
|
exports.createCoreComponentRegistration = createCoreComponentRegistration;
|
|
@@ -15,6 +15,7 @@ const core_string_key_1 = require("./core-string-key");
|
|
|
15
15
|
const language_registry_1 = require("./language-registry");
|
|
16
16
|
const plugin_i18n_engine_1 = require("./plugin-i18n-engine");
|
|
17
17
|
const strict_types_1 = require("./strict-types");
|
|
18
|
+
const DefaultInstanceKey = 'default';
|
|
18
19
|
/**
|
|
19
20
|
* Create default language definitions
|
|
20
21
|
*/
|
|
@@ -63,11 +64,12 @@ function createDefaultLanguages() {
|
|
|
63
64
|
},
|
|
64
65
|
]);
|
|
65
66
|
}
|
|
67
|
+
exports.CoreI18nComponentId = 'core';
|
|
66
68
|
/**
|
|
67
69
|
* Core component definition
|
|
68
70
|
*/
|
|
69
71
|
exports.CoreComponentDefinition = {
|
|
70
|
-
id:
|
|
72
|
+
id: exports.CoreI18nComponentId,
|
|
71
73
|
name: 'Core I18n System',
|
|
72
74
|
stringKeys: Object.values(core_string_key_1.CoreStringKey),
|
|
73
75
|
};
|
|
@@ -418,7 +420,7 @@ function createCoreComponentRegistration() {
|
|
|
418
420
|
/**
|
|
419
421
|
* Create a pre-configured I18n engine with core components
|
|
420
422
|
*/
|
|
421
|
-
function createCoreI18nEngine(instanceKey =
|
|
423
|
+
function createCoreI18nEngine(instanceKey = DefaultInstanceKey) {
|
|
422
424
|
const languages = createDefaultLanguages();
|
|
423
425
|
const engine = plugin_i18n_engine_1.PluginI18nEngine.createInstance(instanceKey, languages);
|
|
424
426
|
engine.registerComponent(createCoreComponentRegistration());
|
|
@@ -429,7 +431,7 @@ function createCoreI18nEngine(instanceKey = 'default') {
|
|
|
429
431
|
*/
|
|
430
432
|
function getCoreTranslation(stringKey, variables, language, instanceKey) {
|
|
431
433
|
const engine = plugin_i18n_engine_1.PluginI18nEngine.getInstance(instanceKey);
|
|
432
|
-
return engine.translate(
|
|
434
|
+
return engine.translate(exports.CoreI18nComponentId, stringKey, variables, language);
|
|
433
435
|
}
|
|
434
436
|
/**
|
|
435
437
|
* Helper function to safely get core translation with fallback
|
|
@@ -439,6 +441,6 @@ function safeCoreTranslation(stringKey, variables, language, instanceKey) {
|
|
|
439
441
|
return getCoreTranslation(stringKey, variables, language, instanceKey);
|
|
440
442
|
}
|
|
441
443
|
catch {
|
|
442
|
-
return `[
|
|
444
|
+
return `[CoreStringKey.${stringKey}]`;
|
|
443
445
|
}
|
|
444
446
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from './i18n-engine';
|
|
|
18
18
|
export * from './language-definition';
|
|
19
19
|
export * from './template';
|
|
20
20
|
export * from './timezone';
|
|
21
|
+
export * from './translatable-generic-error';
|
|
21
22
|
export * from './typed-error';
|
|
22
23
|
export * from './types';
|
|
23
24
|
export * from './utils';
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ __exportStar(require("./i18n-engine"), exports);
|
|
|
37
37
|
__exportStar(require("./language-definition"), exports);
|
|
38
38
|
__exportStar(require("./template"), exports);
|
|
39
39
|
__exportStar(require("./timezone"), exports);
|
|
40
|
+
__exportStar(require("./translatable-generic-error"), exports);
|
|
40
41
|
__exportStar(require("./typed-error"), exports);
|
|
41
42
|
__exportStar(require("./types"), exports);
|
|
42
43
|
__exportStar(require("./utils"), exports);
|
|
@@ -20,6 +20,10 @@ export declare class PluginI18nEngine<TLanguages extends string> {
|
|
|
20
20
|
private readonly enumRegistry;
|
|
21
21
|
private readonly config;
|
|
22
22
|
private contextKey;
|
|
23
|
+
/**
|
|
24
|
+
* Default template processor instance
|
|
25
|
+
*/
|
|
26
|
+
readonly t: (str: string, language?: TLanguages, ...otherVars: Record<string, string | number>[]) => string;
|
|
23
27
|
/**
|
|
24
28
|
* Static instances for semi-singleton pattern
|
|
25
29
|
*/
|
|
@@ -51,6 +51,27 @@ class PluginI18nEngine {
|
|
|
51
51
|
globalContext.setCurrencyCode(this.config.defaultCurrencyCode, this.contextKey);
|
|
52
52
|
globalContext.setUserTimezone(this.config.timezone, this.contextKey);
|
|
53
53
|
globalContext.setAdminTimezone(this.config.adminTimezone, this.contextKey);
|
|
54
|
+
// Initialize the default template processor for component-based patterns
|
|
55
|
+
this.t = (str, language, ...otherVars) => {
|
|
56
|
+
// Step 1: Replace component-based patterns like {{componentId.stringKey}}
|
|
57
|
+
let result = str.replace(/\{\{([^}]+)\}\}/g, (match, pattern) => {
|
|
58
|
+
const parts = pattern.split('.');
|
|
59
|
+
if (parts.length === 2) {
|
|
60
|
+
const [componentId, stringKey] = parts;
|
|
61
|
+
// For template strings, use the first variable object if available
|
|
62
|
+
const isTemplate = stringKey.toLowerCase().endsWith('template');
|
|
63
|
+
const vars = isTemplate && otherVars.length > 0 ? otherVars[0] : {};
|
|
64
|
+
return this.safeTranslate(componentId.trim(), stringKey.trim(), vars, language);
|
|
65
|
+
}
|
|
66
|
+
return match; // Return original if pattern doesn't match expected format
|
|
67
|
+
});
|
|
68
|
+
// Step 2: Replace remaining variable patterns like {varName} with merged variables
|
|
69
|
+
const allVars = otherVars.reduce((acc, vars) => ({ ...acc, ...vars }), {});
|
|
70
|
+
result = result.replace(/\{(\w+)\}/g, (match, varName) => {
|
|
71
|
+
return allVars[varName] !== undefined ? String(allVars[varName]) : match;
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
54
75
|
// Auto-register as default instance if none exists
|
|
55
76
|
if (!PluginI18nEngine._defaultKey) {
|
|
56
77
|
PluginI18nEngine._instances.set(PluginI18nEngine.DefaultInstanceKey, this);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PluginI18nEngine } from './plugin-i18n-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Generic translatable error that works with any plugin engine and component
|
|
4
|
+
*/
|
|
5
|
+
export declare class TranslatableGenericError<TStringKey extends string = string, TLanguage extends string = string> extends Error {
|
|
6
|
+
readonly stringKey: TStringKey;
|
|
7
|
+
readonly componentId: string;
|
|
8
|
+
readonly language?: TLanguage;
|
|
9
|
+
readonly variables?: Record<string, string | number>;
|
|
10
|
+
readonly metadata?: Record<string, any>;
|
|
11
|
+
/**
|
|
12
|
+
* Create a translatable error
|
|
13
|
+
* @param componentId - The component ID to translate from
|
|
14
|
+
* @param stringKey - The translation key
|
|
15
|
+
* @param variables - Variables for interpolation
|
|
16
|
+
* @param language - Optional language override
|
|
17
|
+
* @param metadata - Additional error metadata
|
|
18
|
+
* @param instanceKey - Optional engine instance key
|
|
19
|
+
*/
|
|
20
|
+
constructor(componentId: string, stringKey: TStringKey, variables?: Record<string, string | number>, language?: TLanguage, metadata?: Record<string, any>, instanceKey?: string);
|
|
21
|
+
/**
|
|
22
|
+
* Create error with explicit engine instance
|
|
23
|
+
*/
|
|
24
|
+
static withEngine<TStringKey extends string, TLanguage extends string>(engine: PluginI18nEngine<TLanguage>, componentId: string, stringKey: TStringKey, variables?: Record<string, string | number>, language?: TLanguage, metadata?: Record<string, any>): TranslatableGenericError<TStringKey, TLanguage>;
|
|
25
|
+
/**
|
|
26
|
+
* Retranslate the error message in a different language
|
|
27
|
+
*/
|
|
28
|
+
retranslate(language: TLanguage, instanceKey?: string): string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TranslatableGenericError = void 0;
|
|
4
|
+
const plugin_i18n_engine_1 = require("./plugin-i18n-engine");
|
|
5
|
+
/**
|
|
6
|
+
* Generic translatable error that works with any plugin engine and component
|
|
7
|
+
*/
|
|
8
|
+
class TranslatableGenericError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* Create a translatable error
|
|
11
|
+
* @param componentId - The component ID to translate from
|
|
12
|
+
* @param stringKey - The translation key
|
|
13
|
+
* @param variables - Variables for interpolation
|
|
14
|
+
* @param language - Optional language override
|
|
15
|
+
* @param metadata - Additional error metadata
|
|
16
|
+
* @param instanceKey - Optional engine instance key
|
|
17
|
+
*/
|
|
18
|
+
constructor(componentId, stringKey, variables, language, metadata, instanceKey) {
|
|
19
|
+
let translatedMessage;
|
|
20
|
+
try {
|
|
21
|
+
const engine = plugin_i18n_engine_1.PluginI18nEngine.getInstance(instanceKey);
|
|
22
|
+
translatedMessage = engine.safeTranslate(componentId, stringKey, variables, language);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// If engine not found or translation fails, use fallback format
|
|
26
|
+
translatedMessage = `[${componentId}.${stringKey}]`;
|
|
27
|
+
}
|
|
28
|
+
super(translatedMessage);
|
|
29
|
+
this.name = 'TranslatableGenericError';
|
|
30
|
+
this.stringKey = stringKey;
|
|
31
|
+
this.componentId = componentId;
|
|
32
|
+
this.language = language;
|
|
33
|
+
this.variables = variables;
|
|
34
|
+
this.metadata = metadata;
|
|
35
|
+
Object.setPrototypeOf(this, TranslatableGenericError.prototype);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create error with explicit engine instance
|
|
39
|
+
*/
|
|
40
|
+
static withEngine(engine, componentId, stringKey, variables, language, metadata) {
|
|
41
|
+
const translatedMessage = engine.safeTranslate(componentId, stringKey, variables, language);
|
|
42
|
+
const error = Object.create(TranslatableGenericError.prototype);
|
|
43
|
+
Error.call(error, translatedMessage);
|
|
44
|
+
error.name = 'TranslatableGenericError';
|
|
45
|
+
error.stringKey = stringKey;
|
|
46
|
+
error.componentId = componentId;
|
|
47
|
+
error.language = language;
|
|
48
|
+
error.variables = variables;
|
|
49
|
+
error.metadata = metadata;
|
|
50
|
+
error.message = translatedMessage;
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Retranslate the error message in a different language
|
|
55
|
+
*/
|
|
56
|
+
retranslate(language, instanceKey) {
|
|
57
|
+
try {
|
|
58
|
+
const engine = plugin_i18n_engine_1.PluginI18nEngine.getInstance(instanceKey);
|
|
59
|
+
return engine.safeTranslate(this.componentId, this.stringKey, this.variables, language);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return `[${this.componentId}.${this.stringKey}]`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.TranslatableGenericError = TranslatableGenericError;
|