@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 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 is a template
109
+ // Process variables if the string key indicates it's a template
110
110
  let processedTranslation = translation;
111
- if (variables && (0, utils_1.isTemplate)(translation)) {
111
+ if (variables && (0, utils_1.isTemplate)(stringKey)) {
112
112
  processedTranslation = (0, utils_1.replaceVariables)(translation, variables);
113
113
  }
114
114
  return {
@@ -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: 'core',
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 = 'default') {
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('core', stringKey, variables, language);
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 `[core.${stringKey}]`;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitaldefiance/i18n-lib",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Generic i18n library with enum translation support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",