@digitaldefiance/i18n-lib 1.2.4 → 1.3.0
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 +1222 -33
- package/dist/core-i18n.d.ts +13 -1
- package/dist/core-i18n.js +14 -0
- package/dist/create-translation-adapter.d.ts +20 -0
- package/dist/create-translation-adapter.js +36 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/typed-error.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,49 +1,127 @@
|
|
|
1
1
|
# @digitaldefiance/i18n-lib
|
|
2
2
|
|
|
3
|
-
A comprehensive TypeScript internationalization library
|
|
3
|
+
A comprehensive, production-ready TypeScript internationalization (i18n) library featuring plugin-based architecture, compile-time type safety, component registration, enum translation, template processing, and advanced context management. Built for enterprise applications requiring robust multilingual support with zero-knowledge security patterns.
|
|
4
4
|
|
|
5
|
-
## 🚀
|
|
5
|
+
## 🚀 Plugin-Based Architecture
|
|
6
6
|
|
|
7
|
-
**Version 1.1.0
|
|
7
|
+
**Version 1.1.0+** introduces a revolutionary plugin-based architecture with component registration and rigid compile-time type safety:
|
|
8
8
|
|
|
9
|
-
- **Component Registration System**: Register translation components with their own string keys
|
|
10
|
-
- **Language Plugin Support**: Add new languages dynamically with validation
|
|
11
|
-
- **Compile-Time Type Safety**: TypeScript ensures all strings are complete for all languages
|
|
12
|
-
- **Automatic Validation**: Comprehensive validation with detailed error reporting
|
|
13
|
-
- **Fallback System**:
|
|
14
|
-
- **Multi-Instance Support**: Named instances for different application contexts
|
|
9
|
+
- **Component Registration System**: Register translation components with their own isolated string keys
|
|
10
|
+
- **Language Plugin Support**: Add new languages dynamically with automatic validation
|
|
11
|
+
- **Compile-Time Type Safety**: TypeScript ensures all strings are complete for all languages at build time
|
|
12
|
+
- **Automatic Validation**: Comprehensive validation with detailed error reporting and missing key detection
|
|
13
|
+
- **Intelligent Fallback System**: Graceful degradation to default languages with missing translation tracking
|
|
14
|
+
- **Multi-Instance Support**: Named instances for different application contexts (admin, user, API, etc.)
|
|
15
|
+
- **Global Context Management**: Centralized context with per-instance language, currency, and timezone settings
|
|
16
|
+
- **Translation Adapters**: Generic adapter utilities for seamless integration with error classes and other components
|
|
15
17
|
|
|
16
18
|
## Features
|
|
17
19
|
|
|
18
|
-
### Core Features
|
|
20
|
+
### Core Translation Features
|
|
19
21
|
|
|
20
|
-
- **Type-Safe Translations**: Full TypeScript support with generic types for strings and
|
|
21
|
-
- **Plugin Architecture**: Register components and languages dynamically with full type safety
|
|
22
|
+
- **Type-Safe Translations**: Full TypeScript support with generic types for strings, languages, and contexts
|
|
23
|
+
- **Plugin Architecture**: Register components and languages dynamically with full compile-time type safety
|
|
22
24
|
- **Configuration Validation**: Automatic validation ensures all languages have complete string collections
|
|
23
|
-
- **Localized Error Messages**: Error messages
|
|
24
|
-
- **Enum Translation Registry**: Translate enum values with complete type safety
|
|
25
|
-
- **Template Processing**:
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
25
|
+
- **Localized Error Messages**: Error messages translated using the engine's own translation system
|
|
26
|
+
- **Enum Translation Registry**: Translate enum values with complete type safety and automatic validation
|
|
27
|
+
- **Advanced Template Processing**:
|
|
28
|
+
- Component-based patterns: `{{componentId.stringKey}}`
|
|
29
|
+
- Legacy enum patterns: `{{EnumName.EnumKey}}`
|
|
30
|
+
- Variable replacement: `{variableName}`
|
|
31
|
+
- Nested template support with multiple variable objects
|
|
32
|
+
- **Context Management**:
|
|
33
|
+
- Admin vs user translation contexts
|
|
34
|
+
- Automatic language switching based on context
|
|
35
|
+
- Per-instance context isolation
|
|
36
|
+
- Global context with named instance support
|
|
37
|
+
- **Currency Formatting**: Built-in currency formatting utilities with locale-aware symbol positioning
|
|
38
|
+
- **Timezone Support**: Validated timezone handling with moment-timezone integration
|
|
39
|
+
- **Intelligent Fallback System**:
|
|
40
|
+
- Graceful degradation when translations are missing
|
|
41
|
+
- Fallback to default language with tracking
|
|
42
|
+
- Placeholder generation for missing keys: `[componentId.stringKey]`
|
|
30
43
|
- **Extensible Configuration**: Module augmentation support for layered library extension
|
|
31
|
-
- **Backward Compatibility**: Legacy I18nEngine remains fully supported
|
|
32
|
-
|
|
33
|
-
###
|
|
34
|
-
|
|
35
|
-
- **Component Registry**:
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
44
|
+
- **Backward Compatibility**: Legacy I18nEngine remains fully supported for migration paths
|
|
45
|
+
|
|
46
|
+
### Plugin System Features
|
|
47
|
+
|
|
48
|
+
- **Component Registry**:
|
|
49
|
+
- Manage translation components with automatic validation
|
|
50
|
+
- Component isolation with independent string key namespaces
|
|
51
|
+
- Dynamic component registration and updates
|
|
52
|
+
- Comprehensive validation reporting
|
|
53
|
+
- **Language Registry**:
|
|
54
|
+
- Dynamic language registration with metadata (name, code, default flag)
|
|
55
|
+
- BCP 47 language code support
|
|
56
|
+
- Language lookup by ID or ISO code
|
|
57
|
+
- Display name mapping for UI rendering
|
|
58
|
+
- **Type-Safe Registration**:
|
|
59
|
+
- Compile-time guarantees for translation completeness
|
|
60
|
+
- Strict type helpers for enforcing complete translations
|
|
61
|
+
- Partial registration support with fallback generation
|
|
62
|
+
- **Validation System**:
|
|
63
|
+
- Detailed missing translation reports
|
|
64
|
+
- Per-component validation results
|
|
65
|
+
- Global validation across all components
|
|
66
|
+
- Configurable validation strictness
|
|
67
|
+
- **Instance Management**:
|
|
68
|
+
- Named instances for different application contexts
|
|
69
|
+
- Singleton pattern with automatic default instance
|
|
70
|
+
- Instance cleanup utilities for testing
|
|
71
|
+
- Per-instance context and configuration
|
|
72
|
+
|
|
73
|
+
### Advanced Features
|
|
74
|
+
|
|
75
|
+
- **Translatable Errors**:
|
|
76
|
+
- Generic translatable error class for any component
|
|
77
|
+
- Automatic translation with fallback support
|
|
78
|
+
- Error retranslation for dynamic language switching
|
|
79
|
+
- Metadata attachment for debugging
|
|
80
|
+
- **Translation Adapters**:
|
|
81
|
+
- Generic adapter creation for PluginI18nEngine
|
|
82
|
+
- Seamless integration with error classes
|
|
83
|
+
- Zero-overhead delegation pattern
|
|
84
|
+
- **Context Change Monitoring**:
|
|
85
|
+
- Reactive context proxies with change listeners
|
|
86
|
+
- Property-level change notifications
|
|
87
|
+
- Error-safe listener execution
|
|
88
|
+
- **Strict Type Enforcement**:
|
|
89
|
+
- Compile-time completeness checking
|
|
90
|
+
- Helper functions for strict translation maps
|
|
91
|
+
- Type utilities for extracting string keys and languages
|
|
92
|
+
- **Testing Utilities**:
|
|
93
|
+
- Instance cleanup methods
|
|
94
|
+
- Component registry reset
|
|
95
|
+
- Global engine reset for test isolation
|
|
96
|
+
|
|
97
|
+
## Table of Contents
|
|
98
|
+
|
|
99
|
+
- [Installation](#installation)
|
|
100
|
+
- [Quick Start](#quick-start)
|
|
101
|
+
- [Plugin-Based Architecture](#plugin-based-architecture)
|
|
102
|
+
- [Core Components](#core-components)
|
|
103
|
+
- [Advanced Features](#advanced-features)
|
|
104
|
+
- [API Reference](#api-reference)
|
|
105
|
+
- [Type Definitions](#type-definitions)
|
|
106
|
+
- [Testing](#testing)
|
|
107
|
+
- [Best Practices](#best-practices)
|
|
108
|
+
- [Migration Guide](#migration-guide)
|
|
109
|
+
- [Changelog](#changelog)
|
|
40
110
|
|
|
41
111
|
## Installation
|
|
42
112
|
|
|
43
113
|
```bash
|
|
44
114
|
npm install @digitaldefiance/i18n-lib
|
|
115
|
+
# or
|
|
116
|
+
yarn add @digitaldefiance/i18n-lib
|
|
45
117
|
```
|
|
46
118
|
|
|
119
|
+
### Dependencies
|
|
120
|
+
|
|
121
|
+
- `currency-codes`: Currency code validation
|
|
122
|
+
- `moment-timezone`: Timezone validation and handling
|
|
123
|
+
- TypeScript 4.5+ recommended for full type safety
|
|
124
|
+
|
|
47
125
|
## Quick Start
|
|
48
126
|
|
|
49
127
|
```typescript
|
|
@@ -99,6 +177,174 @@ const spanishGreeting = i18n.translate(MyStrings.UserGreetingTemplate, { name: '
|
|
|
99
177
|
// "¡Hola, Juan!"
|
|
100
178
|
```
|
|
101
179
|
|
|
180
|
+
## Core Components
|
|
181
|
+
|
|
182
|
+
The library is built around several key components that work together to provide comprehensive i18n support:
|
|
183
|
+
|
|
184
|
+
### Component Registry
|
|
185
|
+
|
|
186
|
+
Manages translation components with automatic validation:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { ComponentRegistry, ComponentDefinition, ComponentRegistration } from '@digitaldefiance/i18n-lib';
|
|
190
|
+
|
|
191
|
+
// Define component
|
|
192
|
+
const myComponent: ComponentDefinition<MyStringKeys> = {
|
|
193
|
+
id: 'my-component',
|
|
194
|
+
name: 'My Component',
|
|
195
|
+
stringKeys: Object.values(MyStringKeys)
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Register with translations
|
|
199
|
+
const registration: ComponentRegistration<MyStringKeys, Languages> = {
|
|
200
|
+
component: myComponent,
|
|
201
|
+
strings: {
|
|
202
|
+
'en-US': { /* translations */ },
|
|
203
|
+
'fr': { /* translations */ }
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Key Features:**
|
|
209
|
+
- Component isolation with independent string key namespaces
|
|
210
|
+
- Automatic validation of translation completeness
|
|
211
|
+
- Dynamic component registration and updates
|
|
212
|
+
- Fallback generation for missing translations
|
|
213
|
+
- Detailed validation reporting
|
|
214
|
+
|
|
215
|
+
### Language Registry
|
|
216
|
+
|
|
217
|
+
Manages supported languages with metadata:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { LanguageRegistry, LanguageDefinition, createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
221
|
+
|
|
222
|
+
const registry = new LanguageRegistry<'en-US' | 'fr' | 'es'>();
|
|
223
|
+
|
|
224
|
+
// Register languages
|
|
225
|
+
registry.registerLanguage(createLanguageDefinition('en-US', 'English (US)', 'en-US', true));
|
|
226
|
+
registry.registerLanguage(createLanguageDefinition('fr', 'Français', 'fr'));
|
|
227
|
+
|
|
228
|
+
// Query languages
|
|
229
|
+
const language = registry.getLanguage('en-US');
|
|
230
|
+
const byCode = registry.getLanguageByCode('fr');
|
|
231
|
+
const allLanguages = registry.getAllLanguages();
|
|
232
|
+
const displayNames = registry.getLanguageDisplayNames();
|
|
233
|
+
|
|
234
|
+
// Get language codes for Mongoose enum
|
|
235
|
+
const languageCodes = registry.getLanguageIds(); // ['en-US', 'fr', 'es']
|
|
236
|
+
const isoCodes = registry.getLanguageCodes(); // ['en-US', 'fr', 'es']
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Key Features:**
|
|
240
|
+
- BCP 47 language code support
|
|
241
|
+
- Language lookup by ID or ISO code
|
|
242
|
+
- Display name mapping for UI rendering
|
|
243
|
+
- Default language management
|
|
244
|
+
- Duplicate detection and validation
|
|
245
|
+
- Extract language codes for schema definitions
|
|
246
|
+
|
|
247
|
+
### Enum Translation Registry
|
|
248
|
+
|
|
249
|
+
Translates enum values with type safety:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { EnumTranslationRegistry } from '@digitaldefiance/i18n-lib';
|
|
253
|
+
|
|
254
|
+
enum Status {
|
|
255
|
+
Active = 'active',
|
|
256
|
+
Inactive = 'inactive'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const enumRegistry = new EnumTranslationRegistry<string, 'en-US' | 'fr'>(
|
|
260
|
+
['en-US', 'fr'],
|
|
261
|
+
(key, vars) => engine.translate('core', key, vars)
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
enumRegistry.register(Status, {
|
|
265
|
+
'en-US': {
|
|
266
|
+
[Status.Active]: 'Active',
|
|
267
|
+
[Status.Inactive]: 'Inactive'
|
|
268
|
+
},
|
|
269
|
+
'fr': {
|
|
270
|
+
[Status.Active]: 'Actif',
|
|
271
|
+
[Status.Inactive]: 'Inactif'
|
|
272
|
+
}
|
|
273
|
+
}, 'Status');
|
|
274
|
+
|
|
275
|
+
const translated = enumRegistry.translate(Status, Status.Active, 'fr'); // 'Actif'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Key Features:**
|
|
279
|
+
- Complete enum coverage validation
|
|
280
|
+
- Numeric and string enum support
|
|
281
|
+
- Automatic key resolution for numeric enums
|
|
282
|
+
- Localized error messages
|
|
283
|
+
|
|
284
|
+
### Global Active Context
|
|
285
|
+
|
|
286
|
+
Centralized context management for all engine instances:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { GlobalActiveContext, IActiveContext } from '@digitaldefiance/i18n-lib';
|
|
290
|
+
|
|
291
|
+
const globalContext = GlobalActiveContext.getInstance<'en-US' | 'fr', IActiveContext<'en-US' | 'fr'>>();
|
|
292
|
+
|
|
293
|
+
// Create context for an instance
|
|
294
|
+
globalContext.createContext('en-US', 'en-US', 'my-app');
|
|
295
|
+
|
|
296
|
+
// Update context properties
|
|
297
|
+
globalContext.setUserLanguage('fr', 'my-app');
|
|
298
|
+
globalContext.setAdminLanguage('en-US', 'my-app');
|
|
299
|
+
globalContext.setCurrencyCode(new CurrencyCode('EUR'), 'my-app');
|
|
300
|
+
globalContext.setUserTimezone(new Timezone('Europe/Paris'), 'my-app');
|
|
301
|
+
|
|
302
|
+
// Get context
|
|
303
|
+
const context = globalContext.getContext('my-app');
|
|
304
|
+
console.log(context.language); // 'fr'
|
|
305
|
+
console.log(context.adminLanguage); // 'en-US'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Key Features:**
|
|
309
|
+
- Per-instance context isolation
|
|
310
|
+
- User and admin language separation
|
|
311
|
+
- Currency code management
|
|
312
|
+
- Timezone handling
|
|
313
|
+
- Context space management (admin, user, system, api)
|
|
314
|
+
|
|
315
|
+
### Context Manager
|
|
316
|
+
|
|
317
|
+
Reactive context with change listeners:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { ContextManager } from '@digitaldefiance/i18n-lib';
|
|
321
|
+
|
|
322
|
+
interface AppContext {
|
|
323
|
+
language: string;
|
|
324
|
+
theme: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const manager = new ContextManager<AppContext>();
|
|
328
|
+
|
|
329
|
+
// Add change listener
|
|
330
|
+
manager.addListener((property, oldValue, newValue) => {
|
|
331
|
+
console.log(`${property} changed from ${oldValue} to ${newValue}`);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Create reactive proxy
|
|
335
|
+
const context = { language: 'en', theme: 'dark' };
|
|
336
|
+
const reactiveContext = manager.createProxy(context);
|
|
337
|
+
|
|
338
|
+
// Changes trigger listeners
|
|
339
|
+
reactiveContext.language = 'fr'; // Logs: "language changed from en to fr"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Key Features:**
|
|
343
|
+
- Property-level change notifications
|
|
344
|
+
- Multiple listener support
|
|
345
|
+
- Error-safe listener execution
|
|
346
|
+
- Proxy-based reactivity
|
|
347
|
+
|
|
102
348
|
## 🆕 Plugin-Based Architecture (New in v1.1.0)
|
|
103
349
|
|
|
104
350
|
The new plugin-based architecture provides a component registration system with rigid compile-time type safety.
|
|
@@ -108,9 +354,10 @@ The new plugin-based architecture provides a component registration system with
|
|
|
108
354
|
```typescript
|
|
109
355
|
import {
|
|
110
356
|
createCoreI18nEngine,
|
|
111
|
-
CoreStringKey,
|
|
357
|
+
CoreStringKey,
|
|
112
358
|
CoreLanguageCode,
|
|
113
359
|
LanguageCodes,
|
|
360
|
+
getCoreLanguageCodes,
|
|
114
361
|
ComponentDefinition,
|
|
115
362
|
ComponentRegistration
|
|
116
363
|
} from '@digitaldefiance/i18n-lib';
|
|
@@ -118,8 +365,15 @@ import {
|
|
|
118
365
|
// Create engine with default languages and core strings
|
|
119
366
|
const i18n = createCoreI18nEngine('myapp');
|
|
120
367
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
368
|
+
// Type-safe language parameter
|
|
369
|
+
const lang: CoreLanguageCode = LanguageCodes.FR; // Type-checked!
|
|
370
|
+
|
|
371
|
+
// Get supported language codes from registry (runtime)
|
|
372
|
+
const supportedLanguages = i18n.getLanguageRegistry().getLanguageIds();
|
|
373
|
+
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
374
|
+
|
|
375
|
+
// Use core translations with type safety
|
|
376
|
+
const welcomeMessage = i18n.translate('core', CoreStringKey.System_Welcome, undefined, lang);
|
|
123
377
|
const errorMessage = i18n.translate('core', CoreStringKey.Error_ValidationFailed);
|
|
124
378
|
|
|
125
379
|
// Define your own component with type safety
|
|
@@ -260,6 +514,296 @@ const userNotFound = i18n.translate(
|
|
|
260
514
|
);
|
|
261
515
|
```
|
|
262
516
|
|
|
517
|
+
## Advanced Features
|
|
518
|
+
|
|
519
|
+
### Translation Adapters
|
|
520
|
+
|
|
521
|
+
Create adapters to use PluginI18nEngine with components expecting the TranslationEngine interface:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { createTranslationAdapter, PluginI18nEngine, TranslationEngine } from '@digitaldefiance/i18n-lib';
|
|
525
|
+
|
|
526
|
+
const pluginEngine = PluginI18nEngine.getInstance<'en-US' | 'fr'>();
|
|
527
|
+
|
|
528
|
+
// Create adapter for a specific component
|
|
529
|
+
const adapter: TranslationEngine<MyStringKey> = createTranslationAdapter(
|
|
530
|
+
pluginEngine,
|
|
531
|
+
'my-component'
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// Use with error classes or other components
|
|
535
|
+
class MyError extends Error {
|
|
536
|
+
constructor(
|
|
537
|
+
type: ErrorType,
|
|
538
|
+
engine: TranslationEngine<ErrorStringKey>
|
|
539
|
+
) {
|
|
540
|
+
const message = engine.translate(type);
|
|
541
|
+
super(message);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const error = new MyError(ErrorType.NotFound, adapter);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Key Features:**
|
|
549
|
+
- Zero-overhead delegation to PluginI18nEngine
|
|
550
|
+
- Maintains full type safety
|
|
551
|
+
- Graceful error handling with fallback to key strings
|
|
552
|
+
- Seamless integration with existing code
|
|
553
|
+
|
|
554
|
+
### Translatable Errors
|
|
555
|
+
|
|
556
|
+
Generic error class with automatic translation:
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
import { TranslatableGenericError, PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
560
|
+
|
|
561
|
+
enum UserErrorKey {
|
|
562
|
+
UserNotFound = 'userNotFound',
|
|
563
|
+
InvalidCredentials = 'invalidCredentials'
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Throw translatable error
|
|
567
|
+
throw new TranslatableGenericError(
|
|
568
|
+
'user-errors',
|
|
569
|
+
UserErrorKey.UserNotFound,
|
|
570
|
+
{ username: 'john_doe' },
|
|
571
|
+
'en-US',
|
|
572
|
+
{ userId: 123 },
|
|
573
|
+
'myapp'
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
// Create with explicit engine
|
|
577
|
+
const engine = PluginI18nEngine.getInstance<'en-US' | 'fr'>();
|
|
578
|
+
const error = TranslatableGenericError.withEngine(
|
|
579
|
+
engine,
|
|
580
|
+
'user-errors',
|
|
581
|
+
UserErrorKey.InvalidCredentials,
|
|
582
|
+
undefined,
|
|
583
|
+
'fr'
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
// Retranslate dynamically
|
|
587
|
+
try {
|
|
588
|
+
// ... code that throws TranslatableGenericError
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (error instanceof TranslatableGenericError) {
|
|
591
|
+
const localizedMessage = error.retranslate('fr', 'myapp');
|
|
592
|
+
sendToUser(localizedMessage);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Key Features:**
|
|
598
|
+
- Works with any registered component
|
|
599
|
+
- Uses safeTranslate for consistent fallback behavior
|
|
600
|
+
- Stores error context for retranslation
|
|
601
|
+
- Never throws during construction
|
|
602
|
+
- Metadata attachment for debugging
|
|
603
|
+
|
|
604
|
+
### Typed Errors
|
|
605
|
+
|
|
606
|
+
Base classes for creating strongly-typed error hierarchies:
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
import { BaseTypedError, CompleteReasonMap, TranslationEngine } from '@digitaldefiance/i18n-lib';
|
|
610
|
+
|
|
611
|
+
enum DatabaseErrorType {
|
|
612
|
+
ConnectionFailed = 'connectionFailed',
|
|
613
|
+
QueryTimeout = 'queryTimeout'
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
enum DatabaseErrorKey {
|
|
617
|
+
ConnectionFailedMessage = 'connectionFailedMessage',
|
|
618
|
+
QueryTimeoutMessage = 'queryTimeoutMessage'
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const reasonMap: CompleteReasonMap<typeof DatabaseErrorType, DatabaseErrorKey> = {
|
|
622
|
+
[DatabaseErrorType.ConnectionFailed]: DatabaseErrorKey.ConnectionFailedMessage,
|
|
623
|
+
[DatabaseErrorType.QueryTimeout]: DatabaseErrorKey.QueryTimeoutMessage
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
class DatabaseError extends BaseTypedError<typeof DatabaseErrorType, DatabaseErrorKey> {
|
|
627
|
+
static create(
|
|
628
|
+
engine: TranslationEngine<DatabaseErrorKey>,
|
|
629
|
+
type: DatabaseErrorType,
|
|
630
|
+
metadata?: Record<string, any>
|
|
631
|
+
): DatabaseError {
|
|
632
|
+
return DatabaseError.createTranslated(
|
|
633
|
+
engine,
|
|
634
|
+
'database',
|
|
635
|
+
type,
|
|
636
|
+
reasonMap,
|
|
637
|
+
undefined,
|
|
638
|
+
undefined,
|
|
639
|
+
metadata
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Key Features:**
|
|
646
|
+
- Complete enum coverage enforcement
|
|
647
|
+
- Translation engine integration
|
|
648
|
+
- Simple and translated error creation
|
|
649
|
+
- Metadata support
|
|
650
|
+
|
|
651
|
+
### Template Processing
|
|
652
|
+
|
|
653
|
+
Advanced template system with multiple pattern types:
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
657
|
+
|
|
658
|
+
const engine = PluginI18nEngine.getInstance<'en-US'>();
|
|
659
|
+
|
|
660
|
+
// Component-based patterns
|
|
661
|
+
const message1 = engine.t(
|
|
662
|
+
'{{core.Common_Welcome}} {{user.UserGreetingTemplate}}',
|
|
663
|
+
'en-US',
|
|
664
|
+
{ name: 'John' }
|
|
665
|
+
);
|
|
666
|
+
// "Welcome Hello, John!"
|
|
667
|
+
|
|
668
|
+
// Variable replacement
|
|
669
|
+
const message2 = engine.t(
|
|
670
|
+
'User {username} has {count} messages',
|
|
671
|
+
'en-US',
|
|
672
|
+
{ username: 'john_doe', count: 5 }
|
|
673
|
+
);
|
|
674
|
+
// "User john_doe has 5 messages"
|
|
675
|
+
|
|
676
|
+
// Mixed patterns
|
|
677
|
+
const message3 = engine.t(
|
|
678
|
+
'{{core.System_Welcome}}, {name}! {{core.System_PleaseWait}}',
|
|
679
|
+
'en-US',
|
|
680
|
+
{ name: 'Alice' }
|
|
681
|
+
);
|
|
682
|
+
// "Welcome, Alice! Please wait..."
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Pattern Types:**
|
|
686
|
+
- `{{componentId.stringKey}}`: Component-based translation
|
|
687
|
+
- `{variableName}`: Variable replacement
|
|
688
|
+
- Template strings automatically use first variable object
|
|
689
|
+
- Multiple variable objects merged for replacement
|
|
690
|
+
|
|
691
|
+
### Currency Formatting
|
|
692
|
+
|
|
693
|
+
Locale-aware currency formatting:
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
import { getCurrencyFormat, CurrencyCode } from '@digitaldefiance/i18n-lib';
|
|
697
|
+
|
|
698
|
+
// Get format details
|
|
699
|
+
const usdFormat = getCurrencyFormat('en-US', 'USD');
|
|
700
|
+
console.log(usdFormat);
|
|
701
|
+
// {
|
|
702
|
+
// symbol: '$',
|
|
703
|
+
// position: 'prefix',
|
|
704
|
+
// groupSeparator: ',',
|
|
705
|
+
// decimalSeparator: '.'
|
|
706
|
+
// }
|
|
707
|
+
|
|
708
|
+
const eurFormat = getCurrencyFormat('de-DE', 'EUR');
|
|
709
|
+
console.log(eurFormat);
|
|
710
|
+
// {
|
|
711
|
+
// symbol: '€',
|
|
712
|
+
// position: 'postfix',
|
|
713
|
+
// groupSeparator: '.',
|
|
714
|
+
// decimalSeparator: ','
|
|
715
|
+
// }
|
|
716
|
+
|
|
717
|
+
// Validate currency codes
|
|
718
|
+
const currencyCode = new CurrencyCode('USD');
|
|
719
|
+
console.log(currencyCode.value); // 'USD'
|
|
720
|
+
console.log(CurrencyCode.values); // Array of all valid ISO 4217 codes
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**Key Features:**
|
|
724
|
+
- ISO 4217 currency code validation
|
|
725
|
+
- Locale-aware symbol positioning
|
|
726
|
+
- Group and decimal separator detection
|
|
727
|
+
- Intl.NumberFormat integration
|
|
728
|
+
|
|
729
|
+
### Timezone Handling
|
|
730
|
+
|
|
731
|
+
Validated timezone management:
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
import { Timezone, isValidTimezone } from '@digitaldefiance/i18n-lib';
|
|
735
|
+
|
|
736
|
+
// Create validated timezone
|
|
737
|
+
const tz = new Timezone('America/New_York');
|
|
738
|
+
console.log(tz.value); // 'America/New_York'
|
|
739
|
+
|
|
740
|
+
// Validate timezone strings
|
|
741
|
+
if (isValidTimezone('Europe/Paris')) {
|
|
742
|
+
const parisTz = new Timezone('Europe/Paris');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Invalid timezone throws error
|
|
746
|
+
try {
|
|
747
|
+
new Timezone('Invalid/Timezone');
|
|
748
|
+
} catch (error) {
|
|
749
|
+
console.error('Invalid timezone');
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
**Key Features:**
|
|
754
|
+
- Moment-timezone validation
|
|
755
|
+
- Immutable timezone values
|
|
756
|
+
- Validation utilities
|
|
757
|
+
- IANA timezone database support
|
|
758
|
+
|
|
759
|
+
### Utility Functions
|
|
760
|
+
|
|
761
|
+
Helper functions for common operations:
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
import {
|
|
765
|
+
replaceVariables,
|
|
766
|
+
isTemplate,
|
|
767
|
+
toStringKey,
|
|
768
|
+
buildReasonMap,
|
|
769
|
+
validateReasonMap,
|
|
770
|
+
createCompleteReasonMap
|
|
771
|
+
} from '@digitaldefiance/i18n-lib';
|
|
772
|
+
|
|
773
|
+
// Variable replacement
|
|
774
|
+
const result = replaceVariables(
|
|
775
|
+
'Hello, {name}! You have {count} messages.',
|
|
776
|
+
{ name: 'John', count: 5 }
|
|
777
|
+
);
|
|
778
|
+
// "Hello, John! You have 5 messages."
|
|
779
|
+
|
|
780
|
+
// Template detection
|
|
781
|
+
if (isTemplate('userGreetingTemplate')) {
|
|
782
|
+
// Handle as template
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// String key construction
|
|
786
|
+
const key = toStringKey('error', 'validation', 'failed');
|
|
787
|
+
// 'error_validation_failed'
|
|
788
|
+
|
|
789
|
+
// Reason map building
|
|
790
|
+
enum ErrorType {
|
|
791
|
+
NotFound = 'notFound',
|
|
792
|
+
AccessDenied = 'accessDenied'
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const reasonMap = buildReasonMap(ErrorType, ['error']);
|
|
796
|
+
// {
|
|
797
|
+
// notFound: 'error_notFound',
|
|
798
|
+
// accessDenied: 'error_accessDenied'
|
|
799
|
+
// }
|
|
800
|
+
|
|
801
|
+
// Validate reason map completeness
|
|
802
|
+
if (validateReasonMap(ErrorType, reasonMap)) {
|
|
803
|
+
// All enum values are mapped
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
263
807
|
### Advanced Plugin Usage
|
|
264
808
|
|
|
265
809
|
#### Compile-Time Completeness Enforcement (Strict Mode)
|
|
@@ -676,6 +1220,144 @@ I18nEngine.clearInstances();
|
|
|
676
1220
|
I18nEngine.removeInstance('main');
|
|
677
1221
|
```
|
|
678
1222
|
|
|
1223
|
+
## Supported Languages
|
|
1224
|
+
|
|
1225
|
+
The library includes pre-built translations for 8 languages in the core component:
|
|
1226
|
+
|
|
1227
|
+
| Language Code | Display Name | ISO Code |
|
|
1228
|
+
|--------------|--------------|----------|
|
|
1229
|
+
| `en-US` | English (US) | en-US |
|
|
1230
|
+
| `en-GB` | English (UK) | en-GB |
|
|
1231
|
+
| `fr` | Français | fr |
|
|
1232
|
+
| `es` | Español | es |
|
|
1233
|
+
| `de` | Deutsch | de |
|
|
1234
|
+
| `zh-CN` | 中文 (简体) | zh-CN |
|
|
1235
|
+
| `ja` | 日本語 | ja |
|
|
1236
|
+
| `uk` | Українська | uk |
|
|
1237
|
+
|
|
1238
|
+
### Language Code Constants
|
|
1239
|
+
|
|
1240
|
+
```typescript
|
|
1241
|
+
import { LanguageCodes, LanguageDisplayNames, getCoreLanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1242
|
+
|
|
1243
|
+
// Use constants for type safety
|
|
1244
|
+
const lang = LanguageCodes.FR; // 'fr'
|
|
1245
|
+
const displayName = LanguageDisplayNames[LanguageCodes.FR]; // 'Français'
|
|
1246
|
+
|
|
1247
|
+
// Get all core language codes as array (for Mongoose enums, etc.)
|
|
1248
|
+
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1249
|
+
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1250
|
+
|
|
1251
|
+
// All available codes
|
|
1252
|
+
const codes = {
|
|
1253
|
+
EN_US: 'en-US',
|
|
1254
|
+
EN_GB: 'en-GB',
|
|
1255
|
+
FR: 'fr',
|
|
1256
|
+
ES: 'es',
|
|
1257
|
+
DE: 'de',
|
|
1258
|
+
ZH_CN: 'zh-CN',
|
|
1259
|
+
JA: 'ja',
|
|
1260
|
+
UK: 'uk'
|
|
1261
|
+
};
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
### Custom Language Codes
|
|
1265
|
+
|
|
1266
|
+
Extend core languages with custom ones while maintaining type safety:
|
|
1267
|
+
|
|
1268
|
+
```typescript
|
|
1269
|
+
import {
|
|
1270
|
+
CoreLanguageCode,
|
|
1271
|
+
getCoreLanguageDefinitions,
|
|
1272
|
+
createLanguageDefinition,
|
|
1273
|
+
PluginI18nEngine
|
|
1274
|
+
} from '@digitaldefiance/i18n-lib';
|
|
1275
|
+
|
|
1276
|
+
// Define custom language type extending core
|
|
1277
|
+
type MyLanguageCode = CoreLanguageCode | 'pt-BR' | 'it';
|
|
1278
|
+
|
|
1279
|
+
// Create engine with extended languages
|
|
1280
|
+
const engine = PluginI18nEngine.createInstance<MyLanguageCode>(
|
|
1281
|
+
'myapp',
|
|
1282
|
+
[
|
|
1283
|
+
...getCoreLanguageDefinitions(),
|
|
1284
|
+
createLanguageDefinition('pt-BR', 'Português (Brasil)', 'pt-BR'),
|
|
1285
|
+
createLanguageDefinition('it', 'Italiano', 'it')
|
|
1286
|
+
]
|
|
1287
|
+
);
|
|
1288
|
+
|
|
1289
|
+
// Type-safe language usage
|
|
1290
|
+
const lang1: MyLanguageCode = 'en-US'; // ✓ Core language
|
|
1291
|
+
const lang2: MyLanguageCode = 'pt-BR'; // ✓ Custom language
|
|
1292
|
+
const lang3: MyLanguageCode = 'invalid'; // ✗ Type error!
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
### Helper Functions for Mongoose Schemas
|
|
1296
|
+
|
|
1297
|
+
Extract language codes from the registry (single source of truth):
|
|
1298
|
+
|
|
1299
|
+
```typescript
|
|
1300
|
+
import { PluginI18nEngine, getCoreLanguageCodes, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1301
|
+
import { Schema } from 'mongoose';
|
|
1302
|
+
|
|
1303
|
+
// Static approach: Get core language codes as array
|
|
1304
|
+
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1305
|
+
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1306
|
+
|
|
1307
|
+
// Dynamic approach: Get from engine instance (includes custom languages)
|
|
1308
|
+
const engine = PluginI18nEngine.getInstance<string>();
|
|
1309
|
+
const languageIds = engine.getLanguageRegistry().getLanguageIds();
|
|
1310
|
+
const isoCodes = engine.getLanguageRegistry().getLanguageCodes();
|
|
1311
|
+
|
|
1312
|
+
// Use in Mongoose schema
|
|
1313
|
+
const userSchema = new Schema({
|
|
1314
|
+
language: {
|
|
1315
|
+
type: String,
|
|
1316
|
+
enum: coreLanguageCodes, // Static core languages
|
|
1317
|
+
default: LanguageCodes.EN_US
|
|
1318
|
+
},
|
|
1319
|
+
adminLanguage: {
|
|
1320
|
+
type: String,
|
|
1321
|
+
enum: languageIds, // Dynamic from registry
|
|
1322
|
+
default: LanguageCodes.EN_US
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// Get display names for validation messages
|
|
1327
|
+
const displayNames = engine.getLanguageRegistry().getLanguageDisplayNames();
|
|
1328
|
+
// { 'en-US': 'English (US)', 'fr': 'Français', ... }
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
## Core String Keys
|
|
1332
|
+
|
|
1333
|
+
The core component provides 40+ system strings organized by category:
|
|
1334
|
+
|
|
1335
|
+
### Common Strings
|
|
1336
|
+
- `Common_Yes`, `Common_No`, `Common_Cancel`, `Common_OK`
|
|
1337
|
+
- `Common_Save`, `Common_Delete`, `Common_Edit`, `Common_Create`, `Common_Update`
|
|
1338
|
+
- `Common_Loading`, `Common_Error`, `Common_Success`, `Common_Warning`, `Common_Info`
|
|
1339
|
+
- `Common_Disposed`
|
|
1340
|
+
|
|
1341
|
+
### Error Messages
|
|
1342
|
+
- `Error_InvalidInput`, `Error_NetworkError`, `Error_NotFound`
|
|
1343
|
+
- `Error_AccessDenied`, `Error_InternalServer`, `Error_ValidationFailed`
|
|
1344
|
+
- `Error_RequiredField`, `Error_InvalidContextTemplate`
|
|
1345
|
+
- `Error_MissingTranslationKeyTemplate`
|
|
1346
|
+
|
|
1347
|
+
### Registry Error Templates
|
|
1348
|
+
- `Error_ComponentNotFoundTemplate`
|
|
1349
|
+
- `Error_LanguageNotFoundTemplate`
|
|
1350
|
+
- `Error_StringKeyNotFoundTemplate`
|
|
1351
|
+
- `Error_IncompleteRegistrationTemplate`
|
|
1352
|
+
- `Error_DuplicateComponentTemplate`
|
|
1353
|
+
- `Error_DuplicateLanguageTemplate`
|
|
1354
|
+
- `Error_ValidationFailedTemplate`
|
|
1355
|
+
|
|
1356
|
+
### System Messages
|
|
1357
|
+
- `System_Welcome`, `System_Goodbye`, `System_PleaseWait`
|
|
1358
|
+
- `System_ProcessingRequest`, `System_OperationComplete`
|
|
1359
|
+
- `System_NoDataAvailable`
|
|
1360
|
+
|
|
679
1361
|
## API Reference
|
|
680
1362
|
|
|
681
1363
|
### Plugin Architecture API
|
|
@@ -1008,6 +1690,133 @@ If localized error messages aren't provided, the engine falls back to English te
|
|
|
1008
1690
|
- `Missing translation for key '{key}' in language '{language}'`
|
|
1009
1691
|
- `Default language '{language}' has no string collection`
|
|
1010
1692
|
|
|
1693
|
+
## Error Handling
|
|
1694
|
+
|
|
1695
|
+
The library provides comprehensive error handling with localized error messages:
|
|
1696
|
+
|
|
1697
|
+
### Registry Errors
|
|
1698
|
+
|
|
1699
|
+
```typescript
|
|
1700
|
+
import { RegistryError, RegistryErrorType } from '@digitaldefiance/i18n-lib';
|
|
1701
|
+
|
|
1702
|
+
try {
|
|
1703
|
+
engine.registerComponent(invalidRegistration);
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
if (error instanceof RegistryError) {
|
|
1706
|
+
console.error(`Error type: ${error.type}`);
|
|
1707
|
+
console.error(`Metadata:`, error.metadata);
|
|
1708
|
+
|
|
1709
|
+
switch (error.type) {
|
|
1710
|
+
case RegistryErrorType.ComponentNotFound:
|
|
1711
|
+
// Handle missing component
|
|
1712
|
+
break;
|
|
1713
|
+
case RegistryErrorType.DuplicateComponent:
|
|
1714
|
+
// Handle duplicate registration
|
|
1715
|
+
break;
|
|
1716
|
+
case RegistryErrorType.ValidationFailed:
|
|
1717
|
+
// Handle validation failure
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
**Error Types:**
|
|
1725
|
+
- `ComponentNotFound`: Component ID not registered
|
|
1726
|
+
- `LanguageNotFound`: Language not registered
|
|
1727
|
+
- `StringKeyNotFound`: Translation key not found
|
|
1728
|
+
- `IncompleteRegistration`: Missing translations detected
|
|
1729
|
+
- `DuplicateComponent`: Component already registered
|
|
1730
|
+
- `DuplicateLanguage`: Language already registered
|
|
1731
|
+
- `ValidationFailed`: Validation check failed
|
|
1732
|
+
|
|
1733
|
+
### Context Errors
|
|
1734
|
+
|
|
1735
|
+
```typescript
|
|
1736
|
+
import { ContextError, ContextErrorType } from '@digitaldefiance/i18n-lib';
|
|
1737
|
+
|
|
1738
|
+
try {
|
|
1739
|
+
const context = globalContext.getContext('invalid-key');
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
if (error instanceof ContextError) {
|
|
1742
|
+
console.error(`Invalid context: ${error.contextKey}`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
```
|
|
1746
|
+
|
|
1747
|
+
### Safe Translation
|
|
1748
|
+
|
|
1749
|
+
Use `safeTranslate` to prevent errors:
|
|
1750
|
+
|
|
1751
|
+
```typescript
|
|
1752
|
+
// Regular translate throws on missing key
|
|
1753
|
+
try {
|
|
1754
|
+
const text = engine.translate('component', 'missingKey');
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
// Handle error
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Safe translate returns placeholder
|
|
1760
|
+
const text = engine.safeTranslate('component', 'missingKey');
|
|
1761
|
+
// Returns: "[component.missingKey]"
|
|
1762
|
+
```
|
|
1763
|
+
|
|
1764
|
+
## Validation
|
|
1765
|
+
|
|
1766
|
+
### Component Validation
|
|
1767
|
+
|
|
1768
|
+
```typescript
|
|
1769
|
+
// Validate during registration
|
|
1770
|
+
const result = engine.registerComponent(registration);
|
|
1771
|
+
|
|
1772
|
+
if (!result.isValid) {
|
|
1773
|
+
console.error('Validation errors:', result.errors);
|
|
1774
|
+
console.warn('Missing keys:', result.missingKeys);
|
|
1775
|
+
|
|
1776
|
+
result.missingKeys.forEach(missing => {
|
|
1777
|
+
console.log(`Missing: ${missing.stringKey} for ${missing.languageId} in ${missing.componentId}`);
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
### Global Validation
|
|
1783
|
+
|
|
1784
|
+
```typescript
|
|
1785
|
+
// Validate all components
|
|
1786
|
+
const validation = engine.validateAllComponents();
|
|
1787
|
+
|
|
1788
|
+
if (!validation.isValid) {
|
|
1789
|
+
console.error('Errors:', validation.errors);
|
|
1790
|
+
console.warn('Warnings:', validation.warnings);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Example output:
|
|
1794
|
+
// Errors: ["Component 'user' missing strings for language 'de'"]
|
|
1795
|
+
// Warnings: ["Component 'user' missing key 'greeting' for language 'fr'"]
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
### Validation Configuration
|
|
1799
|
+
|
|
1800
|
+
```typescript
|
|
1801
|
+
import { ValidationConfig } from '@digitaldefiance/i18n-lib';
|
|
1802
|
+
|
|
1803
|
+
const strictConfig: ValidationConfig = {
|
|
1804
|
+
requireCompleteStrings: true,
|
|
1805
|
+
allowPartialRegistration: false,
|
|
1806
|
+
fallbackLanguageId: 'en-US'
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
const lenientConfig: ValidationConfig = {
|
|
1810
|
+
requireCompleteStrings: false,
|
|
1811
|
+
allowPartialRegistration: true,
|
|
1812
|
+
fallbackLanguageId: 'en-US'
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
const engine = new PluginI18nEngine(languages, {
|
|
1816
|
+
validation: strictConfig
|
|
1817
|
+
});
|
|
1818
|
+
```
|
|
1819
|
+
|
|
1011
1820
|
## Best Practices
|
|
1012
1821
|
|
|
1013
1822
|
### Plugin Architecture (Recommended for New Projects)
|
|
@@ -1049,16 +1858,396 @@ const text = modern.translate('my-component', MyStrings.Welcome);
|
|
|
1049
1858
|
|
|
1050
1859
|
Both systems can coexist in the same application during migration.
|
|
1051
1860
|
|
|
1861
|
+
## Performance Considerations
|
|
1862
|
+
|
|
1863
|
+
### Instance Management
|
|
1864
|
+
|
|
1865
|
+
- Use named instances to isolate different application contexts
|
|
1866
|
+
- Reuse instances rather than creating new ones
|
|
1867
|
+
- Clean up instances in tests to prevent memory leaks
|
|
1868
|
+
|
|
1869
|
+
### Translation Caching
|
|
1870
|
+
|
|
1871
|
+
- Translations are stored in memory for fast access
|
|
1872
|
+
- Component strings are validated once during registration
|
|
1873
|
+
- Fallback translations are generated during registration, not at runtime
|
|
1874
|
+
|
|
1875
|
+
### Type Safety Overhead
|
|
1876
|
+
|
|
1877
|
+
- Compile-time type checking has zero runtime cost
|
|
1878
|
+
- Generic types are erased during compilation
|
|
1879
|
+
- Validation runs only during registration, not translation
|
|
1880
|
+
|
|
1881
|
+
## Security Considerations
|
|
1882
|
+
|
|
1883
|
+
### Zero-Knowledge Patterns
|
|
1884
|
+
|
|
1885
|
+
The library is designed to support zero-knowledge security patterns:
|
|
1886
|
+
|
|
1887
|
+
- No translation data is sent to external services
|
|
1888
|
+
- All translations are stored locally
|
|
1889
|
+
- No telemetry or analytics
|
|
1890
|
+
- Suitable for sensitive applications
|
|
1891
|
+
|
|
1892
|
+
### Input Sanitization
|
|
1893
|
+
|
|
1894
|
+
Always sanitize user input before using in translations:
|
|
1895
|
+
|
|
1896
|
+
```typescript
|
|
1897
|
+
import { replaceVariables } from '@digitaldefiance/i18n-lib';
|
|
1898
|
+
|
|
1899
|
+
// BAD: Direct user input
|
|
1900
|
+
const message = engine.translate('component', 'template', {
|
|
1901
|
+
userInput: req.body.unsafeInput
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
// GOOD: Sanitized input
|
|
1905
|
+
const message = engine.translate('component', 'template', {
|
|
1906
|
+
userInput: sanitize(req.body.unsafeInput)
|
|
1907
|
+
});
|
|
1908
|
+
```
|
|
1909
|
+
|
|
1910
|
+
### Error Message Exposure
|
|
1911
|
+
|
|
1912
|
+
Be careful not to expose sensitive information in error messages:
|
|
1913
|
+
|
|
1914
|
+
```typescript
|
|
1915
|
+
// BAD: Exposes internal details
|
|
1916
|
+
throw new TranslatableGenericError(
|
|
1917
|
+
'auth',
|
|
1918
|
+
'loginFailed',
|
|
1919
|
+
{ password: user.password }, // Don't include sensitive data
|
|
1920
|
+
language
|
|
1921
|
+
);
|
|
1922
|
+
|
|
1923
|
+
// GOOD: Safe error message
|
|
1924
|
+
throw new TranslatableGenericError(
|
|
1925
|
+
'auth',
|
|
1926
|
+
'loginFailed',
|
|
1927
|
+
{ username: user.username },
|
|
1928
|
+
language,
|
|
1929
|
+
{ userId: user.id } // Metadata for logging only
|
|
1930
|
+
);
|
|
1931
|
+
```
|
|
1932
|
+
|
|
1933
|
+
## Mongoose Integration
|
|
1934
|
+
|
|
1935
|
+
### Schema Definition with Language Enums
|
|
1936
|
+
|
|
1937
|
+
```typescript
|
|
1938
|
+
import { Schema, model } from 'mongoose';
|
|
1939
|
+
import { LanguageCodes, getCoreLanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1940
|
+
|
|
1941
|
+
// Get core language codes from helper (single source of truth)
|
|
1942
|
+
const supportedLanguages = getCoreLanguageCodes();
|
|
1943
|
+
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1944
|
+
|
|
1945
|
+
const userSchema = new Schema({
|
|
1946
|
+
language: {
|
|
1947
|
+
type: String,
|
|
1948
|
+
enum: supportedLanguages,
|
|
1949
|
+
default: LanguageCodes.EN_US,
|
|
1950
|
+
required: true
|
|
1951
|
+
},
|
|
1952
|
+
adminLanguage: {
|
|
1953
|
+
type: String,
|
|
1954
|
+
enum: supportedLanguages,
|
|
1955
|
+
default: LanguageCodes.EN_US
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
export const User = model('User', userSchema);
|
|
1960
|
+
```
|
|
1961
|
+
|
|
1962
|
+
### Dynamic Language Enum from Engine
|
|
1963
|
+
|
|
1964
|
+
```typescript
|
|
1965
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1966
|
+
|
|
1967
|
+
// Get languages from engine instance
|
|
1968
|
+
const engine = PluginI18nEngine.getInstance();
|
|
1969
|
+
const registry = engine.getLanguageRegistry();
|
|
1970
|
+
|
|
1971
|
+
// Extract for Mongoose
|
|
1972
|
+
const languageEnum = registry.getLanguageIds();
|
|
1973
|
+
const defaultLanguage = registry.getDefaultLanguageId();
|
|
1974
|
+
|
|
1975
|
+
const contentSchema = new Schema({
|
|
1976
|
+
language: {
|
|
1977
|
+
type: String,
|
|
1978
|
+
enum: languageEnum,
|
|
1979
|
+
default: defaultLanguage
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
```
|
|
1983
|
+
|
|
1984
|
+
### Validation with Display Names
|
|
1985
|
+
|
|
1986
|
+
```typescript
|
|
1987
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1988
|
+
|
|
1989
|
+
const engine = PluginI18nEngine.getInstance();
|
|
1990
|
+
const displayNames = engine.getLanguageRegistry().getLanguageDisplayNames();
|
|
1991
|
+
|
|
1992
|
+
const settingsSchema = new Schema({
|
|
1993
|
+
language: {
|
|
1994
|
+
type: String,
|
|
1995
|
+
enum: Object.keys(displayNames),
|
|
1996
|
+
validate: {
|
|
1997
|
+
validator: (v: string) => v in displayNames,
|
|
1998
|
+
message: (props) => {
|
|
1999
|
+
const validLanguages = Object.entries(displayNames)
|
|
2000
|
+
.map(([code, name]) => `${name} (${code})`)
|
|
2001
|
+
.join(', ');
|
|
2002
|
+
return `${props.value} is not a valid language. Valid options: ${validLanguages}`;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
```
|
|
2008
|
+
|
|
2009
|
+
## Integration Examples
|
|
2010
|
+
|
|
2011
|
+
### Express.js Middleware
|
|
2012
|
+
|
|
2013
|
+
```typescript
|
|
2014
|
+
import { PluginI18nEngine, CoreLanguageCode } from '@digitaldefiance/i18n-lib';
|
|
2015
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2016
|
+
|
|
2017
|
+
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2018
|
+
|
|
2019
|
+
function i18nMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
2020
|
+
// Get language from header, query, or cookie
|
|
2021
|
+
const language = (req.headers['accept-language'] ||
|
|
2022
|
+
req.query.lang ||
|
|
2023
|
+
req.cookies.language ||
|
|
2024
|
+
'en-US') as CoreLanguageCode;
|
|
2025
|
+
|
|
2026
|
+
// Set language for this request (type-safe)
|
|
2027
|
+
engine.setLanguage(language);
|
|
2028
|
+
|
|
2029
|
+
// Add translation helper to response locals
|
|
2030
|
+
res.locals.t = (componentId: string, key: string, vars?: any) => {
|
|
2031
|
+
return engine.translate(componentId, key, vars, language);
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
next();
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
app.use(i18nMiddleware);
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2040
|
+
### React Integration
|
|
2041
|
+
|
|
2042
|
+
```typescript
|
|
2043
|
+
import React, { createContext, useContext, useState } from 'react';
|
|
2044
|
+
import { PluginI18nEngine, CoreLanguageCode, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
2045
|
+
|
|
2046
|
+
const I18nContext = createContext<{
|
|
2047
|
+
engine: PluginI18nEngine<CoreLanguageCode>;
|
|
2048
|
+
language: CoreLanguageCode;
|
|
2049
|
+
setLanguage: (lang: CoreLanguageCode) => void;
|
|
2050
|
+
} | null>(null);
|
|
2051
|
+
|
|
2052
|
+
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
|
2053
|
+
const [engine] = useState(() => PluginI18nEngine.getInstance<CoreLanguageCode>());
|
|
2054
|
+
const [language, setLanguageState] = useState<CoreLanguageCode>(LanguageCodes.EN_US);
|
|
2055
|
+
|
|
2056
|
+
const setLanguage = (lang: CoreLanguageCode) => {
|
|
2057
|
+
engine.setLanguage(lang);
|
|
2058
|
+
setLanguageState(lang);
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
return (
|
|
2062
|
+
<I18nContext.Provider value={{ engine, language, setLanguage }}>
|
|
2063
|
+
{children}
|
|
2064
|
+
</I18nContext.Provider>
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
export function useI18n() {
|
|
2069
|
+
const context = useContext(I18nContext);
|
|
2070
|
+
if (!context) throw new Error('useI18n must be used within I18nProvider');
|
|
2071
|
+
|
|
2072
|
+
const t = (componentId: string, key: string, vars?: any) => {
|
|
2073
|
+
return context.engine.translate(componentId, key, vars, context.language);
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
return { ...context, t };
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// Usage in component
|
|
2080
|
+
function MyComponent() {
|
|
2081
|
+
const { t, language, setLanguage } = useI18n();
|
|
2082
|
+
|
|
2083
|
+
return (
|
|
2084
|
+
<div>
|
|
2085
|
+
<h1>{t('core', CoreStringKey.System_Welcome)}</h1>
|
|
2086
|
+
<button onClick={() => setLanguage('fr')}>
|
|
2087
|
+
Switch to French
|
|
2088
|
+
</button>
|
|
2089
|
+
</div>
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
```
|
|
2093
|
+
|
|
2094
|
+
### Vue.js Plugin
|
|
2095
|
+
|
|
2096
|
+
```typescript
|
|
2097
|
+
import { Plugin } from 'vue';
|
|
2098
|
+
import { PluginI18nEngine, CoreLanguageCode } from '@digitaldefiance/i18n-lib';
|
|
2099
|
+
|
|
2100
|
+
const i18nPlugin: Plugin = {
|
|
2101
|
+
install(app, options) {
|
|
2102
|
+
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2103
|
+
|
|
2104
|
+
app.config.globalProperties.$t = (
|
|
2105
|
+
componentId: string,
|
|
2106
|
+
key: string,
|
|
2107
|
+
vars?: any
|
|
2108
|
+
) => {
|
|
2109
|
+
return engine.translate(componentId, key, vars);
|
|
2110
|
+
};
|
|
2111
|
+
|
|
2112
|
+
app.config.globalProperties.$i18n = engine;
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
export default i18nPlugin;
|
|
2117
|
+
|
|
2118
|
+
// Usage in component
|
|
2119
|
+
// <template>
|
|
2120
|
+
// <div>{{ $t('core', 'System_Welcome') }}</div>
|
|
2121
|
+
// </template>
|
|
2122
|
+
```
|
|
2123
|
+
|
|
2124
|
+
## Troubleshooting
|
|
2125
|
+
|
|
2126
|
+
### Common Issues
|
|
2127
|
+
|
|
2128
|
+
**Issue: "Instance with key 'X' not found"**
|
|
2129
|
+
```typescript
|
|
2130
|
+
// Solution: Create instance before using
|
|
2131
|
+
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
2132
|
+
// Or use default instance
|
|
2133
|
+
const engine = PluginI18nEngine.getInstance(); // Uses 'default' key
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
**Issue: "Component 'X' not found"**
|
|
2137
|
+
```typescript
|
|
2138
|
+
// Solution: Register component before translating
|
|
2139
|
+
engine.registerComponent(myComponentRegistration);
|
|
2140
|
+
const text = engine.translate('my-component', 'key');
|
|
2141
|
+
```
|
|
2142
|
+
|
|
2143
|
+
**Issue: "Language 'X' not found"**
|
|
2144
|
+
```typescript
|
|
2145
|
+
// Solution: Register language before using
|
|
2146
|
+
engine.registerLanguage(createLanguageDefinition('fr', 'Français', 'fr'));
|
|
2147
|
+
engine.setLanguage('fr');
|
|
2148
|
+
```
|
|
2149
|
+
|
|
2150
|
+
**Issue: Missing translations in production**
|
|
2151
|
+
```typescript
|
|
2152
|
+
// Solution: Use validation to catch missing translations
|
|
2153
|
+
const validation = engine.validateAllComponents();
|
|
2154
|
+
if (!validation.isValid) {
|
|
2155
|
+
console.error('Missing translations:', validation.errors);
|
|
2156
|
+
// Fix translations before deploying
|
|
2157
|
+
}
|
|
2158
|
+
```
|
|
2159
|
+
|
|
2160
|
+
**Issue: Type errors with string keys**
|
|
2161
|
+
```typescript
|
|
2162
|
+
// Solution: Use enum values, not strings
|
|
2163
|
+
enum MyKeys {
|
|
2164
|
+
Welcome = 'welcome'
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// BAD
|
|
2168
|
+
engine.translate('component', 'welcome'); // Type error
|
|
2169
|
+
|
|
2170
|
+
// GOOD
|
|
2171
|
+
engine.translate('component', MyKeys.Welcome);
|
|
2172
|
+
```
|
|
2173
|
+
|
|
1052
2174
|
## License
|
|
1053
2175
|
|
|
1054
|
-
MIT
|
|
2176
|
+
MIT License - See LICENSE file for details
|
|
1055
2177
|
|
|
1056
2178
|
## Repository
|
|
1057
2179
|
|
|
1058
|
-
Part of the DigitalBurnbag project - a secure file sharing and automated protocol system.
|
|
2180
|
+
Part of the [DigitalBurnbag](https://github.com/Digital-Defiance/DigitalBurnbag) project - a secure file sharing and automated protocol system with zero-knowledge encryption.
|
|
2181
|
+
|
|
2182
|
+
## Contributing
|
|
2183
|
+
|
|
2184
|
+
Contributions are welcome! Please:
|
|
2185
|
+
|
|
2186
|
+
1. Fork the repository
|
|
2187
|
+
2. Create a feature branch
|
|
2188
|
+
3. Add tests for new functionality
|
|
2189
|
+
4. Ensure all tests pass
|
|
2190
|
+
5. Submit a pull request
|
|
2191
|
+
|
|
2192
|
+
### Development Setup
|
|
2193
|
+
|
|
2194
|
+
```bash
|
|
2195
|
+
# Clone repository
|
|
2196
|
+
git clone https://github.com/Digital-Defiance/DigitalBurnbag.git
|
|
2197
|
+
cd DigitalBurnbag/packages/digitaldefiance-i18n-lib
|
|
2198
|
+
|
|
2199
|
+
# Install dependencies
|
|
2200
|
+
yarn install
|
|
2201
|
+
|
|
2202
|
+
# Run tests
|
|
2203
|
+
yarn test
|
|
2204
|
+
|
|
2205
|
+
# Build
|
|
2206
|
+
yarn build
|
|
2207
|
+
```
|
|
2208
|
+
|
|
2209
|
+
## Support
|
|
2210
|
+
|
|
2211
|
+
For issues, questions, or contributions:
|
|
2212
|
+
- GitHub Issues: https://github.com/Digital-Defiance/DigitalBurnbag/issues
|
|
2213
|
+
- Documentation: See README.md and inline code documentation
|
|
2214
|
+
- Examples: See `examples/` directory in repository
|
|
1059
2215
|
|
|
1060
2216
|
## ChangeLog
|
|
1061
2217
|
|
|
2218
|
+
### Version 1.3.0
|
|
2219
|
+
|
|
2220
|
+
- **Changed**: `CoreLanguageCode` now derived from `LanguageCodes` for type safety
|
|
2221
|
+
- Type: `typeof LanguageCodes[keyof typeof LanguageCodes]`
|
|
2222
|
+
- Maintains compile-time type safety while using registry as single source
|
|
2223
|
+
- No breaking changes - existing code continues to work
|
|
2224
|
+
- **Added**: `getCoreLanguageCodes()` - Get core language codes as runtime array
|
|
2225
|
+
- **Added**: `getCoreLanguageDefinitions()` - Get core language definitions
|
|
2226
|
+
- **Improved**: Language Registry is now the single source of truth
|
|
2227
|
+
- **Benefit**: Type safety + runtime flexibility without duplication
|
|
2228
|
+
|
|
2229
|
+
### Version 1.2.5
|
|
2230
|
+
|
|
2231
|
+
- Sat Oct 25 2025 15:0100 GMT-0700 (Pacific Daylight Time)
|
|
2232
|
+
|
|
2233
|
+
#### Added
|
|
2234
|
+
- **`createTranslationAdapter`** - Generic utility function to adapt `PluginI18nEngine` instances to the `TranslationEngine` interface, enabling seamless integration with error classes and other components expecting the simpler interface
|
|
2235
|
+
- Maintains full type safety with generic string key and language types
|
|
2236
|
+
- Provides graceful error handling with fallback to key strings
|
|
2237
|
+
- Zero overhead - direct delegation to underlying `PluginI18nEngine`
|
|
2238
|
+
- Comprehensive test coverage (19 tests)
|
|
2239
|
+
|
|
2240
|
+
#### Benefits
|
|
2241
|
+
- Eliminates need for custom adapter implementations in consuming packages
|
|
2242
|
+
- Standardizes translation engine integration across the monorepo
|
|
2243
|
+
- Simplifies error class constructors that require translation engines
|
|
2244
|
+
|
|
2245
|
+
#### Migration
|
|
2246
|
+
Packages using custom translation adapters can now replace them with:
|
|
2247
|
+
```typescript
|
|
2248
|
+
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
2249
|
+
const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
2250
|
+
|
|
1062
2251
|
### Version 1.2.4
|
|
1063
2252
|
|
|
1064
2253
|
- Sat Oct 25 2025 14:29:00 GMT-0700 (Pacific Daylight Time)
|