@digitaldefiance/i18n-lib 1.0.13 → 1.0.15

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
@@ -1,14 +1,16 @@
1
1
  # @digitaldefiance/i18n-lib
2
2
 
3
- A generic TypeScript i18n library with enum translation support and template variable replacement.
3
+ A comprehensive TypeScript internationalization library with enum translation support, template processing, and context management.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Generic Design**: Works with any string and language enums
8
- - **Template Variables**: Replace `{variable}` placeholders in strings
9
- - **Enum Translation**: Translate enum values with type safety
10
- - **Context Support**: Admin vs user translation contexts
11
- - **Fallback Languages**: Graceful degradation when translations missing
7
+ - **Type-Safe Translations**: Full TypeScript support with generic types for strings and languages
8
+ - **Enum Translation Registry**: Translate enum values with complete type safety
9
+ - **Template Processing**: Advanced template system with `{{EnumName.EnumKey}}` patterns and variable replacement
10
+ - **Context Management**: Admin vs user translation contexts with automatic language switching
11
+ - **Singleton Pattern**: Efficient instance management with named instances
12
+ - **Currency Formatting**: Built-in currency formatting utilities with locale support
13
+ - **Fallback System**: Graceful degradation when translations are missing
12
14
  - **Zero Dependencies**: Lightweight with no external dependencies
13
15
 
14
16
  ## Installation
@@ -30,11 +32,12 @@ enum MyStrings {
30
32
 
31
33
  enum MyLanguages {
32
34
  English = 'English',
33
- Spanish = 'Español'
35
+ Spanish = 'Spanish'
34
36
  }
35
37
 
36
38
  // Configure the engine
37
39
  const config: I18nConfig<MyStrings, MyLanguages> = {
40
+ stringNames: Object.values(MyStrings),
38
41
  strings: {
39
42
  [MyLanguages.English]: {
40
43
  [MyStrings.Welcome]: 'Welcome!',
@@ -46,40 +49,52 @@ const config: I18nConfig<MyStrings, MyLanguages> = {
46
49
  }
47
50
  },
48
51
  defaultLanguage: MyLanguages.English,
52
+ defaultContext: 'user',
49
53
  languageCodes: {
50
54
  [MyLanguages.English]: 'en',
51
55
  [MyLanguages.Spanish]: 'es'
52
- }
56
+ },
57
+ languages: Object.values(MyLanguages)
53
58
  };
54
59
 
55
- // Create engine and context
60
+ // Create engine
56
61
  const i18n = new I18nEngine(config);
57
- const context = createContext(MyLanguages.English);
58
62
 
59
63
  // Translate strings
60
- const welcome = i18n.translate(MyStrings.Welcome, context);
64
+ const welcome = i18n.translate(MyStrings.Welcome);
61
65
  // "Welcome!"
62
66
 
63
- const greeting = i18n.translate(MyStrings.UserGreetingTemplate, context, { name: 'John' });
67
+ const greeting = i18n.translate(MyStrings.UserGreetingTemplate, { name: 'John' });
64
68
  // "Hello, John!"
69
+
70
+ // Change language
71
+ i18n.context = { language: MyLanguages.Spanish };
72
+ const spanishGreeting = i18n.translate(MyStrings.UserGreetingTemplate, { name: 'Juan' });
73
+ // "¡Hola, Juan!"
65
74
  ```
66
75
 
67
- ## Enum Translation
76
+ ## Advanced Features
77
+
78
+ ### Enum Translation Registry
79
+
68
80
  ```typescript
69
81
  enum Status {
70
82
  Active = 'active',
71
- Inactive = 'inactive'
83
+ Inactive = 'inactive',
84
+ Pending = 'pending'
72
85
  }
73
86
 
74
- // Register enum translations
87
+ // Register enum translations (requires complete translations)
75
88
  i18n.registerEnum(Status, {
76
89
  [MyLanguages.English]: {
77
90
  [Status.Active]: 'Active',
78
- [Status.Inactive]: 'Inactive'
91
+ [Status.Inactive]: 'Inactive',
92
+ [Status.Pending]: 'Pending'
79
93
  },
80
94
  [MyLanguages.Spanish]: {
81
95
  [Status.Active]: 'Activo',
82
- [Status.Inactive]: 'Inactivo'
96
+ [Status.Inactive]: 'Inactivo',
97
+ [Status.Pending]: 'Pendiente'
83
98
  }
84
99
  }, 'Status');
85
100
 
@@ -88,24 +103,203 @@ const statusText = i18n.translateEnum(Status, Status.Active, MyLanguages.Spanish
88
103
  // "Activo"
89
104
  ```
90
105
 
91
- ## API Reference
92
- ### i18nEngine
93
- - translate(key, context, vars?, language?, fallback?) - Translate string with optional variables
106
+ ### Template Processing
107
+
108
+ ```typescript
109
+ enum AppStrings {
110
+ WelcomeMessage = 'welcomeMessage',
111
+ UserGreetingTemplate = 'userGreetingTemplate'
112
+ }
94
113
 
95
- - translateEnum(enumObj, value, language) - Translate enum value
114
+ const config: I18nConfig<AppStrings, MyLanguages> = {
115
+ // ... other config
116
+ enumName: 'AppStrings',
117
+ enumObj: AppStrings,
118
+ strings: {
119
+ [MyLanguages.English]: {
120
+ [AppStrings.WelcomeMessage]: 'Welcome to our app!',
121
+ [AppStrings.UserGreetingTemplate]: 'Hello, {name}!'
122
+ }
123
+ }
124
+ };
96
125
 
97
- - registerEnum(enumObj, translations, name) - Register enum translations
126
+ const i18n = new I18nEngine(config);
98
127
 
99
- - getLanguageCode(language) - Get language code for language
128
+ // Use template processor
129
+ const message = i18n.t('{{AppStrings.WelcomeMessage}} {{AppStrings.UserGreetingTemplate}}',
130
+ MyLanguages.English,
131
+ { name: 'John' }
132
+ );
133
+ // "Welcome to our app! Hello, John!"
134
+ ```
100
135
 
101
136
  ### Context Management
102
- - createContext(defaultLanguage) - Create new context
103
137
 
104
- - setLanguage(context, language) - Set user language
138
+ ```typescript
139
+ import { createContext, setLanguage, setAdminLanguage, setContext } from '@digitaldefiance/i18n-lib';
140
+
141
+ // Create and manage context
142
+ const context = createContext(MyLanguages.English, 'user');
143
+
144
+ // Set different languages for user and admin contexts
145
+ setLanguage(context, MyLanguages.Spanish);
146
+ setAdminLanguage(context, MyLanguages.English);
147
+
148
+ // Switch between contexts
149
+ setContext(context, 'admin'); // Uses admin language
150
+ setContext(context, 'user'); // Uses user language
151
+
152
+ // Apply context to engine
153
+ i18n.context = context;
154
+ ```
155
+
156
+ ### Context Change Monitoring
157
+
158
+ ```typescript
159
+ import { ContextManager } from '@digitaldefiance/i18n-lib';
160
+
161
+ interface AppContext {
162
+ language: string;
163
+ theme: string;
164
+ }
165
+
166
+ const manager = new ContextManager<AppContext>();
167
+
168
+ // Add listeners for context changes
169
+ manager.addListener((property, oldValue, newValue) => {
170
+ console.log(`${property} changed from ${oldValue} to ${newValue}`);
171
+ });
172
+
173
+ // Create reactive context
174
+ const context = { language: 'en', theme: 'dark' };
175
+ const reactiveContext = manager.createProxy(context);
176
+
177
+ // Changes are automatically detected
178
+ reactiveContext.language = 'es'; // Triggers listener
179
+ ```
180
+
181
+ ### Currency Formatting
182
+
183
+ ```typescript
184
+ import { getCurrencyFormat } from '@digitaldefiance/i18n-lib';
185
+
186
+ // Get currency formatting information
187
+ const usdFormat = getCurrencyFormat('en-US', 'USD');
188
+ // { symbol: '$', position: 'prefix', groupSeparator: ',', decimalSeparator: '.' }
189
+
190
+ const eurFormat = getCurrencyFormat('de-DE', 'EUR');
191
+ // { symbol: '€', position: 'postfix', groupSeparator: '.', decimalSeparator: ',' }
192
+ ```
193
+
194
+ ### Instance Management
195
+
196
+ ```typescript
197
+ // Create named instances
198
+ const mainI18n = new I18nEngine(config, 'main');
199
+ const adminI18n = new I18nEngine(adminConfig, 'admin');
200
+
201
+ // Get instances by key
202
+ const instance = I18nEngine.getInstance('main');
203
+
204
+ // Clean up instances (useful for testing)
205
+ I18nEngine.clearInstances();
206
+ I18nEngine.removeInstance('main');
207
+ ```
208
+
209
+ ## API Reference
210
+
211
+ ### I18nEngine
212
+
213
+ #### Constructor
214
+ - `new I18nEngine<TStringKey, TLanguage>(config, key?)` - Create new engine instance
215
+
216
+ #### Translation Methods
217
+ - `translate(key, vars?, language?, fallbackLanguage?)` - Translate string with optional variables
218
+ - `translateEnum(enumObj, value, language)` - Translate enum value
219
+ - `t(templateString, language?, ...vars)` - Process template with enum patterns
220
+
221
+ #### Registration
222
+ - `registerEnum(enumObj, translations, enumName)` - Register enum translations
223
+
224
+ #### Language Management
225
+ - `getLanguageCode(language)` - Get language code for language
226
+ - `getLanguageFromCode(code)` - Get language from code
227
+ - `getAllLanguageCodes()` - Get all language codes
228
+ - `getAvailableLanguages()` - Get available languages
229
+ - `isLanguageAvailable(language)` - Check if language is available
230
+
231
+ #### Context Management
232
+ - `get context()` - Get current context
233
+ - `set context(context)` - Set context properties
234
+
235
+ #### Static Methods
236
+ - `getInstance(key?)` - Get instance by key
237
+ - `clearInstances()` - Clear all instances
238
+ - `removeInstance(key?)` - Remove specific instance
239
+
240
+ ### Context Utilities
241
+ - `createContext(defaultLanguage, defaultContext)` - Create new context
242
+ - `setLanguage(context, language)` - Set user language
243
+ - `setAdminLanguage(context, language)` - Set admin language
244
+ - `setContext(context, contextType)` - Set context type
245
+
246
+ ### ContextManager
247
+ - `addListener(listener)` - Add change listener
248
+ - `removeListener(listener)` - Remove change listener
249
+ - `createProxy(context)` - Create reactive context proxy
250
+
251
+ ### Currency Utilities
252
+ - `getCurrencyFormat(locale, currencyCode)` - Get currency formatting info
253
+
254
+ ### Type Utilities
255
+ - `createTranslations(translations)` - Helper for creating typed translations
256
+
257
+ ## Type Definitions
258
+
259
+ ### Core Types
260
+ ```typescript
261
+ type EnumTranslation<T extends string | number> = {
262
+ [K in T]: string;
263
+ };
264
+
265
+ type EnumLanguageTranslation<T extends string | number, TLanguage extends string> = Partial<{
266
+ [L in TLanguage]: EnumTranslation<T>;
267
+ }>;
268
+
269
+ interface I18nConfig<TStringKey, TLanguage, TConstants?, TContext?> {
270
+ stringNames: TStringKey[];
271
+ strings: MasterStringsCollection<TStringKey, TLanguage>;
272
+ defaultLanguage: TLanguage;
273
+ defaultContext: TContext;
274
+ languageCodes: LanguageCodeCollection<TLanguage>;
275
+ languages: TLanguage[];
276
+ constants?: TConstants;
277
+ enumName?: string;
278
+ enumObj?: Record<string, TStringKey>;
279
+ }
280
+ ```
281
+
282
+ ## Testing
283
+
284
+ The library includes comprehensive test coverage:
285
+
286
+ ```bash
287
+ # Run tests
288
+ yarn test
289
+
290
+ # Run specific test suites
291
+ yarn test context-manager.spec.ts
292
+ yarn test enum-registry.spec.ts
293
+ yarn test i18n-engine.spec.ts
294
+ ```
105
295
 
106
- - setAdminLanguage(context, language) - Set admin language
296
+ ## Best Practices
107
297
 
108
- - setContext(context, 'admin' | 'user') - Set context type
298
+ 1. **Complete Translations**: EnumTranslation requires all enum values to be translated
299
+ 2. **Type Safety**: Use TypeScript enums for string keys and languages
300
+ 3. **Context Separation**: Use different contexts for admin and user interfaces
301
+ 4. **Instance Management**: Use named instances for different parts of your application
302
+ 5. **Error Handling**: Handle missing translations gracefully with fallback languages
109
303
 
110
304
  ## License
111
305
 
@@ -113,4 +307,4 @@ MIT
113
307
 
114
308
  ## Repository
115
309
 
116
- [https://github.com/Digital-Defiance/i18n-lib](https://github.com/Digital-Defiance/i18n-lib)
310
+ Part of the DigitalBurnbag project - a secure file sharing and automated protocol system.
@@ -18,7 +18,7 @@ class ContextManager {
18
18
  }
19
19
  }
20
20
  notifyChange(property, oldValue, newValue) {
21
- this.listeners.forEach(listener => {
21
+ this.listeners.forEach((listener) => {
22
22
  try {
23
23
  listener(property, oldValue, newValue);
24
24
  }
@@ -35,7 +35,7 @@ class ContextManager {
35
35
  target[property] = value;
36
36
  manager.notifyChange(property, oldValue, value);
37
37
  return true;
38
- }
38
+ },
39
39
  });
40
40
  }
41
41
  }
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { I18nContext, LanguageContext } from './types';
2
- export declare function createContext<TLanguage extends string>(defaultLanguage: TLanguage): I18nContext<TLanguage>;
3
- export declare function setLanguage<TLanguage extends string>(context: I18nContext<TLanguage>, language: TLanguage): void;
4
- export declare function setAdminLanguage<TLanguage extends string>(context: I18nContext<TLanguage>, language: TLanguage): void;
5
- export declare function setContext<TLanguage extends string>(context: I18nContext<TLanguage>, languageContext: LanguageContext): void;
2
+ export declare function createContext<TLanguage extends string, TContext extends string = LanguageContext>(defaultLanguage: TLanguage, defaultContext: TContext): I18nContext<TLanguage, TContext>;
3
+ export declare function setLanguage<TLanguage extends string, TContext extends string = LanguageContext>(context: I18nContext<TLanguage, TContext>, language: TLanguage): void;
4
+ export declare function setAdminLanguage<TLanguage extends string, TContext extends string = LanguageContext>(context: I18nContext<TLanguage, TContext>, language: TLanguage): void;
5
+ export declare function setContext<TLanguage extends string, TContext extends string = LanguageContext>(context: I18nContext<TLanguage, TContext>, languageContext: TContext): void;
package/dist/context.js CHANGED
@@ -4,11 +4,11 @@ exports.createContext = createContext;
4
4
  exports.setLanguage = setLanguage;
5
5
  exports.setAdminLanguage = setAdminLanguage;
6
6
  exports.setContext = setContext;
7
- function createContext(defaultLanguage) {
7
+ function createContext(defaultLanguage, defaultContext) {
8
8
  return {
9
9
  language: defaultLanguage,
10
10
  adminLanguage: defaultLanguage,
11
- currentContext: 'user'
11
+ currentContext: defaultContext,
12
12
  };
13
13
  }
14
14
  function setLanguage(context, language) {
@@ -21,7 +21,7 @@ class EnumTranslationRegistry {
21
21
  }
22
22
  let result = langTranslations[value];
23
23
  if (!result && typeof value === 'number') {
24
- const stringKey = Object.keys(enumObj).find(key => enumObj[key] === value);
24
+ const stringKey = Object.keys(enumObj).find((key) => enumObj[key] === value);
25
25
  if (stringKey) {
26
26
  result = langTranslations[stringKey];
27
27
  }
@@ -1,13 +1,153 @@
1
- import { I18nConfig, I18nContext, LanguageContext, EnumLanguageTranslation } from './types';
2
- export declare class I18nEngine<TStringKey extends string, TLanguage extends string, TConstants = Record<string, string | number>, TContext extends string = LanguageContext> {
3
- private config;
4
- private enumRegistry;
5
- constructor(config: I18nConfig<TStringKey, TLanguage, TConstants>);
6
- translate(key: TStringKey, context: I18nContext<TLanguage, TContext>, vars?: Record<string, string | number>, language?: TLanguage, fallbackLanguage?: TLanguage): string;
1
+ import { EnumTranslationRegistry } from './enum-registry';
2
+ import { EnumLanguageTranslation, I18nConfig, I18nContext } from './types';
3
+ export declare class I18nEngine<TStringKey extends string, TLanguage extends string, TConstants extends Record<string, any> = Record<string, any>, TContext extends string = string> {
4
+ /**
5
+ * Registry for enum translations
6
+ */
7
+ protected enumRegistry: EnumTranslationRegistry<TLanguage>;
8
+ /**
9
+ * Configuration for the i18n engine
10
+ */
11
+ readonly config: I18nConfig<TStringKey, TLanguage, TConstants, TContext>;
12
+ /**
13
+ * Static instances for semi-singleton pattern
14
+ */
15
+ private static _instances;
16
+ /**
17
+ * Default instance key (first created instance)
18
+ */
19
+ private static _defaultKey;
20
+ /**
21
+ * Default instance key if none is provided
22
+ */
23
+ protected static readonly DefaultInstanceKey = "default";
24
+ /**
25
+ * Global context for translations (used if no context is provided) for this instance
26
+ */
27
+ private _context;
28
+ /**
29
+ * Default template processor instance
30
+ */
31
+ readonly t: (key: TStringKey, vars?: Record<string, string | number>, language?: TLanguage) => string;
32
+ /**
33
+ * Creates a new I18nEngine instance
34
+ * @param config The i18n configuration
35
+ * @param key Optional instance key for the semi-singleton pattern
36
+ * @throws Error if an instance with the same key already exists
37
+ */
38
+ constructor(config: I18nConfig<TStringKey, TLanguage, TConstants, TContext>, key?: string);
39
+ /**
40
+ * Gets an instance of the I18nEngine by key. If no key is provided, the default instance is returned.
41
+ * @param key The key of the instance to retrieve
42
+ * @returns The I18nEngine instance
43
+ * @throws Error if the instance with the provided key does not exist
44
+ */
45
+ static getInstance<T extends I18nEngine<any, any, any, any>>(key?: string): T;
46
+ /**
47
+ * Gets a translation for the provided error key using the specified instance (or default instance if none is provided).
48
+ * @param errorKey The error key to translate
49
+ * @param vars Variables to replace in the translation string
50
+ * @param instanceKey The key of the I18nEngine instance to use
51
+ * @param language The language to translate to
52
+ * @param fallbackLanguage The fallback language if the translation is not found
53
+ * @returns The translated error message
54
+ */
55
+ static getErrorMessage(errorKey: string, vars?: Record<string, string | number>, instanceKey?: string, language?: string, fallbackLanguage?: string): string;
56
+ /**
57
+ * Throws an error with a translated message using the specified instance (or default instance if none is provided).
58
+ * @param errorKey The error key to translate
59
+ * @param vars Variables to replace in the translation string
60
+ * @param instanceKey The key of the I18nEngine instance to use
61
+ * @throws Error with translated message
62
+ */
63
+ static throwError(errorKey: string, vars?: Record<string, string | number>, instanceKey?: string): never;
64
+ /**
65
+ * Gets the global context for translations
66
+ * @returns The global context object
67
+ */
68
+ get context(): I18nContext<TLanguage, TContext>;
69
+ /**
70
+ * Sets the global context for translations (used if no context is provided) for this instance
71
+ * @param context The context to set
72
+ */
73
+ set context(context: Partial<I18nContext<TLanguage, TContext>>);
74
+ /**
75
+ * Translates a string key into the specified language, replacing any variables as needed.
76
+ * @param key The string key to translate
77
+ * @param vars Variables to replace in the translation string
78
+ * @param language The language to translate to
79
+ * @param fallbackLanguage The fallback language if the translation is not found
80
+ * @returns The translated string
81
+ */
82
+ translate(key: TStringKey, vars?: Record<string, string | number>, language?: TLanguage, fallbackLanguage?: TLanguage): string;
83
+ /**
84
+ * Translates an enumeration value into the specified language.
85
+ * @param enumObj The enumeration object
86
+ * @param value The enumeration value to translate
87
+ * @param language The language to translate to
88
+ * @returns The translated enumeration value
89
+ */
7
90
  translateEnum<TEnum extends string | number>(enumObj: Record<string, TEnum>, value: TEnum, language: TLanguage): string;
91
+ /**
92
+ * Registers an enumeration and its translations with the engine.
93
+ * @param enumObj The enumeration object
94
+ * @param translations The translations for the enumeration
95
+ * @param enumName The name of the enumeration (for error messages)
96
+ */
8
97
  registerEnum<TEnum extends string | number>(enumObj: Record<string, TEnum> | Record<string | number, string | number>, translations: EnumLanguageTranslation<TEnum, TLanguage>, enumName: string): void;
98
+ /**
99
+ * Safe translation that prevents infinite recursion for error messages
100
+ * @param key The string key to translate
101
+ * @param vars Variables to replace in the translation string
102
+ * @param language The language to translate to
103
+ * @returns The translated string or the key if translation fails
104
+ */
105
+ private safeTranslate;
106
+ /**
107
+ * Retrieves the string for the given language and key, throwing an error if not found.
108
+ * @param language The language to get the string for
109
+ * @param key The string key to retrieve
110
+ * @returns The string value
111
+ * @throws Error if the language or string key is not found
112
+ */
9
113
  private getString;
114
+ /**
115
+ * Gets the language code for the specified language.
116
+ * @param language The language to get the code for
117
+ * @returns The language code
118
+ */
10
119
  getLanguageCode(language: TLanguage): string;
120
+ /**
121
+ * Gets the language for the specified language code.
122
+ * @param code The language code to look up
123
+ * @returns The language, or undefined if not found
124
+ */
11
125
  getLanguageFromCode(code: string): TLanguage | undefined;
126
+ /**
127
+ * Gets all language codes.
128
+ * @returns A record of all language codes
129
+ */
12
130
  getAllLanguageCodes(): Record<TLanguage, string>;
131
+ /**
132
+ * Gets all available languages.
133
+ * @returns An array of all available languages
134
+ */
135
+ getAvailableLanguages(): TLanguage[];
136
+ /**
137
+ * Checks if a language is available.
138
+ * @param language The language to check
139
+ * @returns True if the language is available, false otherwise
140
+ */
141
+ isLanguageAvailable(language: string): language is TLanguage;
142
+ /**
143
+ * Clears all instances (for testing purposes)
144
+ * @internal
145
+ */
146
+ static clearInstances(): void;
147
+ /**
148
+ * Removes a specific instance by key
149
+ * @param key The key of the instance to remove
150
+ * @internal
151
+ */
152
+ static removeInstance(key?: string): void;
13
153
  }
@@ -2,15 +2,107 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.I18nEngine = void 0;
4
4
  const enum_registry_1 = require("./enum-registry");
5
+ const template_1 = require("./template");
5
6
  const utils_1 = require("./utils");
6
7
  class I18nEngine {
7
- constructor(config) {
8
+ /**
9
+ * Creates a new I18nEngine instance
10
+ * @param config The i18n configuration
11
+ * @param key Optional instance key for the semi-singleton pattern
12
+ * @throws Error if an instance with the same key already exists
13
+ */
14
+ constructor(config, key) {
8
15
  this.config = config;
9
16
  this.enumRegistry = new enum_registry_1.EnumTranslationRegistry();
17
+ this._context = {
18
+ language: config.defaultLanguage,
19
+ adminLanguage: config.defaultLanguage,
20
+ currentContext: config.defaultContext,
21
+ };
22
+ const instanceKey = key || I18nEngine.DefaultInstanceKey;
23
+ if (I18nEngine._instances.has(instanceKey)) {
24
+ const existing = I18nEngine._instances.get(instanceKey);
25
+ throw new Error(existing.translate('Error_InstanceAlreadyExistsTemplate', {
26
+ key: instanceKey,
27
+ }));
28
+ }
29
+ I18nEngine._instances.set(instanceKey, this);
30
+ if (!I18nEngine._defaultKey) {
31
+ I18nEngine._defaultKey = instanceKey;
32
+ }
33
+ // Initialize the default template processor
34
+ this.t = (0, template_1.createTemplateProcessor)(this.config.enumObj || {}, (key, vars, language) => this.translate(key, vars, language), this.config.enumName || 'StringKey');
35
+ }
36
+ /**
37
+ * Gets an instance of the I18nEngine by key. If no key is provided, the default instance is returned.
38
+ * @param key The key of the instance to retrieve
39
+ * @returns The I18nEngine instance
40
+ * @throws Error if the instance with the provided key does not exist
41
+ */
42
+ static getInstance(key) {
43
+ const instanceKey = key || I18nEngine._defaultKey || I18nEngine.DefaultInstanceKey;
44
+ if (!instanceKey || !I18nEngine._instances.has(instanceKey)) {
45
+ throw new Error(I18nEngine.getErrorMessage('Error_InstanceNotFoundTemplate', {
46
+ key: instanceKey,
47
+ }));
48
+ }
49
+ return I18nEngine._instances.get(instanceKey);
10
50
  }
11
- translate(key, context, vars, language, fallbackLanguage) {
51
+ /**
52
+ * Gets a translation for the provided error key using the specified instance (or default instance if none is provided).
53
+ * @param errorKey The error key to translate
54
+ * @param vars Variables to replace in the translation string
55
+ * @param instanceKey The key of the I18nEngine instance to use
56
+ * @param language The language to translate to
57
+ * @param fallbackLanguage The fallback language if the translation is not found
58
+ * @returns The translated error message
59
+ */
60
+ static getErrorMessage(errorKey, vars, instanceKey, language, fallbackLanguage) {
61
+ try {
62
+ const instance = I18nEngine.getInstance(instanceKey);
63
+ return instance.translate(errorKey, vars, language, fallbackLanguage);
64
+ }
65
+ catch {
66
+ return `${errorKey}: ${JSON.stringify(vars || {})}`;
67
+ }
68
+ }
69
+ /**
70
+ * Throws an error with a translated message using the specified instance (or default instance if none is provided).
71
+ * @param errorKey The error key to translate
72
+ * @param vars Variables to replace in the translation string
73
+ * @param instanceKey The key of the I18nEngine instance to use
74
+ * @throws Error with translated message
75
+ */
76
+ static throwError(errorKey, vars, instanceKey) {
77
+ throw new Error(I18nEngine.getErrorMessage(errorKey, vars, instanceKey));
78
+ }
79
+ /**
80
+ * Gets the global context for translations
81
+ * @returns The global context object
82
+ */
83
+ get context() {
84
+ return this._context;
85
+ }
86
+ /**
87
+ * Sets the global context for translations (used if no context is provided) for this instance
88
+ * @param context The context to set
89
+ */
90
+ set context(context) {
91
+ this._context = { ...this._context, ...context };
92
+ }
93
+ /**
94
+ * Translates a string key into the specified language, replacing any variables as needed.
95
+ * @param key The string key to translate
96
+ * @param vars Variables to replace in the translation string
97
+ * @param language The language to translate to
98
+ * @param fallbackLanguage The fallback language if the translation is not found
99
+ * @returns The translated string
100
+ */
101
+ translate(key, vars, language, fallbackLanguage) {
12
102
  const lang = language ??
13
- (context.currentContext === 'admin' ? context.adminLanguage : context.language);
103
+ (this._context.currentContext === 'admin'
104
+ ? this._context.adminLanguage
105
+ : this._context.language);
14
106
  const fallback = fallbackLanguage ?? this.config.defaultLanguage;
15
107
  try {
16
108
  const stringValue = this.getString(lang, key);
@@ -33,26 +125,80 @@ class I18nEngine {
33
125
  return key;
34
126
  }
35
127
  }
128
+ /**
129
+ * Translates an enumeration value into the specified language.
130
+ * @param enumObj The enumeration object
131
+ * @param value The enumeration value to translate
132
+ * @param language The language to translate to
133
+ * @returns The translated enumeration value
134
+ */
36
135
  translateEnum(enumObj, value, language) {
37
136
  return this.enumRegistry.translate(enumObj, value, language);
38
137
  }
138
+ /**
139
+ * Registers an enumeration and its translations with the engine.
140
+ * @param enumObj The enumeration object
141
+ * @param translations The translations for the enumeration
142
+ * @param enumName The name of the enumeration (for error messages)
143
+ */
39
144
  registerEnum(enumObj, translations, enumName) {
40
145
  this.enumRegistry.register(enumObj, translations, enumName);
41
146
  }
147
+ /**
148
+ * Safe translation that prevents infinite recursion for error messages
149
+ * @param key The string key to translate
150
+ * @param vars Variables to replace in the translation string
151
+ * @param language The language to translate to
152
+ * @returns The translated string or the key if translation fails
153
+ */
154
+ safeTranslate(key, vars, language) {
155
+ try {
156
+ const lang = language ?? this.config.defaultLanguage;
157
+ const strings = this.config.strings[lang];
158
+ if (!strings?.[key])
159
+ return key;
160
+ const stringValue = strings[key];
161
+ return (0, utils_1.isTemplate)(key)
162
+ ? (0, utils_1.replaceVariables)(stringValue, vars, this.config.constants)
163
+ : stringValue;
164
+ }
165
+ catch {
166
+ return key;
167
+ }
168
+ }
169
+ /**
170
+ * Retrieves the string for the given language and key, throwing an error if not found.
171
+ * @param language The language to get the string for
172
+ * @param key The string key to retrieve
173
+ * @returns The string value
174
+ * @throws Error if the language or string key is not found
175
+ */
42
176
  getString(language, key) {
43
177
  const strings = this.config.strings[language];
44
178
  if (!strings) {
45
- throw new Error(`Language not found: ${language}`);
179
+ throw new Error(this.safeTranslate('Error_LanguageNotFound', { language }) ||
180
+ `Language not found: ${language}`);
46
181
  }
47
182
  const value = strings[key];
48
183
  if (!value) {
49
- throw new Error(`String not found: ${key}`);
184
+ throw new Error(this.safeTranslate('Error_StringNotFound', { key }) ||
185
+ `String not found: ${key}`);
50
186
  }
51
187
  return value;
52
188
  }
189
+ /**
190
+ * Gets the language code for the specified language.
191
+ * @param language The language to get the code for
192
+ * @returns The language code
193
+ */
53
194
  getLanguageCode(language) {
54
195
  return this.config.languageCodes[language] || language;
55
196
  }
197
+ /**
198
+ * Gets the language for the specified language code.
199
+ * @param code The language code to look up
200
+ * @returns The language, or undefined if not found
201
+ */
56
202
  getLanguageFromCode(code) {
57
203
  for (const [lang, langCode] of Object.entries(this.config.languageCodes)) {
58
204
  if (langCode === code) {
@@ -61,8 +207,60 @@ class I18nEngine {
61
207
  }
62
208
  return undefined;
63
209
  }
210
+ /**
211
+ * Gets all language codes.
212
+ * @returns A record of all language codes
213
+ */
64
214
  getAllLanguageCodes() {
65
215
  return this.config.languageCodes;
66
216
  }
217
+ /**
218
+ * Gets all available languages.
219
+ * @returns An array of all available languages
220
+ */
221
+ getAvailableLanguages() {
222
+ return Object.keys(this.config.strings);
223
+ }
224
+ /**
225
+ * Checks if a language is available.
226
+ * @param language The language to check
227
+ * @returns True if the language is available, false otherwise
228
+ */
229
+ isLanguageAvailable(language) {
230
+ return Object.keys(this.config.strings).includes(language);
231
+ }
232
+ /**
233
+ * Clears all instances (for testing purposes)
234
+ * @internal
235
+ */
236
+ static clearInstances() {
237
+ I18nEngine._instances.clear();
238
+ I18nEngine._defaultKey = null;
239
+ }
240
+ /**
241
+ * Removes a specific instance by key
242
+ * @param key The key of the instance to remove
243
+ * @internal
244
+ */
245
+ static removeInstance(key) {
246
+ const instanceKey = key || I18nEngine.DefaultInstanceKey;
247
+ I18nEngine._instances.delete(instanceKey);
248
+ if (I18nEngine._defaultKey === instanceKey) {
249
+ const nextKey = I18nEngine._instances.keys().next().value;
250
+ I18nEngine._defaultKey = I18nEngine._instances.size > 0 && nextKey ? nextKey : null;
251
+ }
252
+ }
67
253
  }
68
254
  exports.I18nEngine = I18nEngine;
255
+ /**
256
+ * Static instances for semi-singleton pattern
257
+ */
258
+ I18nEngine._instances = new Map();
259
+ /**
260
+ * Default instance key (first created instance)
261
+ */
262
+ I18nEngine._defaultKey = null;
263
+ /**
264
+ * Default instance key if none is provided
265
+ */
266
+ I18nEngine.DefaultInstanceKey = 'default';
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export * from './types';
2
- export * from './i18n-engine';
3
- export * from './enum-registry';
4
1
  export * from './context';
5
- export * from './utils';
6
- export * from './template';
7
2
  export * from './context-manager';
8
3
  export * from './currency';
4
+ export * from './enum-registry';
5
+ export * from './i18n-engine';
6
+ export * from './template';
7
+ export * from './types';
8
+ export * from './utils';
9
9
  export { I18nEngine as I18n } from './i18n-engine';
package/dist/index.js CHANGED
@@ -15,14 +15,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.I18n = void 0;
18
- __exportStar(require("./types"), exports);
19
- __exportStar(require("./i18n-engine"), exports);
20
- __exportStar(require("./enum-registry"), exports);
21
18
  __exportStar(require("./context"), exports);
22
- __exportStar(require("./utils"), exports);
23
- __exportStar(require("./template"), exports);
24
19
  __exportStar(require("./context-manager"), exports);
25
20
  __exportStar(require("./currency"), exports);
21
+ __exportStar(require("./enum-registry"), exports);
22
+ __exportStar(require("./i18n-engine"), exports);
23
+ __exportStar(require("./template"), exports);
24
+ __exportStar(require("./types"), exports);
25
+ __exportStar(require("./utils"), exports);
26
26
  // Re-export for convenience
27
27
  var i18n_engine_1 = require("./i18n-engine");
28
28
  Object.defineProperty(exports, "I18n", { enumerable: true, get: function () { return i18n_engine_1.I18nEngine; } });
package/dist/template.js CHANGED
@@ -17,7 +17,9 @@ function createTemplateProcessor(enumObj, translateFn, enumName) {
17
17
  if (!enumValue) {
18
18
  return match; // Return original if enum key not found
19
19
  }
20
- const needsVars = enumValue.toLowerCase().endsWith('template');
20
+ const needsVars = enumValue
21
+ .toLowerCase()
22
+ .endsWith('template');
21
23
  const vars = needsVars ? otherVars[varIndex++] ?? {} : {};
22
24
  return translateFn(enumValue, vars, language);
23
25
  });
package/dist/types.d.ts CHANGED
@@ -4,11 +4,16 @@ export type StringsCollection<TStringKey extends string> = Partial<Record<TStrin
4
4
  export type MasterStringsCollection<TStringKey extends string, TLanguage extends string> = Partial<Record<TLanguage, StringsCollection<TStringKey>>>;
5
5
  export type LanguageCodeCollection<TLanguage extends string> = Partial<Record<TLanguage, string>>;
6
6
  export type EnumTranslationMap<TEnum extends string | number, TLanguage extends string> = Partial<Record<TLanguage, Partial<Record<TEnum, string>>>>;
7
- export interface I18nConfig<TStringKey extends string, TLanguage extends string, TConstants = Record<string, string | number>> {
7
+ export interface I18nConfig<TStringKey extends string, TLanguage extends string, TConstants extends Record<string, any> = Record<string, any>, TContext extends string = LanguageContext> {
8
+ stringNames: TStringKey[];
8
9
  strings: MasterStringsCollection<TStringKey, TLanguage>;
9
10
  defaultLanguage: TLanguage;
11
+ defaultContext: TContext;
10
12
  languageCodes: LanguageCodeCollection<TLanguage>;
13
+ languages: TLanguage[];
11
14
  constants?: TConstants;
15
+ enumName?: string;
16
+ enumObj?: Record<string, TStringKey>;
12
17
  }
13
18
  export interface I18nContext<TLanguage extends string, TContext extends string = LanguageContext> {
14
19
  language: TLanguage;
@@ -24,9 +29,9 @@ export type EnumTranslation<T extends string | number> = {
24
29
  /**
25
30
  * Generic language translation type for any enumeration
26
31
  */
27
- export type EnumLanguageTranslation<T extends string | number, TLanguage extends string = string> = {
32
+ export type EnumLanguageTranslation<T extends string | number, TLanguage extends string = string> = Partial<{
28
33
  [L in TLanguage]: EnumTranslation<T>;
29
- };
34
+ }>;
30
35
  /**
31
36
  * Helper function to create typed translations for an enumeration
32
37
  */
package/package.json CHANGED
@@ -1,19 +1,32 @@
1
1
  {
2
2
  "name": "@digitaldefiance/i18n-lib",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Generic i18n library with enum translation support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "yarn tsc",
9
9
  "test": "yarn jest",
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",
10
15
  "prepublishOnly": "yarn build",
11
16
  "publish:public": "npm publish --access public"
12
17
  },
13
18
  "devDependencies": {
14
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",
15
26
  "jest": "^29.0.0",
16
27
  "jest-util": "^30.0.5",
28
+ "prettier": "^2.6.2",
29
+ "prettier-plugin-organize-imports": "^4.1.0",
17
30
  "ts-jest": "^29.0.0",
18
31
  "typescript": "^5.9.2"
19
32
  },