@digitaldefiance/i18n-lib 4.2.20 → 4.3.1
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 +521 -43
- package/package.json +1 -1
- package/src/builders/i18n-builder.d.ts +13 -0
- package/src/builders/i18n-builder.d.ts.map +1 -1
- package/src/builders/i18n-builder.js +24 -1
- package/src/builders/i18n-builder.js.map +1 -1
- package/src/core/i18n-engine.d.ts +167 -0
- package/src/core/i18n-engine.d.ts.map +1 -1
- package/src/core/i18n-engine.js +185 -0
- package/src/core/i18n-engine.js.map +1 -1
- package/src/core/index.d.ts +2 -0
- package/src/core/index.d.ts.map +1 -1
- package/src/core/index.js +3 -1
- package/src/core/index.js.map +1 -1
- package/src/core/string-key-enum-registry.d.ts +238 -0
- package/src/core/string-key-enum-registry.d.ts.map +1 -0
- package/src/core/string-key-enum-registry.js +289 -0
- package/src/core/string-key-enum-registry.js.map +1 -0
- package/src/errors/i18n-error.d.ts +15 -0
- package/src/errors/i18n-error.d.ts.map +1 -1
- package/src/errors/i18n-error.js +19 -0
- package/src/errors/i18n-error.js.map +1 -1
- package/src/interfaces/i18n-engine.interface.d.ts +28 -0
- package/src/interfaces/i18n-engine.interface.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,35 @@ A production-ready TypeScript internationalization library with component-based
|
|
|
4
4
|
|
|
5
5
|
Part of [Express Suite](https://github.com/Digital-Defiance/express-suite)
|
|
6
6
|
|
|
7
|
+
## What's New in v4.3.0
|
|
8
|
+
|
|
9
|
+
✨ **String Key Enum Registration** - Register branded string key enums for direct translation without specifying component IDs.
|
|
10
|
+
|
|
11
|
+
**Prerequisites**: Requires branded enums from v4.0.4+ (see [Branded Enums](#branded-enums) section)
|
|
12
|
+
|
|
13
|
+
**New Features:**
|
|
14
|
+
- **`registerStringKeyEnum()`**: Register branded enums for automatic component routing
|
|
15
|
+
- **`translateStringKey()`**: Translate keys directly - component ID resolved from branded enum
|
|
16
|
+
- **`safeTranslateStringKey()`**: Safe version returning placeholder on failure
|
|
17
|
+
- **`hasStringKeyEnum()` / `getStringKeyEnums()`**: Query registered enums
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createI18nStringKeysFromEnum, PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
21
|
+
|
|
22
|
+
// First, create a branded enum from your string keys
|
|
23
|
+
enum MyStringKeys {
|
|
24
|
+
Welcome = 'welcome',
|
|
25
|
+
Goodbye = 'goodbye',
|
|
26
|
+
}
|
|
27
|
+
const MyBrandedKeys = createI18nStringKeysFromEnum('my-component', MyStringKeys);
|
|
28
|
+
|
|
29
|
+
// Register your branded enum
|
|
30
|
+
engine.registerStringKeyEnum(MyBrandedKeys);
|
|
31
|
+
|
|
32
|
+
// Translate without component ID - it's resolved automatically
|
|
33
|
+
const text = engine.translateStringKey(MyBrandedKeys.Welcome, { name: 'Alice' });
|
|
34
|
+
```
|
|
35
|
+
|
|
7
36
|
## Features
|
|
8
37
|
|
|
9
38
|
- **Production-Grade Security**: Comprehensive protection against common attacks
|
|
@@ -33,8 +62,8 @@ Part of [Express Suite](https://github.com/Digital-Defiance/express-suite)
|
|
|
33
62
|
- **Type Safety**: Full TypeScript support with generic types
|
|
34
63
|
- **Branded Enums**: Runtime-identifiable string keys with collision detection and component routing
|
|
35
64
|
- **Error Handling**: Comprehensive error classes with translation support and ICU formatting
|
|
36
|
-
- **93.22% Test Coverage**: 1,
|
|
37
|
-
- **Security Hardened**:
|
|
65
|
+
- **93.22% Test Coverage**: 1,779 tests covering all features
|
|
66
|
+
- **Security Hardened**: Comprehensive protection against prototype pollution, ReDoS, and XSS attacks
|
|
38
67
|
|
|
39
68
|
## Installation
|
|
40
69
|
|
|
@@ -84,8 +113,12 @@ console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
|
|
|
84
113
|
// Output: "Bienvenue sur MyApp!"
|
|
85
114
|
|
|
86
115
|
// Pluralization (automatic form selection)
|
|
87
|
-
engine.
|
|
88
|
-
|
|
116
|
+
engine.registerComponent({
|
|
117
|
+
component: {
|
|
118
|
+
id: 'cart',
|
|
119
|
+
name: 'Cart',
|
|
120
|
+
stringKeys: ['items']
|
|
121
|
+
},
|
|
89
122
|
strings: {
|
|
90
123
|
'en-US': {
|
|
91
124
|
items: {
|
|
@@ -104,7 +137,18 @@ console.log(engine.translate('cart', 'items', { count: 5 }));
|
|
|
104
137
|
|
|
105
138
|
## ICU MessageFormat
|
|
106
139
|
|
|
107
|
-
Industry-standard message formatting with powerful features. See [
|
|
140
|
+
Industry-standard message formatting with powerful features. See [docs/ICU_MESSAGEFORMAT.md](docs/ICU_MESSAGEFORMAT.md) for complete guide.
|
|
141
|
+
|
|
142
|
+
**When to use ICU MessageFormat:**
|
|
143
|
+
- Complex pluralization with multiple forms
|
|
144
|
+
- Gender-specific translations
|
|
145
|
+
- Number/date/time formatting with locale awareness
|
|
146
|
+
- Nested conditional logic (select within plural)
|
|
147
|
+
|
|
148
|
+
**When to use simple templates:**
|
|
149
|
+
- Basic variable substitution
|
|
150
|
+
- Component references ({{Component.key}})
|
|
151
|
+
- Simple string interpolation
|
|
108
152
|
|
|
109
153
|
### Quick Example
|
|
110
154
|
|
|
@@ -146,9 +190,9 @@ formatICUMessage(
|
|
|
146
190
|
|
|
147
191
|
### Documentation
|
|
148
192
|
|
|
149
|
-
- **[
|
|
150
|
-
- **[
|
|
151
|
-
- **[
|
|
193
|
+
- **[docs/ICU_MESSAGEFORMAT.md](docs/ICU_MESSAGEFORMAT.md)** - Complete guide with syntax reference and examples
|
|
194
|
+
- **[docs/ICU_COMPREHENSIVE_VALIDATION.md](docs/ICU_COMPREHENSIVE_VALIDATION.md)** - Validation report with test coverage
|
|
195
|
+
- **[docs/ICU_PROJECT_COMPLETE.md](docs/ICU_PROJECT_COMPLETE.md)** - Implementation summary
|
|
152
196
|
|
|
153
197
|
### API
|
|
154
198
|
|
|
@@ -172,11 +216,19 @@ import {
|
|
|
172
216
|
Automatic plural form selection based on count with CLDR-compliant rules for 37 languages:
|
|
173
217
|
|
|
174
218
|
```typescript
|
|
175
|
-
import { createPluralString } from '@digitaldefiance/i18n-lib';
|
|
219
|
+
import { createPluralString, PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
220
|
+
|
|
221
|
+
const engine = PluginI18nEngine.createInstance('app', [
|
|
222
|
+
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true }
|
|
223
|
+
]);
|
|
176
224
|
|
|
177
225
|
// English (one/other)
|
|
178
|
-
engine.
|
|
179
|
-
|
|
226
|
+
engine.registerComponent({
|
|
227
|
+
component: {
|
|
228
|
+
id: 'shop',
|
|
229
|
+
name: 'Shop',
|
|
230
|
+
stringKeys: ['items']
|
|
231
|
+
},
|
|
180
232
|
strings: {
|
|
181
233
|
'en-US': {
|
|
182
234
|
items: createPluralString({
|
|
@@ -188,8 +240,12 @@ engine.register({
|
|
|
188
240
|
});
|
|
189
241
|
|
|
190
242
|
// Russian (one/few/many)
|
|
191
|
-
engine.
|
|
192
|
-
|
|
243
|
+
engine.registerComponent({
|
|
244
|
+
component: {
|
|
245
|
+
id: 'shop',
|
|
246
|
+
name: 'Shop',
|
|
247
|
+
stringKeys: ['items']
|
|
248
|
+
},
|
|
193
249
|
strings: {
|
|
194
250
|
'ru': {
|
|
195
251
|
items: createPluralString({
|
|
@@ -202,8 +258,12 @@ engine.register({
|
|
|
202
258
|
});
|
|
203
259
|
|
|
204
260
|
// Arabic (zero/one/two/few/many/other)
|
|
205
|
-
engine.
|
|
206
|
-
|
|
261
|
+
engine.registerComponent({
|
|
262
|
+
component: {
|
|
263
|
+
id: 'shop',
|
|
264
|
+
name: 'Shop',
|
|
265
|
+
stringKeys: ['items']
|
|
266
|
+
},
|
|
207
267
|
strings: {
|
|
208
268
|
'ar': {
|
|
209
269
|
items: createPluralString({
|
|
@@ -240,10 +300,18 @@ See [PLURALIZATION_SUPPORT.md](docs/PLURALIZATION_SUPPORT.md) for complete langu
|
|
|
240
300
|
Gender-aware translations with intelligent fallback:
|
|
241
301
|
|
|
242
302
|
```typescript
|
|
243
|
-
import { createGenderedString } from '@digitaldefiance/i18n-lib';
|
|
303
|
+
import { createGenderedString, PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
244
304
|
|
|
245
|
-
engine.
|
|
246
|
-
id: '
|
|
305
|
+
const engine = PluginI18nEngine.createInstance('app', [
|
|
306
|
+
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true }
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
engine.registerComponent({
|
|
310
|
+
component: {
|
|
311
|
+
id: 'profile',
|
|
312
|
+
name: 'Profile',
|
|
313
|
+
stringKeys: ['greeting']
|
|
314
|
+
},
|
|
247
315
|
strings: {
|
|
248
316
|
'en-US': {
|
|
249
317
|
greeting: createGenderedString({
|
|
@@ -346,10 +414,10 @@ The main engine class that manages translations, languages, and components.
|
|
|
346
414
|
```typescript
|
|
347
415
|
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
348
416
|
|
|
349
|
-
// Create instance
|
|
417
|
+
// Recommended: Create named instance (supports multiple engines)
|
|
350
418
|
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
351
419
|
|
|
352
|
-
//
|
|
420
|
+
// Alternative: Direct constructor (for single engine use cases)
|
|
353
421
|
const engine = new PluginI18nEngine(languages, config);
|
|
354
422
|
```
|
|
355
423
|
|
|
@@ -464,10 +532,45 @@ const engine = I18nBuilder.create()
|
|
|
464
532
|
.build();
|
|
465
533
|
```
|
|
466
534
|
|
|
535
|
+
#### Builder with String Key Enum Registration
|
|
536
|
+
|
|
537
|
+
Register branded string key enums during engine construction for direct translation via `translateStringKey()`:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import { I18nBuilder, createI18nStringKeysFromEnum } from '@digitaldefiance/i18n-lib';
|
|
541
|
+
|
|
542
|
+
// Create branded enum from your string keys
|
|
543
|
+
enum MyStringKeys {
|
|
544
|
+
Welcome = 'welcome',
|
|
545
|
+
Goodbye = 'goodbye',
|
|
546
|
+
}
|
|
547
|
+
const BrandedKeys = createI18nStringKeysFromEnum('my-component', MyStringKeys);
|
|
548
|
+
|
|
549
|
+
const engine = I18nBuilder.create()
|
|
550
|
+
.withLanguages([
|
|
551
|
+
{ id: 'en-US', name: 'English', code: 'en-US', isDefault: true },
|
|
552
|
+
])
|
|
553
|
+
.withStringKeyEnum(BrandedKeys) // Register single enum
|
|
554
|
+
// Or register multiple at once:
|
|
555
|
+
// .withStringKeyEnums([BrandedKeys, OtherKeys])
|
|
556
|
+
.build();
|
|
557
|
+
|
|
558
|
+
// Now you can translate directly without component ID
|
|
559
|
+
engine.translateStringKey(BrandedKeys.Welcome);
|
|
560
|
+
```
|
|
561
|
+
|
|
467
562
|
### Context Integration
|
|
468
563
|
|
|
564
|
+
Automatic injection of currency, timezone, and language from GlobalActiveContext:
|
|
565
|
+
|
|
469
566
|
```typescript
|
|
470
|
-
import {
|
|
567
|
+
import {
|
|
568
|
+
GlobalActiveContext,
|
|
569
|
+
CurrencyCode,
|
|
570
|
+
Timezone,
|
|
571
|
+
PluginI18nEngine,
|
|
572
|
+
LanguageCodes
|
|
573
|
+
} from '@digitaldefiance/i18n-lib';
|
|
471
574
|
|
|
472
575
|
// Set context variables
|
|
473
576
|
const context = GlobalActiveContext.getInstance();
|
|
@@ -486,16 +589,23 @@ engine.t('Price in {currency}', { currency: 'USD' }); // "Price in USD"
|
|
|
486
589
|
|
|
487
590
|
### Constants Management
|
|
488
591
|
|
|
592
|
+
Manage application-wide constants for use in translations:
|
|
593
|
+
|
|
489
594
|
```typescript
|
|
490
|
-
// Merge constants (adds/
|
|
595
|
+
// Merge constants (adds/updates specific keys, preserves others)
|
|
491
596
|
engine.mergeConstants({ Version: '2.0', NewKey: 'value' });
|
|
492
597
|
// Existing constants preserved, specified ones added/updated
|
|
493
598
|
|
|
494
|
-
// Update all constants (replaces
|
|
599
|
+
// Update all constants (replaces entire constants object)
|
|
495
600
|
engine.updateConstants({ Site: 'NewSite', Version: '2.0' });
|
|
496
601
|
// All previous constants removed, only these remain
|
|
497
602
|
```
|
|
498
603
|
|
|
604
|
+
**When to use:**
|
|
605
|
+
- **Constants**: Application-wide values that rarely change (AppName, Version)
|
|
606
|
+
- **Variables**: Request-specific or dynamic values passed to translate()
|
|
607
|
+
- **Context**: User-specific values (currency, timezone, language)
|
|
608
|
+
|
|
499
609
|
### Language Management
|
|
500
610
|
|
|
501
611
|
```typescript
|
|
@@ -505,9 +615,11 @@ engine.setLanguage(LanguageCodes.FR);
|
|
|
505
615
|
// Get current language
|
|
506
616
|
const lang = engine.getCurrentLanguage();
|
|
507
617
|
|
|
508
|
-
// Check if language exists
|
|
618
|
+
// Check if language exists (recommended before setLanguage)
|
|
509
619
|
if (engine.hasLanguage(LanguageCodes.ES)) {
|
|
510
620
|
engine.setLanguage(LanguageCodes.ES);
|
|
621
|
+
} else {
|
|
622
|
+
console.warn('Spanish not available, using default');
|
|
511
623
|
}
|
|
512
624
|
|
|
513
625
|
// Get all languages
|
|
@@ -516,22 +628,29 @@ const languages = engine.getLanguages();
|
|
|
516
628
|
|
|
517
629
|
### Admin Context
|
|
518
630
|
|
|
519
|
-
Separate language for admin interfaces:
|
|
631
|
+
Separate language for admin interfaces (useful for multi-tenant applications where admins need consistent UI language regardless of user's language):
|
|
520
632
|
|
|
521
633
|
```typescript
|
|
522
|
-
// Set admin language
|
|
634
|
+
// Set admin language (e.g., always English for admin panel)
|
|
523
635
|
engine.setAdminLanguage(LanguageCodes.EN_US);
|
|
524
636
|
|
|
525
|
-
// Switch to admin context
|
|
637
|
+
// Switch to admin context (uses admin language)
|
|
526
638
|
engine.switchToAdmin();
|
|
639
|
+
const adminText = engine.translate('app', 'dashboard'); // Uses EN_US
|
|
527
640
|
|
|
528
|
-
// Switch back to user context
|
|
641
|
+
// Switch back to user context (uses user's language)
|
|
529
642
|
engine.switchToUser();
|
|
643
|
+
const userText = engine.translate('app', 'dashboard'); // Uses user's language
|
|
530
644
|
```
|
|
531
645
|
|
|
646
|
+
**Use cases:**
|
|
647
|
+
- Admin panels in multi-language applications
|
|
648
|
+
- Support interfaces that need consistent language
|
|
649
|
+
- Internal tools accessed by multilingual teams
|
|
650
|
+
|
|
532
651
|
## Core System Strings
|
|
533
652
|
|
|
534
|
-
Pre-built translations for common UI elements:
|
|
653
|
+
Pre-built translations for common UI elements in 8 languages:
|
|
535
654
|
|
|
536
655
|
```typescript
|
|
537
656
|
import { getCoreI18nEngine, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
|
|
@@ -543,21 +662,23 @@ const yes = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Common_Yes);
|
|
|
543
662
|
const error = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Error_NotFound);
|
|
544
663
|
```
|
|
545
664
|
|
|
546
|
-
Available core string categories
|
|
665
|
+
**Available core string categories:**
|
|
547
666
|
|
|
548
|
-
- **Common
|
|
549
|
-
- **Errors
|
|
550
|
-
- **System
|
|
667
|
+
- **Common** (30+ strings): Yes, No, Cancel, OK, Save, Delete, Edit, Create, Update, Loading, Search, Filter, Sort, Export, Import, Settings, Help, About, Contact, Terms, Privacy, Logout, Profile, Dashboard, Home, Back, Next, Previous, Submit, Reset
|
|
668
|
+
- **Errors** (25+ strings): InvalidInput, NetworkError, NotFound, AccessDenied, ValidationFailed, Unauthorized, Forbidden, ServerError, Timeout, BadRequest, Conflict, Gone, TooManyRequests, ServiceUnavailable
|
|
669
|
+
- **System** (20+ strings): Welcome, Goodbye, PleaseWait, ProcessingRequest, OperationComplete, OperationFailed, Success, Warning, Info, Confirm, AreYouSure, UnsavedChanges, SessionExpired, MaintenanceMode
|
|
670
|
+
|
|
671
|
+
See `CoreStringKey` enum for complete list of available strings.
|
|
551
672
|
|
|
552
673
|
## Multiple Instances
|
|
553
674
|
|
|
554
|
-
Create isolated engines for different parts of your application:
|
|
675
|
+
Create isolated engines for different parts of your application (useful for micro-frontends, plugins, or multi-tenant systems):
|
|
555
676
|
|
|
556
677
|
```typescript
|
|
557
|
-
// Admin engine
|
|
678
|
+
// Admin engine with admin-specific languages
|
|
558
679
|
const adminEngine = PluginI18nEngine.createInstance('admin', adminLanguages);
|
|
559
680
|
|
|
560
|
-
// User engine
|
|
681
|
+
// User engine with user-facing languages
|
|
561
682
|
const userEngine = PluginI18nEngine.createInstance('user', userLanguages);
|
|
562
683
|
|
|
563
684
|
// Get instance by key
|
|
@@ -568,13 +689,19 @@ if (PluginI18nEngine.hasInstance('admin')) {
|
|
|
568
689
|
// ...
|
|
569
690
|
}
|
|
570
691
|
|
|
571
|
-
// Remove instance
|
|
692
|
+
// Remove instance (cleanup)
|
|
572
693
|
PluginI18nEngine.removeInstance('admin');
|
|
573
694
|
|
|
574
|
-
// Reset all instances
|
|
575
|
-
PluginI18nEngine.resetAll();
|
|
695
|
+
// Reset all instances (useful in tests)
|
|
696
|
+
PluginI18nEngine.resetAll(); // ⚠️ Removes ALL instances globally
|
|
576
697
|
```
|
|
577
698
|
|
|
699
|
+
**Use cases:**
|
|
700
|
+
- Micro-frontends with independent i18n
|
|
701
|
+
- Plugin systems with isolated translations
|
|
702
|
+
- Multi-tenant applications with tenant-specific languages
|
|
703
|
+
- Testing (create/destroy engines per test)
|
|
704
|
+
|
|
578
705
|
## Error Handling
|
|
579
706
|
|
|
580
707
|
### RegistryError
|
|
@@ -618,7 +745,7 @@ throw new MyError(LanguageCodes.FR); // Throws with French error message
|
|
|
618
745
|
|
|
619
746
|
## Translation Adapter
|
|
620
747
|
|
|
621
|
-
Adapt PluginI18nEngine to simpler TranslationEngine interface:
|
|
748
|
+
Adapt PluginI18nEngine to simpler TranslationEngine interface (useful when integrating with error classes or other components expecting a simplified translation interface):
|
|
622
749
|
|
|
623
750
|
```typescript
|
|
624
751
|
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
@@ -626,6 +753,7 @@ import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
|
626
753
|
const adapter = createTranslationAdapter(engine, 'componentId');
|
|
627
754
|
|
|
628
755
|
// Use adapter where TranslationEngine is expected
|
|
756
|
+
// (e.g., error classes, third-party libraries)
|
|
629
757
|
const message = adapter.translate('key', { var: 'value' });
|
|
630
758
|
```
|
|
631
759
|
|
|
@@ -646,6 +774,23 @@ LanguageCodes.JA // 'ja'
|
|
|
646
774
|
LanguageCodes.UK // 'uk'
|
|
647
775
|
```
|
|
648
776
|
|
|
777
|
+
**Adding custom language codes:**
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
// Define your custom language type
|
|
781
|
+
type MyLanguages = CoreLanguageCode | 'pt-BR' | 'it' | 'nl';
|
|
782
|
+
|
|
783
|
+
// Create engine with custom languages
|
|
784
|
+
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', [
|
|
785
|
+
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true },
|
|
786
|
+
{ id: 'pt-BR', name: 'Portuguese (Brazil)', code: 'pt-BR' },
|
|
787
|
+
{ id: 'it', name: 'Italian', code: 'it' },
|
|
788
|
+
{ id: 'nl', name: 'Dutch', code: 'nl' },
|
|
789
|
+
]);
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**Note**: The 8 built-in codes have pre-translated core strings. Custom languages require you to provide all translations.
|
|
793
|
+
|
|
649
794
|
## API Reference
|
|
650
795
|
|
|
651
796
|
### PluginI18nEngine
|
|
@@ -674,6 +819,11 @@ LanguageCodes.UK // 'uk'
|
|
|
674
819
|
- `validate()` - Validate all components
|
|
675
820
|
- `registerBrandedComponent(registration)` - Register component with branded string keys
|
|
676
821
|
- `getCollisionReport()` - Get map of key collisions across components
|
|
822
|
+
- `registerStringKeyEnum(enum)` - Register a branded string key enum for direct translation
|
|
823
|
+
- `translateStringKey(key, variables?, language?)` - Translate a branded string key directly
|
|
824
|
+
- `safeTranslateStringKey(key, variables?, language?)` - Safe version returning placeholder on failure
|
|
825
|
+
- `hasStringKeyEnum(enum)` - Check if a branded enum is registered
|
|
826
|
+
- `getStringKeyEnums()` - Get all registered branded enums
|
|
677
827
|
|
|
678
828
|
### Branded Enum Functions
|
|
679
829
|
|
|
@@ -924,6 +1074,77 @@ const allValues = getStringKeyValues(AllKeys);
|
|
|
924
1074
|
|
|
925
1075
|
For complete migration guide, see [BRANDED_ENUM_MIGRATION.md](docs/BRANDED_ENUM_MIGRATION.md).
|
|
926
1076
|
|
|
1077
|
+
### String Key Enum Registration
|
|
1078
|
+
|
|
1079
|
+
Register branded string key enums with the engine for direct translation without specifying component IDs. This simplifies translation calls and enables automatic component routing.
|
|
1080
|
+
|
|
1081
|
+
#### Registering String Key Enums
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
import { createI18nStringKeysFromEnum, PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1085
|
+
|
|
1086
|
+
// Create a branded enum from your string keys
|
|
1087
|
+
enum MyStringKeys {
|
|
1088
|
+
Welcome = 'welcome',
|
|
1089
|
+
Goodbye = 'goodbye',
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const BrandedKeys = createI18nStringKeysFromEnum('my-component', MyStringKeys);
|
|
1093
|
+
|
|
1094
|
+
// Create engine and register component
|
|
1095
|
+
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
1096
|
+
engine.registerBrandedComponent({
|
|
1097
|
+
component: {
|
|
1098
|
+
id: 'my-component',
|
|
1099
|
+
name: 'My Component',
|
|
1100
|
+
brandedStringKeys: BrandedKeys,
|
|
1101
|
+
},
|
|
1102
|
+
strings: {
|
|
1103
|
+
[LanguageCodes.EN_US]: {
|
|
1104
|
+
[BrandedKeys.Welcome]: 'Welcome!',
|
|
1105
|
+
[BrandedKeys.Goodbye]: 'Goodbye!',
|
|
1106
|
+
},
|
|
1107
|
+
},
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// Register the enum for direct translation
|
|
1111
|
+
engine.registerStringKeyEnum(BrandedKeys);
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
#### Direct Translation with translateStringKey
|
|
1115
|
+
|
|
1116
|
+
Once registered, translate keys directly without specifying the component ID:
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
// Before: Required component ID
|
|
1120
|
+
const text = engine.translate('my-component', BrandedKeys.Welcome, { name: 'Alice' });
|
|
1121
|
+
|
|
1122
|
+
// After: Component ID resolved automatically from branded enum
|
|
1123
|
+
const text = engine.translateStringKey(BrandedKeys.Welcome, { name: 'Alice' });
|
|
1124
|
+
|
|
1125
|
+
// Safe version returns placeholder on failure instead of throwing
|
|
1126
|
+
const safeText = engine.safeTranslateStringKey(BrandedKeys.Welcome, { name: 'Alice' });
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
#### Checking Registration Status
|
|
1130
|
+
|
|
1131
|
+
```typescript
|
|
1132
|
+
// Check if an enum is registered
|
|
1133
|
+
if (engine.hasStringKeyEnum(BrandedKeys)) {
|
|
1134
|
+
console.log('BrandedKeys is registered');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Get all registered enums
|
|
1138
|
+
const registeredEnums = engine.getStringKeyEnums();
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
#### Benefits
|
|
1142
|
+
|
|
1143
|
+
- **Cleaner Code**: No need to repeat component IDs in every translation call
|
|
1144
|
+
- **Automatic Routing**: Component ID resolved from branded enum metadata
|
|
1145
|
+
- **Type Safety**: Full TypeScript support with branded enum types
|
|
1146
|
+
- **Idempotent**: Safe to call `registerStringKeyEnum()` multiple times
|
|
1147
|
+
|
|
927
1148
|
### Branded Enum Translation
|
|
928
1149
|
|
|
929
1150
|
The enum translation system supports branded enums from `@digitaldefiance/branded-enum`, enabling automatic name inference and type-safe enum value translations.
|
|
@@ -1096,11 +1317,167 @@ function translateAnyValue<T extends string | number>(
|
|
|
1096
1317
|
|
|
1097
1318
|
## Browser Support
|
|
1098
1319
|
|
|
1099
|
-
- Chrome/Edge: Latest 2 versions
|
|
1100
|
-
- Firefox: Latest 2 versions
|
|
1101
|
-
- Safari: Latest 2 versions
|
|
1320
|
+
- Chrome/Edge: Latest 2 versions (minimum: Chrome 90, Edge 90)
|
|
1321
|
+
- Firefox: Latest 2 versions (minimum: Firefox 88)
|
|
1322
|
+
- Safari: Latest 2 versions (minimum: Safari 14)
|
|
1102
1323
|
- Node.js: 18+
|
|
1103
1324
|
|
|
1325
|
+
**Polyfills**: Not required for supported versions. For older browsers, you may need:
|
|
1326
|
+
- `Intl.PluralRules` polyfill
|
|
1327
|
+
- `Intl.NumberFormat` polyfill
|
|
1328
|
+
|
|
1329
|
+
## Troubleshooting
|
|
1330
|
+
|
|
1331
|
+
### Component Not Found Error
|
|
1332
|
+
|
|
1333
|
+
**Problem**: `RegistryError: Component 'xyz' not found`
|
|
1334
|
+
|
|
1335
|
+
**Solutions**:
|
|
1336
|
+
1. Ensure component is registered before use: `engine.registerComponent({...})`
|
|
1337
|
+
2. Check component ID spelling matches exactly
|
|
1338
|
+
3. Verify registration completed successfully (no errors thrown)
|
|
1339
|
+
|
|
1340
|
+
### Translation Returns `[componentId.key]`
|
|
1341
|
+
|
|
1342
|
+
**Problem**: Translation returns placeholder instead of translated text
|
|
1343
|
+
|
|
1344
|
+
**Solutions**:
|
|
1345
|
+
1. Check string key exists in component registration
|
|
1346
|
+
2. Verify current language has translation for this key
|
|
1347
|
+
3. Use `engine.validate()` to find missing translations
|
|
1348
|
+
4. Check if using `safeTranslate()` (returns placeholder on error)
|
|
1349
|
+
|
|
1350
|
+
### Plural Forms Not Working
|
|
1351
|
+
|
|
1352
|
+
**Problem**: Always shows same plural form regardless of count
|
|
1353
|
+
|
|
1354
|
+
**Solutions**:
|
|
1355
|
+
1. Ensure passing `count` variable: `engine.translate('id', 'key', { count: 5 })`
|
|
1356
|
+
2. Use `createPluralString()` helper to create plural object
|
|
1357
|
+
3. Verify language has correct plural rules (see PLURALIZATION_SUPPORT.md)
|
|
1358
|
+
4. Check required plural forms for language: `getRequiredPluralForms('ru')`
|
|
1359
|
+
|
|
1360
|
+
### ICU MessageFormat Not Parsing
|
|
1361
|
+
|
|
1362
|
+
**Problem**: ICU syntax appears as literal text
|
|
1363
|
+
|
|
1364
|
+
**Solutions**:
|
|
1365
|
+
1. Use `formatICUMessage()` function, not `translate()`
|
|
1366
|
+
2. Check ICU syntax is valid: `validateICUMessage(message)`
|
|
1367
|
+
3. Ensure variables are provided: `formatICUMessage(msg, { count: 1 })`
|
|
1368
|
+
4. See [docs/ICU_MESSAGEFORMAT.md](docs/ICU_MESSAGEFORMAT.md) for syntax
|
|
1369
|
+
|
|
1370
|
+
### Memory Leak with Multiple Instances
|
|
1371
|
+
|
|
1372
|
+
**Problem**: Memory usage grows over time
|
|
1373
|
+
|
|
1374
|
+
**Solutions**:
|
|
1375
|
+
1. Call `PluginI18nEngine.removeInstance(key)` when done
|
|
1376
|
+
2. Use `resetAll()` in test cleanup
|
|
1377
|
+
3. Reuse instances instead of creating new ones
|
|
1378
|
+
4. Check for circular references in custom code
|
|
1379
|
+
|
|
1380
|
+
### TypeScript Type Errors
|
|
1381
|
+
|
|
1382
|
+
**Problem**: Type errors with language codes or string keys
|
|
1383
|
+
|
|
1384
|
+
**Solutions**:
|
|
1385
|
+
1. Use generic types: `PluginI18nEngine.createInstance<MyLanguages>(...)`
|
|
1386
|
+
2. Ensure `as const` on string key objects
|
|
1387
|
+
3. Import types: `import type { ComponentRegistration } from '@digitaldefiance/i18n-lib'`
|
|
1388
|
+
4. Check TypeScript version (4.5+ recommended)
|
|
1389
|
+
|
|
1390
|
+
## FAQ
|
|
1391
|
+
|
|
1392
|
+
### When should I use ICU MessageFormat vs simple templates?
|
|
1393
|
+
|
|
1394
|
+
**Use ICU MessageFormat when:**
|
|
1395
|
+
- You need complex pluralization (multiple forms)
|
|
1396
|
+
- You need gender-specific translations
|
|
1397
|
+
- You need number/date/time formatting
|
|
1398
|
+
- You need nested conditional logic
|
|
1399
|
+
|
|
1400
|
+
**Use simple templates when:**
|
|
1401
|
+
- You only need variable substitution
|
|
1402
|
+
- You're referencing other components ({{Component.key}})
|
|
1403
|
+
- You want simpler, more readable strings
|
|
1404
|
+
|
|
1405
|
+
### How do I handle missing translations?
|
|
1406
|
+
|
|
1407
|
+
**Options:**
|
|
1408
|
+
1. Use `safeTranslate()` - returns `[componentId.key]` placeholder
|
|
1409
|
+
2. Set fallback language in config
|
|
1410
|
+
3. Use `engine.validate()` to find missing translations during development
|
|
1411
|
+
4. Implement custom error handling with try/catch
|
|
1412
|
+
|
|
1413
|
+
### Can I use this library without TypeScript?
|
|
1414
|
+
|
|
1415
|
+
**Yes**, but you lose type safety benefits:
|
|
1416
|
+
```javascript
|
|
1417
|
+
const { PluginI18nEngine, LanguageCodes } = require('@digitaldefiance/i18n-lib');
|
|
1418
|
+
const engine = PluginI18nEngine.createInstance('app', languages);
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
### How do I add a new language?
|
|
1422
|
+
|
|
1423
|
+
**Steps:**
|
|
1424
|
+
1. Add language definition to engine creation
|
|
1425
|
+
2. Register components with translations for new language
|
|
1426
|
+
3. If using plurals, check required forms: `getRequiredPluralForms('pt-BR')`
|
|
1427
|
+
4. See [docs/ADDING_LANGUAGES.md](docs/ADDING_LANGUAGES.md) for details
|
|
1428
|
+
|
|
1429
|
+
### What's the difference between branded enums and regular enums?
|
|
1430
|
+
|
|
1431
|
+
**Branded enums** (v4.0.4+):
|
|
1432
|
+
- Runtime identification of component ownership
|
|
1433
|
+
- Collision detection between components
|
|
1434
|
+
- Automatic component routing
|
|
1435
|
+
- Created with `createI18nStringKeys()`
|
|
1436
|
+
|
|
1437
|
+
**Regular enums**:
|
|
1438
|
+
- Compile-time only (erased at runtime)
|
|
1439
|
+
- No collision detection
|
|
1440
|
+
- Must specify component ID in every translate call
|
|
1441
|
+
|
|
1442
|
+
### How do I migrate from v1.x to v4.x?
|
|
1443
|
+
|
|
1444
|
+
**Key changes:**
|
|
1445
|
+
1. `CoreLanguage` enum → `LanguageCodes` constants
|
|
1446
|
+
2. Language IDs now use BCP 47 codes ('en-US' not 'English (US)')
|
|
1447
|
+
3. `register()` → `registerComponent()` (more explicit)
|
|
1448
|
+
4. See [docs/MIGRATION_GUIDE.md](docs/MIGRATION_GUIDE.md) for complete guide
|
|
1449
|
+
|
|
1450
|
+
### Can I use multiple engines in the same application?
|
|
1451
|
+
|
|
1452
|
+
**Yes**, use named instances:
|
|
1453
|
+
```typescript
|
|
1454
|
+
const adminEngine = PluginI18nEngine.createInstance('admin', adminLangs);
|
|
1455
|
+
const userEngine = PluginI18nEngine.createInstance('user', userLangs);
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
See [Multiple Instances](#multiple-instances) section for details.
|
|
1459
|
+
|
|
1460
|
+
### How do I test components that use i18n?
|
|
1461
|
+
|
|
1462
|
+
**Pattern:**
|
|
1463
|
+
```typescript
|
|
1464
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1465
|
+
|
|
1466
|
+
describe('MyComponent', () => {
|
|
1467
|
+
beforeEach(() => {
|
|
1468
|
+
PluginI18nEngine.resetAll();
|
|
1469
|
+
const engine = PluginI18nEngine.createInstance('test', languages);
|
|
1470
|
+
engine.registerComponent(/* ... */);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
afterEach(() => {
|
|
1474
|
+
PluginI18nEngine.resetAll();
|
|
1475
|
+
});
|
|
1476
|
+
});
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
See [Testing](#testing) section for more patterns.
|
|
1480
|
+
|
|
1104
1481
|
## License
|
|
1105
1482
|
|
|
1106
1483
|
MIT License - See LICENSE file for details
|
|
@@ -1123,6 +1500,107 @@ Contributions welcome! Please:
|
|
|
1123
1500
|
|
|
1124
1501
|
## ChangeLog
|
|
1125
1502
|
|
|
1503
|
+
### Version 4.3.0
|
|
1504
|
+
|
|
1505
|
+
**String Key Enum Registration for Direct Translation**
|
|
1506
|
+
|
|
1507
|
+
This release adds the ability to register branded string key enums with the I18nEngine for direct translation via `translateStringKey()`. This enables automatic component ID resolution from branded enum values.
|
|
1508
|
+
|
|
1509
|
+
**New Features:**
|
|
1510
|
+
|
|
1511
|
+
- **`registerStringKeyEnum()`**: Register a branded string key enum for direct translation support
|
|
1512
|
+
```typescript
|
|
1513
|
+
import { createI18nStringKeysFromEnum } from '@digitaldefiance/i18n-lib';
|
|
1514
|
+
|
|
1515
|
+
const MyKeys = createI18nStringKeysFromEnum('my-component', MyStringKeyEnum);
|
|
1516
|
+
engine.registerStringKeyEnum(MyKeys);
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
- **`translateStringKey()`**: Translate a branded string key value directly without specifying component ID
|
|
1520
|
+
```typescript
|
|
1521
|
+
// Before: engine.translate('my-component', MyKeys.Welcome, { name: 'Alice' });
|
|
1522
|
+
// After:
|
|
1523
|
+
engine.translateStringKey(MyKeys.Welcome, { name: 'Alice' });
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
- **`safeTranslateStringKey()`**: Safe version that returns a placeholder on failure instead of throwing
|
|
1527
|
+
|
|
1528
|
+
- **`hasStringKeyEnum()`**: Check if a branded enum is registered
|
|
1529
|
+
|
|
1530
|
+
- **`getStringKeyEnums()`**: Get all registered branded enums
|
|
1531
|
+
|
|
1532
|
+
**New Error Codes:**
|
|
1533
|
+
|
|
1534
|
+
- `INVALID_STRING_KEY_ENUM` - When a non-branded enum is passed to `registerStringKeyEnum()`
|
|
1535
|
+
- `STRING_KEY_NOT_REGISTERED` - When translating a key from an unregistered enum
|
|
1536
|
+
|
|
1537
|
+
**Benefits:**
|
|
1538
|
+
|
|
1539
|
+
- Cleaner translation calls without repeating component IDs
|
|
1540
|
+
- Automatic component routing based on branded enum metadata
|
|
1541
|
+
- Type-safe translations with full TypeScript support
|
|
1542
|
+
- Idempotent registration (safe to call multiple times)
|
|
1543
|
+
|
|
1544
|
+
### Version 4.2.0
|
|
1545
|
+
|
|
1546
|
+
**Branded Enum Translation Support**
|
|
1547
|
+
|
|
1548
|
+
This release adds comprehensive support for translating branded enums from `@digitaldefiance/branded-enum`, enabling automatic name inference and type-safe enum value translations.
|
|
1549
|
+
|
|
1550
|
+
**New Features:**
|
|
1551
|
+
|
|
1552
|
+
- **Automatic Name Inference**: When registering a branded enum, the name is automatically extracted from the enum's component ID
|
|
1553
|
+
```typescript
|
|
1554
|
+
const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' });
|
|
1555
|
+
engine.registerEnum(Status, translations); // Name 'status' inferred automatically
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
- **New Utility Functions**:
|
|
1559
|
+
- `isBrandedEnum()` - Type guard to detect branded enums
|
|
1560
|
+
- `getBrandedEnumComponentId()` - Extract component ID from branded enum
|
|
1561
|
+
- `getBrandedEnumId()` - Get raw brand ID for debugging
|
|
1562
|
+
|
|
1563
|
+
- **Enhanced `registerEnum()`**: Now accepts branded enums and returns the registered translations object
|
|
1564
|
+
|
|
1565
|
+
- **Property-Based Tests**: Comprehensive property tests for branded enum translation ensuring:
|
|
1566
|
+
- Registration idempotence
|
|
1567
|
+
- Translation consistency across languages
|
|
1568
|
+
- Proper error handling for missing translations
|
|
1569
|
+
|
|
1570
|
+
**Documentation:**
|
|
1571
|
+
|
|
1572
|
+
- Added "Branded Enum Translation" section to README
|
|
1573
|
+
- Comprehensive JSDoc documentation for all new utilities
|
|
1574
|
+
|
|
1575
|
+
### Version 4.1.0
|
|
1576
|
+
|
|
1577
|
+
**Enhanced Type Documentation and BrandedMasterStringsCollection**
|
|
1578
|
+
|
|
1579
|
+
This release focuses on improved documentation and ergonomic type aliases for working with branded enums.
|
|
1580
|
+
|
|
1581
|
+
**New Features:**
|
|
1582
|
+
|
|
1583
|
+
- **`BrandedMasterStringsCollection<E, TLanguage>`**: Ergonomic type alias for defining translation collections with branded enums
|
|
1584
|
+
```typescript
|
|
1585
|
+
const translations: BrandedMasterStringsCollection<typeof MyKeys, CoreLanguageCode> = {
|
|
1586
|
+
[LanguageCodes.EN_US]: { 'my.welcome': 'Welcome!' },
|
|
1587
|
+
[LanguageCodes.FR]: { 'my.welcome': 'Bienvenue!' },
|
|
1588
|
+
};
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
- **`BrandedPluralMasterStringsCollection<E, TLanguage>`**: Type alias for plural-aware branded translations
|
|
1592
|
+
|
|
1593
|
+
- **Comprehensive Module Documentation**: Added extensive JSDoc documentation to `types.ts` explaining:
|
|
1594
|
+
- Traditional enums vs branded enums
|
|
1595
|
+
- Migration patterns from legacy enums
|
|
1596
|
+
- Complete examples for defining translations
|
|
1597
|
+
|
|
1598
|
+
**Documentation:**
|
|
1599
|
+
|
|
1600
|
+
- Step-by-step guide for creating branded string keys
|
|
1601
|
+
- Examples showing `BrandedMasterStringsCollection` usage
|
|
1602
|
+
- Migration guide from traditional `EnumLanguageTranslation` types
|
|
1603
|
+
|
|
1126
1604
|
### Version 4.0.4
|
|
1127
1605
|
|
|
1128
1606
|
**Branded Enums for Runtime String Key Identification**
|