@digitaldefiance/i18n-lib 1.3.27 → 2.0.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 +394 -2262
- package/package.json +4 -1
- package/src/active-context.d.ts +2 -2
- package/src/active-context.d.ts.map +1 -1
- package/src/builders/i18n-builder.d.ts +24 -0
- package/src/builders/i18n-builder.d.ts.map +1 -0
- package/src/builders/i18n-builder.js +62 -0
- package/src/builders/i18n-builder.js.map +1 -0
- package/src/builders/index.d.ts +5 -0
- package/src/builders/index.d.ts.map +1 -0
- package/src/builders/index.js +8 -0
- package/src/builders/index.js.map +1 -0
- package/src/core/component-store.d.ts +20 -0
- package/src/core/component-store.d.ts.map +1 -0
- package/src/core/component-store.js +105 -0
- package/src/core/component-store.js.map +1 -0
- package/src/core/context-manager.d.ts +20 -0
- package/src/core/context-manager.d.ts.map +1 -0
- package/src/core/context-manager.js +50 -0
- package/src/core/context-manager.js.map +1 -0
- package/src/core/enum-registry.d.ts +14 -0
- package/src/core/enum-registry.d.ts.map +1 -0
- package/src/core/enum-registry.js +48 -0
- package/src/core/enum-registry.js.map +1 -0
- package/src/core/i18n-engine.d.ts +44 -0
- package/src/core/i18n-engine.d.ts.map +1 -0
- package/src/core/i18n-engine.js +192 -0
- package/src/core/i18n-engine.js.map +1 -0
- package/src/core/index.d.ts +9 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.js +12 -0
- package/src/core/index.js.map +1 -0
- package/src/core/language-registry.d.ts +41 -0
- package/src/core/language-registry.d.ts.map +1 -0
- package/src/core/language-registry.js +157 -0
- package/src/core/language-registry.js.map +1 -0
- package/src/core-i18n.d.ts +4 -0
- package/src/core-i18n.d.ts.map +1 -1
- package/src/core-i18n.js +109 -4
- package/src/core-i18n.js.map +1 -1
- package/src/core-string-key.d.ts +10 -1
- package/src/core-string-key.d.ts.map +1 -1
- package/src/core-string-key.js +10 -0
- package/src/core-string-key.js.map +1 -1
- package/src/create-translation-adapter.d.ts +15 -3
- package/src/create-translation-adapter.d.ts.map +1 -1
- package/src/create-translation-adapter.js +44 -9
- package/src/create-translation-adapter.js.map +1 -1
- package/src/{context-error.d.ts → errors/context-error.d.ts} +1 -1
- package/src/errors/context-error.d.ts.map +1 -0
- package/src/errors/context-error.js +21 -0
- package/src/errors/context-error.js.map +1 -0
- package/src/{handleable.d.ts → errors/handleable.d.ts} +2 -2
- package/src/errors/handleable.d.ts.map +1 -0
- package/src/errors/handleable.js.map +1 -0
- package/src/errors/i18n-error.d.ts +32 -0
- package/src/errors/i18n-error.d.ts.map +1 -0
- package/src/errors/i18n-error.js +61 -0
- package/src/errors/i18n-error.js.map +1 -0
- package/src/errors/index.d.ts +5 -0
- package/src/errors/index.d.ts.map +1 -0
- package/src/errors/index.js +8 -0
- package/src/errors/index.js.map +1 -0
- package/src/{plugin-translatable-generic-error.d.ts → errors/plugin-translatable-generic.d.ts} +7 -7
- package/src/errors/plugin-translatable-generic.d.ts.map +1 -0
- package/src/{plugin-translatable-generic-error.js → errors/plugin-translatable-generic.js} +2 -3
- package/src/errors/plugin-translatable-generic.js.map +1 -0
- package/src/{plugin-translatable-handleable-generic.d.ts → errors/plugin-translatable-handleable-generic.d.ts} +4 -4
- package/src/errors/plugin-translatable-handleable-generic.d.ts.map +1 -0
- package/src/{plugin-translatable-handleable-generic.js → errors/plugin-translatable-handleable-generic.js} +2 -2
- package/src/errors/plugin-translatable-handleable-generic.js.map +1 -0
- package/src/errors/plugin-typed-handleable.d.ts +14 -0
- package/src/errors/plugin-typed-handleable.d.ts.map +1 -0
- package/src/{plugin-typed-handleable.js → errors/plugin-typed-handleable.js} +4 -4
- package/src/errors/plugin-typed-handleable.js.map +1 -0
- package/src/errors/translatable.d.ts +4 -0
- package/src/errors/translatable.d.ts.map +1 -0
- package/src/{translatable.js → errors/translatable.js} +4 -2
- package/src/errors/translatable.js.map +1 -0
- package/src/errors/typed-handleable.d.ts +13 -0
- package/src/errors/typed-handleable.d.ts.map +1 -0
- package/src/{typed-handleable.js → errors/typed-handleable.js} +8 -7
- package/src/errors/typed-handleable.js.map +1 -0
- package/src/{typed-error.d.ts → errors/typed.d.ts} +13 -15
- package/src/errors/typed.d.ts.map +1 -0
- package/src/{typed-error.js → errors/typed.js} +27 -23
- package/src/errors/typed.js.map +1 -0
- package/src/global-active-context.d.ts +4 -5
- package/src/global-active-context.d.ts.map +1 -1
- package/src/global-active-context.js +4 -4
- package/src/global-active-context.js.map +1 -1
- package/src/index.d.ts +15 -32
- package/src/index.d.ts.map +1 -1
- package/src/index.js +29 -37
- package/src/index.js.map +1 -1
- package/src/interfaces/component-config.interface.d.ts +9 -0
- package/src/interfaces/component-config.interface.d.ts.map +1 -0
- package/src/interfaces/component-config.interface.js +6 -0
- package/src/interfaces/component-config.interface.js.map +1 -0
- package/src/interfaces/engine-config.interface.d.ts +13 -0
- package/src/interfaces/engine-config.interface.d.ts.map +1 -0
- package/src/interfaces/engine-config.interface.js +6 -0
- package/src/interfaces/engine-config.interface.js.map +1 -0
- package/src/{i-global-active-context.d.ts → interfaces/global-active-context.d.ts} +5 -5
- package/src/interfaces/global-active-context.d.ts.map +1 -0
- package/src/{currency-format.js → interfaces/global-active-context.js} +1 -1
- package/src/interfaces/global-active-context.js.map +1 -0
- package/src/{i-handleable-error-options.d.ts → interfaces/handleable-error-options.d.ts} +1 -1
- package/src/interfaces/handleable-error-options.d.ts.map +1 -0
- package/src/{i-global-active-context.js → interfaces/handleable-error-options.js} +1 -1
- package/src/interfaces/handleable-error-options.js.map +1 -0
- package/src/{i-handleable.d.ts → interfaces/handleable.d.ts} +1 -1
- package/src/interfaces/handleable.d.ts.map +1 -0
- package/src/{i18n-config.js → interfaces/handleable.js} +1 -1
- package/src/interfaces/handleable.js.map +1 -0
- package/src/interfaces/i18n-engine.interface.d.ts +25 -0
- package/src/interfaces/i18n-engine.interface.d.ts.map +1 -0
- package/src/interfaces/i18n-engine.interface.js +6 -0
- package/src/interfaces/i18n-engine.interface.js.map +1 -0
- package/src/interfaces/index.d.ts +10 -0
- package/src/interfaces/index.d.ts.map +1 -0
- package/src/interfaces/index.js +13 -0
- package/src/interfaces/index.js.map +1 -0
- package/src/interfaces/language-definition.interface.d.ts +10 -0
- package/src/interfaces/language-definition.interface.d.ts.map +1 -0
- package/src/interfaces/language-definition.interface.js +6 -0
- package/src/interfaces/language-definition.interface.js.map +1 -0
- package/src/interfaces/translation-options.interface.d.ts +9 -0
- package/src/interfaces/translation-options.interface.d.ts.map +1 -0
- package/src/interfaces/translation-options.interface.js +6 -0
- package/src/interfaces/translation-options.interface.js.map +1 -0
- package/src/interfaces/validation-result.interface.d.ts +14 -0
- package/src/interfaces/validation-result.interface.d.ts.map +1 -0
- package/src/interfaces/validation-result.interface.js +6 -0
- package/src/interfaces/validation-result.interface.js.map +1 -0
- package/src/plugin-i18n-engine.d.ts.map +1 -1
- package/src/plugin-i18n-engine.js +19 -7
- package/src/plugin-i18n-engine.js.map +1 -1
- package/src/registry-config.d.ts +2 -2
- package/src/registry-config.d.ts.map +1 -1
- package/src/registry-error.d.ts +1 -1
- package/src/registry-error.d.ts.map +1 -1
- package/src/registry-error.js +3 -2
- package/src/registry-error.js.map +1 -1
- package/src/translation-engine.d.ts +2 -2
- package/src/translation-engine.d.ts.map +1 -1
- package/src/utils/currency.d.ts +19 -0
- package/src/utils/currency.d.ts.map +1 -0
- package/src/{currency.js → utils/currency.js} +25 -14
- package/src/utils/currency.js.map +1 -0
- package/src/utils/index.d.ts +7 -0
- package/src/utils/index.d.ts.map +1 -0
- package/src/utils/index.js +10 -0
- package/src/utils/index.js.map +1 -0
- package/src/utils/string-utils.d.ts +6 -0
- package/src/utils/string-utils.d.ts.map +1 -0
- package/src/utils/string-utils.js +34 -0
- package/src/utils/string-utils.js.map +1 -0
- package/src/utils/timezone.d.ts +13 -0
- package/src/utils/timezone.d.ts.map +1 -0
- package/src/utils/timezone.js +35 -0
- package/src/utils/timezone.js.map +1 -0
- package/src/context-error.d.ts.map +0 -1
- package/src/context-error.js +0 -18
- package/src/context-error.js.map +0 -1
- package/src/context-manager.d.ts +0 -34
- package/src/context-manager.d.ts.map +0 -1
- package/src/context-manager.js +0 -60
- package/src/context-manager.js.map +0 -1
- package/src/context.d.ts +0 -45
- package/src/context.d.ts.map +0 -1
- package/src/context.js +0 -70
- package/src/context.js.map +0 -1
- package/src/currency-code.d.ts +0 -20
- package/src/currency-code.d.ts.map +0 -1
- package/src/currency-code.js +0 -37
- package/src/currency-code.js.map +0 -1
- package/src/currency-format.d.ts +0 -11
- package/src/currency-format.d.ts.map +0 -1
- package/src/currency-format.js.map +0 -1
- package/src/currency.d.ts +0 -12
- package/src/currency.d.ts.map +0 -1
- package/src/currency.js.map +0 -1
- package/src/default-config.d.ts +0 -33
- package/src/default-config.d.ts.map +0 -1
- package/src/default-config.js +0 -131
- package/src/default-config.js.map +0 -1
- package/src/handleable.d.ts.map +0 -1
- package/src/handleable.js.map +0 -1
- package/src/i-global-active-context.d.ts.map +0 -1
- package/src/i-global-active-context.js.map +0 -1
- package/src/i-handleable-error-options.d.ts.map +0 -1
- package/src/i-handleable-error-options.js +0 -3
- package/src/i-handleable-error-options.js.map +0 -1
- package/src/i-handleable.d.ts.map +0 -1
- package/src/i-handleable.js +0 -3
- package/src/i-handleable.js.map +0 -1
- package/src/i18n-config.d.ts +0 -21
- package/src/i18n-config.d.ts.map +0 -1
- package/src/i18n-config.js.map +0 -1
- package/src/i18n-context.d.ts +0 -15
- package/src/i18n-context.d.ts.map +0 -1
- package/src/i18n-context.js +0 -3
- package/src/i18n-context.js.map +0 -1
- package/src/i18n-engine.d.ts +0 -179
- package/src/i18n-engine.d.ts.map +0 -1
- package/src/i18n-engine.js +0 -355
- package/src/i18n-engine.js.map +0 -1
- package/src/language-registry.d.ts +0 -114
- package/src/language-registry.d.ts.map +0 -1
- package/src/language-registry.js +0 -218
- package/src/language-registry.js.map +0 -1
- package/src/plugin-translatable-generic-error.d.ts.map +0 -1
- package/src/plugin-translatable-generic-error.js.map +0 -1
- package/src/plugin-translatable-handleable-generic.d.ts.map +0 -1
- package/src/plugin-translatable-handleable-generic.js.map +0 -1
- package/src/plugin-typed-handleable.d.ts +0 -15
- package/src/plugin-typed-handleable.d.ts.map +0 -1
- package/src/plugin-typed-handleable.js.map +0 -1
- package/src/timezone.d.ts +0 -12
- package/src/timezone.d.ts.map +0 -1
- package/src/timezone.js +0 -24
- package/src/timezone.js.map +0 -1
- package/src/translatable.d.ts +0 -6
- package/src/translatable.d.ts.map +0 -1
- package/src/translatable.js.map +0 -1
- package/src/typed-error.d.ts.map +0 -1
- package/src/typed-error.js.map +0 -1
- package/src/typed-handleable.d.ts +0 -15
- package/src/typed-handleable.d.ts.map +0 -1
- package/src/typed-handleable.js.map +0 -1
- package/src/unified-translator.d.ts +0 -31
- package/src/unified-translator.d.ts.map +0 -1
- package/src/unified-translator.js +0 -71
- package/src/unified-translator.js.map +0 -1
- /package/src/{handleable.js → errors/handleable.js} +0 -0
package/README.md
CHANGED
|
@@ -1,2395 +1,447 @@
|
|
|
1
1
|
# @digitaldefiance/i18n-lib
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A production-ready TypeScript internationalization library with component-based architecture, type-safe translations, and comprehensive error handling.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
**Version 1.1.0+** introduces a revolutionary plugin-based architecture with component registration and rigid compile-time type safety:
|
|
8
|
-
|
|
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
|
|
17
|
-
|
|
18
|
-
## Features
|
|
19
|
-
|
|
20
|
-
### Core Translation Features
|
|
21
|
-
|
|
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
|
|
24
|
-
- **Configuration Validation**: Automatic validation ensures all languages have complete string collections
|
|
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}}` (supports component IDs, registered aliases, or enum-name prefixes such as `{{SuiteCoreStringKey.User_Login}}`)
|
|
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]`
|
|
43
|
-
- **Extensible Configuration**: Module augmentation support for layered library extension
|
|
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)
|
|
110
|
-
|
|
111
|
-
## Installation
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
npm install @digitaldefiance/i18n-lib
|
|
115
|
-
# or
|
|
116
|
-
yarn add @digitaldefiance/i18n-lib
|
|
117
|
-
```
|
|
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
|
-
|
|
125
|
-
## Quick Start
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
import {
|
|
129
|
-
PluginI18nEngine,
|
|
130
|
-
ComponentDefinition,
|
|
131
|
-
ComponentRegistration,
|
|
132
|
-
LanguageDefinition,
|
|
133
|
-
CurrencyCode,
|
|
134
|
-
Timezone,
|
|
135
|
-
} from '@digitaldefiance/i18n-lib';
|
|
136
|
-
|
|
137
|
-
enum MyStringKey {
|
|
138
|
-
Welcome = 'welcome',
|
|
139
|
-
UserGreetingTemplate = 'userGreetingTemplate',
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type MyLanguages = 'en-US' | 'es';
|
|
143
|
-
|
|
144
|
-
const languages: ReadonlyArray<LanguageDefinition> = [
|
|
145
|
-
{ id: 'en-US', name: 'English (US)', code: 'en-US', isDefault: true },
|
|
146
|
-
{ id: 'es', name: 'Español', code: 'es' },
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
const engine = new PluginI18nEngine<MyLanguages>(languages, {
|
|
150
|
-
defaultCurrencyCode: new CurrencyCode('USD'),
|
|
151
|
-
timezone: new Timezone('UTC'),
|
|
152
|
-
adminTimezone: new Timezone('UTC'),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const MyComponent: ComponentDefinition<MyStringKey> = {
|
|
156
|
-
id: 'my-component',
|
|
157
|
-
name: 'My Component',
|
|
158
|
-
stringKeys: Object.values(MyStringKey),
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const registration: ComponentRegistration<MyStringKey, MyLanguages> = {
|
|
162
|
-
component: MyComponent,
|
|
163
|
-
strings: {
|
|
164
|
-
'en-US': {
|
|
165
|
-
[MyStringKey.Welcome]: 'Welcome!',
|
|
166
|
-
[MyStringKey.UserGreetingTemplate]: 'Hello, {name}!'
|
|
167
|
-
},
|
|
168
|
-
'es': {
|
|
169
|
-
[MyStringKey.Welcome]: '¡Bienvenido!',
|
|
170
|
-
[MyStringKey.UserGreetingTemplate]: '¡Hola, {name}!'
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
engine.registerComponent(registration);
|
|
176
|
-
|
|
177
|
-
const welcome = engine.translate('my-component', MyStringKey.Welcome);
|
|
178
|
-
// "Welcome!"
|
|
179
|
-
|
|
180
|
-
const greeting = engine.translate('my-component', MyStringKey.UserGreetingTemplate, { name: 'John' });
|
|
181
|
-
// "Hello, John!"
|
|
182
|
-
|
|
183
|
-
engine.setLanguage('es');
|
|
184
|
-
const spanishGreeting = engine.translate('my-component', MyStringKey.UserGreetingTemplate, { name: 'Juan' });
|
|
185
|
-
// "¡Hola, Juan!"
|
|
186
|
-
|
|
187
|
-
const templated = engine.t('User: {name} → {{my-component.UserGreetingTemplate}}', 'es', { name: 'Laura' });
|
|
188
|
-
// "User: Laura → ¡Hola, Laura!"
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
The constructor automatically registers the provided languages, seeds the global context (language, currency, timezone), and makes the instance discoverable through `PluginI18nEngine.getInstance()`. Prefer `setLanguage` or `updateContext({ language: 'es' })` instead of mutating context objects directly.
|
|
192
|
-
|
|
193
|
-
## Core Components
|
|
194
|
-
|
|
195
|
-
The library is built around several key components that work together to provide comprehensive i18n support:
|
|
196
|
-
|
|
197
|
-
### Component Registry
|
|
198
|
-
|
|
199
|
-
Manages translation components with automatic validation:
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
import { ComponentRegistry, ComponentDefinition, ComponentRegistration } from '@digitaldefiance/i18n-lib';
|
|
203
|
-
|
|
204
|
-
// Define component
|
|
205
|
-
const myComponent: ComponentDefinition<MyStringKeys> = {
|
|
206
|
-
id: 'my-component',
|
|
207
|
-
name: 'My Component',
|
|
208
|
-
stringKeys: Object.values(MyStringKeys)
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// Register with translations
|
|
212
|
-
const registration: ComponentRegistration<MyStringKeys, Languages> = {
|
|
213
|
-
component: myComponent,
|
|
214
|
-
strings: {
|
|
215
|
-
'en-US': { /* translations */ },
|
|
216
|
-
'fr': { /* translations */ }
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Key Features:**
|
|
222
|
-
|
|
223
|
-
- Component isolation with independent string key namespaces
|
|
224
|
-
- Automatic validation of translation completeness
|
|
225
|
-
- Dynamic component registration and updates
|
|
226
|
-
- Fallback generation for missing translations
|
|
227
|
-
- Detailed validation reporting
|
|
228
|
-
|
|
229
|
-
### Language Registry
|
|
230
|
-
|
|
231
|
-
Manages supported languages with metadata:
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { LanguageRegistry, LanguageDefinition, createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
235
|
-
|
|
236
|
-
const registry = new LanguageRegistry<'en-US' | 'fr' | 'es'>();
|
|
237
|
-
|
|
238
|
-
// Register languages
|
|
239
|
-
registry.registerLanguage(createLanguageDefinition('en-US', 'English (US)', 'en-US', true));
|
|
240
|
-
registry.registerLanguage(createLanguageDefinition('fr', 'Français', 'fr'));
|
|
241
|
-
|
|
242
|
-
// Query languages
|
|
243
|
-
const language = registry.getLanguage('en-US');
|
|
244
|
-
const byCode = registry.getLanguageByCode('fr');
|
|
245
|
-
const allLanguages = registry.getAllLanguages();
|
|
246
|
-
const displayNames = registry.getLanguageDisplayNames();
|
|
247
|
-
|
|
248
|
-
// Get language codes for Mongoose enum
|
|
249
|
-
const languageCodes = registry.getLanguageIds(); // ['en-US', 'fr', 'es']
|
|
250
|
-
const isoCodes = registry.getLanguageCodes(); // ['en-US', 'fr', 'es']
|
|
251
|
-
|
|
252
|
-
// Get matching language code with fallback logic
|
|
253
|
-
const matchedCode = registry.getMatchingLanguageCode(
|
|
254
|
-
'de-DE', // Requested code (not registered)
|
|
255
|
-
'fr', // User default (registered)
|
|
256
|
-
// Falls back to site default if neither match
|
|
257
|
-
);
|
|
258
|
-
// Returns: 'fr' (user default)
|
|
259
|
-
|
|
260
|
-
const defaultCode = registry.getMatchingLanguageCode();
|
|
261
|
-
// Returns: 'en-US' (site default)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**Key Features:**
|
|
265
|
-
|
|
266
|
-
- BCP 47 language code support
|
|
267
|
-
- Language lookup by ID or ISO code
|
|
268
|
-
- Display name mapping for UI rendering
|
|
269
|
-
- Default language management
|
|
270
|
-
- Duplicate detection and validation
|
|
271
|
-
- Extract language codes for schema definitions
|
|
272
|
-
- Intelligent language code matching with fallback chain
|
|
273
|
-
|
|
274
|
-
### Language Code Resolution
|
|
275
|
-
|
|
276
|
-
The Language Registry provides intelligent language code matching with a fallback chain for handling user preferences and browser language headers:
|
|
277
|
-
|
|
278
|
-
```typescript
|
|
279
|
-
import { LanguageRegistry } from '@digitaldefiance/i18n-lib';
|
|
280
|
-
|
|
281
|
-
const registry = new LanguageRegistry<'en-US' | 'fr' | 'es'>();
|
|
282
|
-
registry.registerLanguages([
|
|
283
|
-
createLanguageDefinition('en-US', 'English (US)', 'en-US', true),
|
|
284
|
-
createLanguageDefinition('fr', 'Français', 'fr'),
|
|
285
|
-
createLanguageDefinition('es', 'Español', 'es'),
|
|
286
|
-
]);
|
|
287
|
-
|
|
288
|
-
// Fallback chain: requested → user default → site default
|
|
289
|
-
const languageCode = registry.getMatchingLanguageCode(
|
|
290
|
-
req.headers['accept-language'], // 1. Try requested code first
|
|
291
|
-
req.user?.siteLanguage, // 2. Fall back to user default
|
|
292
|
-
// 3. Falls back to site default if neither match
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// Example scenarios:
|
|
296
|
-
registry.getMatchingLanguageCode('fr', 'es'); // Returns: 'fr' (requested exists)
|
|
297
|
-
registry.getMatchingLanguageCode('de', 'es'); // Returns: 'es' (user default exists)
|
|
298
|
-
registry.getMatchingLanguageCode('de', 'it'); // Returns: 'en-US' (site default)
|
|
299
|
-
registry.getMatchingLanguageCode(); // Returns: 'en-US' (site default)
|
|
300
|
-
registry.getMatchingLanguageCode('', ''); // Returns: 'en-US' (empty strings ignored)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**Use Cases:**
|
|
304
|
-
|
|
305
|
-
- HTTP Accept-Language header processing
|
|
306
|
-
- User preference resolution
|
|
307
|
-
- Browser language detection with fallback
|
|
308
|
-
- Multi-tenant applications with per-user defaults
|
|
309
|
-
|
|
310
|
-
**Error Handling:**
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
try {
|
|
314
|
-
const emptyRegistry = new LanguageRegistry();
|
|
315
|
-
emptyRegistry.getMatchingLanguageCode('en-US');
|
|
316
|
-
} catch (error) {
|
|
317
|
-
// Throws RegistryError if no default language configured
|
|
318
|
-
console.error('No default language configured');
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Enum Translation Registry
|
|
323
|
-
|
|
324
|
-
Translates enum values with type safety:
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
import { EnumTranslationRegistry } from '@digitaldefiance/i18n-lib';
|
|
328
|
-
|
|
329
|
-
enum Status {
|
|
330
|
-
Active = 'active',
|
|
331
|
-
Inactive = 'inactive'
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const enumRegistry = new EnumTranslationRegistry<string, 'en-US' | 'fr'>(
|
|
335
|
-
['en-US', 'fr'],
|
|
336
|
-
(key, vars) => engine.translate('core', key, vars)
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
enumRegistry.register(Status, {
|
|
340
|
-
'en-US': {
|
|
341
|
-
[Status.Active]: 'Active',
|
|
342
|
-
[Status.Inactive]: 'Inactive'
|
|
343
|
-
},
|
|
344
|
-
'fr': {
|
|
345
|
-
[Status.Active]: 'Actif',
|
|
346
|
-
[Status.Inactive]: 'Inactif'
|
|
347
|
-
}
|
|
348
|
-
}, 'Status');
|
|
349
|
-
|
|
350
|
-
const translated = enumRegistry.translate(Status, Status.Active, 'fr'); // 'Actif'
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Key Features:**
|
|
354
|
-
|
|
355
|
-
- Complete enum coverage validation
|
|
356
|
-
- Numeric and string enum support
|
|
357
|
-
- Automatic key resolution for numeric enums
|
|
358
|
-
- Localized error messages
|
|
359
|
-
|
|
360
|
-
### Global Active Context
|
|
361
|
-
|
|
362
|
-
Centralized context management for all engine instances:
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
import { GlobalActiveContext, IActiveContext } from '@digitaldefiance/i18n-lib';
|
|
366
|
-
|
|
367
|
-
const globalContext = GlobalActiveContext.getInstance<'en-US' | 'fr', IActiveContext<'en-US' | 'fr'>>();
|
|
368
|
-
|
|
369
|
-
// Create context for an instance
|
|
370
|
-
globalContext.createContext('en-US', 'en-US', 'my-app');
|
|
371
|
-
|
|
372
|
-
// Update context properties
|
|
373
|
-
globalContext.setUserLanguage('fr', 'my-app');
|
|
374
|
-
globalContext.setAdminLanguage('en-US', 'my-app');
|
|
375
|
-
globalContext.setCurrencyCode(new CurrencyCode('EUR'), 'my-app');
|
|
376
|
-
globalContext.setUserTimezone(new Timezone('Europe/Paris'), 'my-app');
|
|
377
|
-
|
|
378
|
-
// Get context
|
|
379
|
-
const context = globalContext.getContext('my-app');
|
|
380
|
-
console.log(context.language); // 'fr'
|
|
381
|
-
console.log(context.adminLanguage); // 'en-US'
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
**Key Features:**
|
|
385
|
-
|
|
386
|
-
- Per-instance context isolation
|
|
387
|
-
- User and admin language separation
|
|
388
|
-
- Currency code management
|
|
389
|
-
- Timezone handling
|
|
390
|
-
- Context space management (admin, user, system, api)
|
|
391
|
-
|
|
392
|
-
### Context Manager
|
|
393
|
-
|
|
394
|
-
Reactive context with change listeners:
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
import { ContextManager } from '@digitaldefiance/i18n-lib';
|
|
398
|
-
|
|
399
|
-
interface AppContext {
|
|
400
|
-
language: string;
|
|
401
|
-
theme: string;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const manager = new ContextManager<AppContext>();
|
|
405
|
-
|
|
406
|
-
// Add change listener
|
|
407
|
-
manager.addListener((property, oldValue, newValue) => {
|
|
408
|
-
console.log(`${property} changed from ${oldValue} to ${newValue}`);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Create reactive proxy
|
|
412
|
-
const context = { language: 'en', theme: 'dark' };
|
|
413
|
-
const reactiveContext = manager.createProxy(context);
|
|
414
|
-
|
|
415
|
-
// Changes trigger listeners
|
|
416
|
-
reactiveContext.language = 'fr'; // Logs: "language changed from en to fr"
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
**Key Features:**
|
|
420
|
-
|
|
421
|
-
- Property-level change notifications
|
|
422
|
-
- Multiple listener support
|
|
423
|
-
- Error-safe listener execution
|
|
424
|
-
- Proxy-based reactivity
|
|
425
|
-
|
|
426
|
-
## 🆕 Plugin-Based Architecture (New in v1.1.0)
|
|
427
|
-
|
|
428
|
-
The new plugin-based architecture provides a component registration system with rigid compile-time type safety.
|
|
429
|
-
|
|
430
|
-
### Quick Start with Plugin Architecture
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
import {
|
|
434
|
-
createCoreI18nEngine,
|
|
435
|
-
CoreStringKey,
|
|
436
|
-
CoreLanguageCode,
|
|
437
|
-
LanguageCodes,
|
|
438
|
-
getCoreLanguageCodes,
|
|
439
|
-
ComponentDefinition,
|
|
440
|
-
ComponentRegistration
|
|
441
|
-
} from '@digitaldefiance/i18n-lib';
|
|
442
|
-
|
|
443
|
-
// Create engine with default languages and core strings
|
|
444
|
-
const i18n = createCoreI18nEngine('myapp');
|
|
445
|
-
|
|
446
|
-
// Type-safe language parameter
|
|
447
|
-
const lang: CoreLanguageCode = LanguageCodes.FR; // Type-checked!
|
|
448
|
-
|
|
449
|
-
// Get supported language codes from registry (runtime)
|
|
450
|
-
const supportedLanguages = i18n.getLanguages().map((lang) => lang.id);
|
|
451
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
452
|
-
|
|
453
|
-
// Use core translations with type safety
|
|
454
|
-
const welcomeMessage = i18n.translate('core', CoreStringKey.System_Welcome, undefined, lang);
|
|
455
|
-
const errorMessage = i18n.translate('core', CoreStringKey.Error_ValidationFailed);
|
|
456
|
-
|
|
457
|
-
// Define your own component with type safety
|
|
458
|
-
enum MyComponentStringKey {
|
|
459
|
-
Welcome = 'welcome',
|
|
460
|
-
Goodbye = 'goodbye',
|
|
461
|
-
UserGreetingTemplate = 'userGreetingTemplate'
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const MyComponent: ComponentDefinition<MyComponentStringKey> = {
|
|
465
|
-
id: 'my-component',
|
|
466
|
-
name: 'My Custom Component',
|
|
467
|
-
stringKeys: Object.values(MyComponentStringKey)
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
// Define translations for all supported languages
|
|
471
|
-
const myComponentStrings = {
|
|
472
|
-
[LanguageCodes.EN_US]: {
|
|
473
|
-
[MyComponentStringKey.Welcome]: 'Welcome to my component!',
|
|
474
|
-
[MyComponentStringKey.Goodbye]: 'Goodbye from my component!',
|
|
475
|
-
[MyComponentStringKey.UserGreetingTemplate]: 'Hello, {name}!'
|
|
476
|
-
},
|
|
477
|
-
[LanguageCodes.FR]: {
|
|
478
|
-
[MyComponentStringKey.Welcome]: 'Bienvenue dans mon composant !',
|
|
479
|
-
[MyComponentStringKey.Goodbye]: 'Au revoir de mon composant !',
|
|
480
|
-
[MyComponentStringKey.UserGreetingTemplate]: 'Bonjour, {name} !'
|
|
481
|
-
},
|
|
482
|
-
[LanguageCodes.ES]: {
|
|
483
|
-
[MyComponentStringKey.Welcome]: '¡Bienvenido a mi componente!',
|
|
484
|
-
[MyComponentStringKey.Goodbye]: '¡Adiós desde mi componente!',
|
|
485
|
-
[MyComponentStringKey.UserGreetingTemplate]: '¡Hola, {name}!'
|
|
486
|
-
}
|
|
487
|
-
// TypeScript ensures all language codes are handled
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
// Register component (with validation)
|
|
491
|
-
const registration: ComponentRegistration<MyComponentStringKey, CoreLanguageCode> = {
|
|
492
|
-
component: MyComponent,
|
|
493
|
-
strings: myComponentStrings
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const validationResult = i18n.registerComponent(registration);
|
|
497
|
-
if (!validationResult.isValid) {
|
|
498
|
-
console.warn('Missing translations:', validationResult.missingKeys);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Use your component's translations
|
|
502
|
-
const welcome = i18n.translate('my-component', MyComponentStringKey.Welcome);
|
|
503
|
-
const greeting = i18n.translate('my-component', MyComponentStringKey.UserGreetingTemplate, {
|
|
504
|
-
name: 'John'
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Change language - affects all components
|
|
508
|
-
i18n.setLanguage(LanguageCodes.FR);
|
|
509
|
-
const frenchWelcome = i18n.translate('my-component', MyComponentStringKey.Welcome);
|
|
510
|
-
// "Bienvenue dans mon composant !"
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### Key Plugin Architecture Benefits
|
|
514
|
-
|
|
515
|
-
1. **Compile-Time Type Safety**: TypeScript ensures all string keys exist for all languages
|
|
516
|
-
2. **Component Isolation**: Each component manages its own strings independently
|
|
517
|
-
3. **Flexible Language Support**: Components can support different subsets of system languages
|
|
518
|
-
4. **Comprehensive Validation**: Automatic detection of missing translations with detailed reporting
|
|
519
|
-
5. **Fallback System**: Intelligent fallback to default language when translations are missing
|
|
520
|
-
6. **Dynamic Language Addition**: Add new languages at runtime with automatic validation updates
|
|
521
|
-
7. **Extensibility**: Easy to add new languages and components dynamically
|
|
522
|
-
|
|
523
|
-
### Pre-built Components
|
|
524
|
-
|
|
525
|
-
The library includes several pre-built components:
|
|
526
|
-
|
|
527
|
-
#### Core I18n Component
|
|
528
|
-
|
|
529
|
-
Provides essential system strings in 8 languages:
|
|
530
|
-
|
|
531
|
-
- English (US/UK), French, Spanish, German, Chinese (Simplified), Japanese, Ukrainian
|
|
532
|
-
|
|
533
|
-
**Language Codes**: Use `LanguageCodes` constants or define your own:
|
|
534
|
-
|
|
535
|
-
```typescript
|
|
536
|
-
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
537
|
-
|
|
538
|
-
// Common codes provided
|
|
539
|
-
LanguageCodes.EN_US // 'en-US'
|
|
540
|
-
LanguageCodes.FR // 'fr'
|
|
541
|
-
LanguageCodes.ES // 'es'
|
|
542
|
-
// ... or use any string: 'custom-lang'
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
import { createCoreI18nEngine, CoreStringKey } from '@digitaldefiance/i18n-lib';
|
|
547
|
-
|
|
548
|
-
const i18n = createCoreI18nEngine();
|
|
549
|
-
const saveText = i18n.translate('core', CoreStringKey.Common_Save);
|
|
550
|
-
const errorMsg = i18n.translate('core', CoreStringKey.Error_ValidationFailed);
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
#### Custom Component Example
|
|
554
|
-
|
|
555
|
-
Create your own component with translations:
|
|
556
|
-
|
|
557
|
-
```typescript
|
|
558
|
-
import {
|
|
559
|
-
ComponentDefinition,
|
|
560
|
-
ComponentRegistration,
|
|
561
|
-
LanguageCodes
|
|
562
|
-
} from '@digitaldefiance/i18n-lib';
|
|
563
|
-
|
|
564
|
-
enum UserStringKey {
|
|
565
|
-
Auth_Login = 'auth_login',
|
|
566
|
-
Error_UserNotFoundTemplate = 'error_user_not_found_template'
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const userComponent: ComponentDefinition<UserStringKey> = {
|
|
570
|
-
id: 'user-system',
|
|
571
|
-
name: 'User System',
|
|
572
|
-
stringKeys: Object.values(UserStringKey)
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
const registration: ComponentRegistration<UserStringKey, CoreLanguageCode> = {
|
|
576
|
-
component: userComponent,
|
|
577
|
-
strings: {
|
|
578
|
-
[LanguageCodes.EN_US]: {
|
|
579
|
-
[UserStringKey.Auth_Login]: 'Login',
|
|
580
|
-
[UserStringKey.Error_UserNotFoundTemplate]: 'User "{username}" not found'
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
i18n.registerComponent(registration);
|
|
586
|
-
|
|
587
|
-
// Use translations
|
|
588
|
-
const loginText = i18n.translate('user-system', UserStringKey.Auth_Login);
|
|
589
|
-
const userNotFound = i18n.translate(
|
|
590
|
-
'user-system',
|
|
591
|
-
UserStringKey.Error_UserNotFoundTemplate,
|
|
592
|
-
{ username: 'john_doe' }
|
|
593
|
-
);
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
## Advanced Features
|
|
597
|
-
|
|
598
|
-
### Translation Adapters
|
|
599
|
-
|
|
600
|
-
Create adapters to use PluginI18nEngine with components expecting the TranslationEngine interface:
|
|
601
|
-
|
|
602
|
-
```typescript
|
|
603
|
-
import { createTranslationAdapter, PluginI18nEngine, TranslationEngine } from '@digitaldefiance/i18n-lib';
|
|
604
|
-
|
|
605
|
-
const pluginEngine = PluginI18nEngine.getInstance<'en-US' | 'fr'>();
|
|
606
|
-
|
|
607
|
-
// Create adapter for a specific component
|
|
608
|
-
const adapter: TranslationEngine<MyStringKey> = createTranslationAdapter(
|
|
609
|
-
pluginEngine,
|
|
610
|
-
'my-component'
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
// Use with error classes or other components
|
|
614
|
-
class MyError extends Error {
|
|
615
|
-
constructor(
|
|
616
|
-
type: ErrorType,
|
|
617
|
-
engine: TranslationEngine<ErrorStringKey>
|
|
618
|
-
) {
|
|
619
|
-
const message = engine.translate(type);
|
|
620
|
-
super(message);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const error = new MyError(ErrorType.NotFound, adapter);
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
**Key Features:**
|
|
628
|
-
|
|
629
|
-
- Zero-overhead delegation to PluginI18nEngine
|
|
630
|
-
- Maintains full type safety
|
|
631
|
-
- Graceful error handling with fallback to key strings
|
|
632
|
-
- Seamless integration with existing code
|
|
633
|
-
|
|
634
|
-
### Translatable Errors
|
|
635
|
-
|
|
636
|
-
Generic error class with automatic translation:
|
|
637
|
-
|
|
638
|
-
```typescript
|
|
639
|
-
import { TranslatableGenericError, PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
640
|
-
|
|
641
|
-
enum UserErrorKey {
|
|
642
|
-
UserNotFound = 'userNotFound',
|
|
643
|
-
InvalidCredentials = 'invalidCredentials'
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Throw translatable error
|
|
647
|
-
throw new TranslatableGenericError(
|
|
648
|
-
'user-errors',
|
|
649
|
-
UserErrorKey.UserNotFound,
|
|
650
|
-
{ username: 'john_doe' },
|
|
651
|
-
'en-US',
|
|
652
|
-
{ userId: 123 },
|
|
653
|
-
'myapp'
|
|
654
|
-
);
|
|
655
|
-
|
|
656
|
-
// Create with explicit engine
|
|
657
|
-
const engine = PluginI18nEngine.getInstance<'en-US' | 'fr'>();
|
|
658
|
-
const error = TranslatableGenericError.withEngine(
|
|
659
|
-
engine,
|
|
660
|
-
'user-errors',
|
|
661
|
-
UserErrorKey.InvalidCredentials,
|
|
662
|
-
undefined,
|
|
663
|
-
'fr'
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
// Retranslate dynamically
|
|
667
|
-
try {
|
|
668
|
-
// ... code that throws TranslatableGenericError
|
|
669
|
-
} catch (error) {
|
|
670
|
-
if (error instanceof TranslatableGenericError) {
|
|
671
|
-
const localizedMessage = error.retranslate('fr', 'myapp');
|
|
672
|
-
sendToUser(localizedMessage);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
**Key Features:**
|
|
678
|
-
|
|
679
|
-
- Works with any registered component
|
|
680
|
-
- Uses safeTranslate for consistent fallback behavior
|
|
681
|
-
- Stores error context for retranslation
|
|
682
|
-
- Never throws during construction
|
|
683
|
-
- Metadata attachment for debugging
|
|
684
|
-
|
|
685
|
-
### Typed Errors
|
|
686
|
-
|
|
687
|
-
Base classes for creating strongly-typed error hierarchies:
|
|
688
|
-
|
|
689
|
-
```typescript
|
|
690
|
-
import { BaseTypedError, CompleteReasonMap, TranslationEngine } from '@digitaldefiance/i18n-lib';
|
|
691
|
-
|
|
692
|
-
enum DatabaseErrorType {
|
|
693
|
-
ConnectionFailed = 'connectionFailed',
|
|
694
|
-
QueryTimeout = 'queryTimeout'
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
enum DatabaseErrorKey {
|
|
698
|
-
ConnectionFailedMessage = 'connectionFailedMessage',
|
|
699
|
-
QueryTimeoutMessage = 'queryTimeoutMessage'
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const reasonMap: CompleteReasonMap<typeof DatabaseErrorType, DatabaseErrorKey> = {
|
|
703
|
-
[DatabaseErrorType.ConnectionFailed]: DatabaseErrorKey.ConnectionFailedMessage,
|
|
704
|
-
[DatabaseErrorType.QueryTimeout]: DatabaseErrorKey.QueryTimeoutMessage
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
class DatabaseError extends BaseTypedError<typeof DatabaseErrorType, DatabaseErrorKey> {
|
|
708
|
-
static create(
|
|
709
|
-
engine: TranslationEngine<DatabaseErrorKey>,
|
|
710
|
-
type: DatabaseErrorType,
|
|
711
|
-
metadata?: Record<string, any>
|
|
712
|
-
): DatabaseError {
|
|
713
|
-
return DatabaseError.createTranslated(
|
|
714
|
-
engine,
|
|
715
|
-
'database',
|
|
716
|
-
type,
|
|
717
|
-
reasonMap,
|
|
718
|
-
undefined,
|
|
719
|
-
undefined,
|
|
720
|
-
metadata
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
**Key Features:**
|
|
727
|
-
|
|
728
|
-
- Complete enum coverage enforcement
|
|
729
|
-
- Translation engine integration
|
|
730
|
-
- Simple and translated error creation
|
|
731
|
-
- Metadata support
|
|
732
|
-
|
|
733
|
-
### Template Processing
|
|
734
|
-
|
|
735
|
-
Advanced template system with multiple pattern types:
|
|
736
|
-
|
|
737
|
-
```typescript
|
|
738
|
-
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
739
|
-
|
|
740
|
-
const engine = PluginI18nEngine.getInstance<'en-US'>();
|
|
741
|
-
|
|
742
|
-
// Component-based patterns
|
|
743
|
-
const message1 = engine.t(
|
|
744
|
-
'{{core.Common_Welcome}} {{user.UserGreetingTemplate}}',
|
|
745
|
-
'en-US',
|
|
746
|
-
{ name: 'John' }
|
|
747
|
-
);
|
|
748
|
-
// "Welcome Hello, John!"
|
|
749
|
-
|
|
750
|
-
// Variable replacement
|
|
751
|
-
const message2 = engine.t(
|
|
752
|
-
'User {username} has {count} messages',
|
|
753
|
-
'en-US',
|
|
754
|
-
{ username: 'john_doe', count: 5 }
|
|
755
|
-
);
|
|
756
|
-
// "User john_doe has 5 messages"
|
|
757
|
-
|
|
758
|
-
// Mixed patterns
|
|
759
|
-
const message3 = engine.t(
|
|
760
|
-
'{{core.System_Welcome}}, {name}! {{core.System_PleaseWait}}',
|
|
761
|
-
'en-US',
|
|
762
|
-
{ name: 'Alice' }
|
|
763
|
-
);
|
|
764
|
-
// "Welcome, Alice! Please wait..."
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
**Pattern Types:**
|
|
768
|
-
|
|
769
|
-
- `{{componentId.stringKey}}`: Component-based translation
|
|
770
|
-
- `{variableName}`: Variable replacement
|
|
771
|
-
- Template strings automatically use first variable object
|
|
772
|
-
- Multiple variable objects merged for replacement
|
|
773
|
-
|
|
774
|
-
### Constants Support
|
|
775
|
-
|
|
776
|
-
Automatically replace constants in template strings:
|
|
777
|
-
|
|
778
|
-
```typescript
|
|
779
|
-
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
780
|
-
|
|
781
|
-
const constants = {
|
|
782
|
-
Site: 'MyApp.com',
|
|
783
|
-
Version: '2.0',
|
|
784
|
-
SupportEmail: 'support@myapp.com'
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
const engine = new PluginI18nEngine(languages, { constants });
|
|
788
|
-
|
|
789
|
-
const component: ComponentDefinition<'welcomeTemplate'> = {
|
|
790
|
-
id: 'app',
|
|
791
|
-
name: 'Application',
|
|
792
|
-
stringKeys: ['welcomeTemplate'],
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const registration: ComponentRegistration<'welcomeTemplate', 'en'> = {
|
|
796
|
-
component,
|
|
797
|
-
strings: {
|
|
798
|
-
en: { welcomeTemplate: 'Welcome to {Site} v{Version}' },
|
|
799
|
-
},
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
engine.registerComponent(registration);
|
|
803
|
-
const message = engine.translate('app', 'welcomeTemplate');
|
|
804
|
-
// "Welcome to MyApp.com v2.0"
|
|
805
|
-
|
|
806
|
-
// Variables override constants
|
|
807
|
-
const custom = engine.translate('app', 'welcomeTemplate', { Site: 'CustomSite.com' });
|
|
808
|
-
// "Welcome to CustomSite.com v2.0"
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
**Key Features:**
|
|
812
|
-
|
|
813
|
-
- Automatic constant replacement in template strings
|
|
814
|
-
- Variables take precedence over constants
|
|
815
|
-
- Shared constants across all components
|
|
816
|
-
- No need to pass constants on every translate call
|
|
817
|
-
|
|
818
|
-
### Currency Formatting
|
|
819
|
-
|
|
820
|
-
Locale-aware currency formatting:
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
823
|
-
import { getCurrencyFormat, CurrencyCode } from '@digitaldefiance/i18n-lib';
|
|
824
|
-
|
|
825
|
-
// Get format details
|
|
826
|
-
const usdFormat = getCurrencyFormat('en-US', 'USD');
|
|
827
|
-
console.log(usdFormat);
|
|
828
|
-
// {
|
|
829
|
-
// symbol: '$',
|
|
830
|
-
// position: 'prefix',
|
|
831
|
-
// groupSeparator: ',',
|
|
832
|
-
// decimalSeparator: '.'
|
|
833
|
-
// }
|
|
834
|
-
|
|
835
|
-
const eurFormat = getCurrencyFormat('de-DE', 'EUR');
|
|
836
|
-
console.log(eurFormat);
|
|
837
|
-
// {
|
|
838
|
-
// symbol: '€',
|
|
839
|
-
// position: 'postfix',
|
|
840
|
-
// groupSeparator: '.',
|
|
841
|
-
// decimalSeparator: ','
|
|
842
|
-
// }
|
|
843
|
-
|
|
844
|
-
// Validate currency codes
|
|
845
|
-
const currencyCode = new CurrencyCode('USD');
|
|
846
|
-
console.log(currencyCode.value); // 'USD'
|
|
847
|
-
console.log(CurrencyCode.values); // Array of all valid ISO 4217 codes
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
**Key Features:**
|
|
851
|
-
|
|
852
|
-
- ISO 4217 currency code validation
|
|
853
|
-
- Locale-aware symbol positioning
|
|
854
|
-
- Group and decimal separator detection
|
|
855
|
-
- Intl.NumberFormat integration
|
|
856
|
-
|
|
857
|
-
### Timezone Handling
|
|
858
|
-
|
|
859
|
-
Validated timezone management:
|
|
860
|
-
|
|
861
|
-
```typescript
|
|
862
|
-
import { Timezone, isValidTimezone } from '@digitaldefiance/i18n-lib';
|
|
863
|
-
|
|
864
|
-
// Create validated timezone
|
|
865
|
-
const tz = new Timezone('America/New_York');
|
|
866
|
-
console.log(tz.value); // 'America/New_York'
|
|
867
|
-
|
|
868
|
-
// Validate timezone strings
|
|
869
|
-
if (isValidTimezone('Europe/Paris')) {
|
|
870
|
-
const parisTz = new Timezone('Europe/Paris');
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Invalid timezone throws error
|
|
874
|
-
try {
|
|
875
|
-
new Timezone('Invalid/Timezone');
|
|
876
|
-
} catch (error) {
|
|
877
|
-
console.error('Invalid timezone');
|
|
878
|
-
}
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
**Key Features:**
|
|
882
|
-
|
|
883
|
-
- Moment-timezone validation
|
|
884
|
-
- Immutable timezone values
|
|
885
|
-
- Validation utilities
|
|
886
|
-
- IANA timezone database support
|
|
887
|
-
|
|
888
|
-
### Utility Functions
|
|
889
|
-
|
|
890
|
-
Helper functions for common operations:
|
|
891
|
-
|
|
892
|
-
```typescript
|
|
893
|
-
import {
|
|
894
|
-
replaceVariables,
|
|
895
|
-
isTemplate,
|
|
896
|
-
toStringKey,
|
|
897
|
-
buildReasonMap,
|
|
898
|
-
validateReasonMap,
|
|
899
|
-
createCompleteReasonMap
|
|
900
|
-
} from '@digitaldefiance/i18n-lib';
|
|
901
|
-
|
|
902
|
-
// Variable replacement
|
|
903
|
-
const result = replaceVariables(
|
|
904
|
-
'Hello, {name}! You have {count} messages.',
|
|
905
|
-
{ name: 'John', count: 5 }
|
|
906
|
-
);
|
|
907
|
-
// "Hello, John! You have 5 messages."
|
|
908
|
-
|
|
909
|
-
// Template detection
|
|
910
|
-
if (isTemplate('userGreetingTemplate')) {
|
|
911
|
-
// Handle as template
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// String key construction
|
|
915
|
-
const key = toStringKey('error', 'validation', 'failed');
|
|
916
|
-
// 'error_validation_failed'
|
|
917
|
-
|
|
918
|
-
// Reason map building
|
|
919
|
-
enum ErrorType {
|
|
920
|
-
NotFound = 'notFound',
|
|
921
|
-
AccessDenied = 'accessDenied'
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const reasonMap = buildReasonMap(ErrorType, ['error']);
|
|
925
|
-
// {
|
|
926
|
-
// notFound: 'error_notFound',
|
|
927
|
-
// accessDenied: 'error_accessDenied'
|
|
928
|
-
// }
|
|
929
|
-
|
|
930
|
-
// Validate reason map completeness
|
|
931
|
-
if (validateReasonMap(ErrorType, reasonMap)) {
|
|
932
|
-
// All enum values are mapped
|
|
933
|
-
}
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
### Advanced Plugin Usage
|
|
937
|
-
|
|
938
|
-
#### Compile-Time Completeness Enforcement (Strict Mode)
|
|
939
|
-
|
|
940
|
-
By default the plugin engine performs runtime validation and provides fallbacks. If you want **compile-time** enforcement that every language mapping contains every string key, use the helper in `strict-types`:
|
|
941
|
-
|
|
942
|
-
```typescript
|
|
943
|
-
import { createCompleteComponentStrings } from '@digitaldefiance/i18n-lib';
|
|
944
|
-
|
|
945
|
-
enum MyStrings {
|
|
946
|
-
Welcome = 'welcome',
|
|
947
|
-
Farewell = 'farewell'
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
type AppLang = 'en' | 'fr';
|
|
951
|
-
|
|
952
|
-
// This will only compile if BOTH languages contain BOTH keys.
|
|
953
|
-
const myStrictStrings = createCompleteComponentStrings<MyStrings, AppLang>({
|
|
954
|
-
en: {
|
|
955
|
-
[MyStrings.Welcome]: 'Welcome',
|
|
956
|
-
[MyStrings.Farewell]: 'Goodbye'
|
|
957
|
-
},
|
|
958
|
-
fr: {
|
|
959
|
-
[MyStrings.Welcome]: 'Bienvenue',
|
|
960
|
-
[MyStrings.Farewell]: 'Au revoir'
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
// If any key is missing, TypeScript reports an error before runtime.
|
|
965
|
-
```
|
|
966
|
-
|
|
967
|
-
The core library itself uses this helper for the core component (`createCoreComponentStrings`) to guarantee internal completeness. For partial / iterative authoring you can still start with normal objects and later switch to the strict helper when translations stabilize.
|
|
968
|
-
|
|
969
|
-
#### Adding New Languages
|
|
970
|
-
|
|
971
|
-
```typescript
|
|
972
|
-
import { createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
973
|
-
|
|
974
|
-
// Add Italian support
|
|
975
|
-
const italian = createLanguageDefinition('it', 'Italiano', 'it');
|
|
976
|
-
i18n.registerLanguage(italian);
|
|
977
|
-
|
|
978
|
-
// Update existing components with Italian translations
|
|
979
|
-
i18n.updateComponentStrings('my-component', {
|
|
980
|
-
it: {
|
|
981
|
-
[MyComponentStringKey.Welcome]: 'Benvenuto nel mio componente!',
|
|
982
|
-
[MyComponentStringKey.Goodbye]: 'Arrivederci dal mio componente!',
|
|
983
|
-
[MyComponentStringKey.UserGreetingTemplate]: 'Ciao, {name}!'
|
|
984
|
-
}
|
|
985
|
-
});
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
#### Component Registration Validation
|
|
989
|
-
|
|
990
|
-
The plugin engine provides comprehensive validation to ensure translation completeness:
|
|
991
|
-
|
|
992
|
-
```typescript
|
|
993
|
-
// Each component is validated against ALL system languages
|
|
994
|
-
enum MyStrings {
|
|
995
|
-
Welcome = 'welcome',
|
|
996
|
-
Goodbye = 'goodbye'
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
const myComponent: ComponentDefinition<MyStrings> = {
|
|
1000
|
-
id: 'my-component',
|
|
1001
|
-
name: 'My Component',
|
|
1002
|
-
stringKeys: Object.values(MyStrings)
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
// System has EN, FR, ES languages - component must provide translations for all three
|
|
1006
|
-
const registration: ComponentRegistration<MyStrings, CoreLanguageCode> = {
|
|
1007
|
-
component: myComponent,
|
|
1008
|
-
strings: {
|
|
1009
|
-
[LanguageCodes.EN_US]: {
|
|
1010
|
-
[MyStrings.Welcome]: 'Welcome',
|
|
1011
|
-
[MyStrings.Goodbye]: 'Goodbye'
|
|
1012
|
-
},
|
|
1013
|
-
[LanguageCodes.FR]: {
|
|
1014
|
-
[MyStrings.Welcome]: 'Bienvenue',
|
|
1015
|
-
[MyStrings.Goodbye]: 'Au revoir'
|
|
1016
|
-
},
|
|
1017
|
-
[LanguageCodes.ES]: {
|
|
1018
|
-
[MyStrings.Welcome]: 'Bienvenido',
|
|
1019
|
-
[MyStrings.Goodbye]: 'Adiós'
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
|
|
1024
|
-
const result = i18n.registerComponent(registration);
|
|
1025
|
-
if (!result.isValid) {
|
|
1026
|
-
console.log('Missing translations:', result.missingKeys);
|
|
1027
|
-
// Shows exactly which string keys are missing for which languages
|
|
1028
|
-
}
|
|
1029
|
-
```
|
|
1030
|
-
|
|
1031
|
-
#### Flexible Language Support
|
|
1032
|
-
|
|
1033
|
-
Components can support different subsets of system languages:
|
|
1034
|
-
|
|
1035
|
-
```typescript
|
|
1036
|
-
// Component A supports EN, FR, ES
|
|
1037
|
-
const componentA = {
|
|
1038
|
-
component: { id: 'comp-a', name: 'Component A', stringKeys: ['hello'] },
|
|
1039
|
-
strings: {
|
|
1040
|
-
'en-US': { hello: 'Hello' },
|
|
1041
|
-
'fr': { hello: 'Bonjour' },
|
|
1042
|
-
'es': { hello: 'Hola' }
|
|
1043
|
-
}
|
|
1044
|
-
};
|
|
1045
|
-
|
|
1046
|
-
// Component B only supports EN and DE (added later)
|
|
1047
|
-
const componentB = {
|
|
1048
|
-
component: { id: 'comp-b', name: 'Component B', stringKeys: ['save'] },
|
|
1049
|
-
strings: {
|
|
1050
|
-
'en-US': { save: 'Save' },
|
|
1051
|
-
'de': { save: 'Speichern' }
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
// Both components can coexist - missing translations use fallback
|
|
1056
|
-
i18n.registerComponent(componentA); // ✓ Complete
|
|
1057
|
-
i18n.registerComponent(componentB); // ⚠ Missing FR, ES - uses fallback
|
|
1058
|
-
|
|
1059
|
-
// Usage automatically handles fallbacks
|
|
1060
|
-
i18n.translate('comp-b', 'save', {}, 'fr'); // Returns 'Save' (en-US fallback)
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
#### Dynamic Language Addition
|
|
1064
|
-
|
|
1065
|
-
```typescript
|
|
1066
|
-
import { createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
1067
|
-
|
|
1068
|
-
// Add new language to system
|
|
1069
|
-
const germanLang = createLanguageDefinition('de', 'Deutsch', 'de');
|
|
1070
|
-
i18n.registerLanguage(germanLang);
|
|
1071
|
-
|
|
1072
|
-
// New component registrations now require German translations
|
|
1073
|
-
const newRegistration = {
|
|
1074
|
-
component: { id: 'new-comp', name: 'New Component', stringKeys: ['test'] },
|
|
1075
|
-
strings: {
|
|
1076
|
-
'en-US': { test: 'Test' },
|
|
1077
|
-
'fr': { test: 'Test' },
|
|
1078
|
-
'es': { test: 'Prueba' }
|
|
1079
|
-
// Missing 'de' - validation will flag this
|
|
1080
|
-
}
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
const result = i18n.registerComponent(newRegistration);
|
|
1084
|
-
console.log(result.missingKeys); // Shows missing German translations
|
|
1085
|
-
```
|
|
1086
|
-
|
|
1087
|
-
#### Validation and Error Handling
|
|
1088
|
-
|
|
1089
|
-
```typescript
|
|
1090
|
-
// Comprehensive validation
|
|
1091
|
-
const globalValidation = i18n.validateAllComponents();
|
|
1092
|
-
if (!globalValidation.isValid) {
|
|
1093
|
-
console.error('Validation errors:', globalValidation.errors);
|
|
1094
|
-
console.warn('Warnings:', globalValidation.warnings);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// Handle registration errors
|
|
1098
|
-
try {
|
|
1099
|
-
i18n.registerComponent(incompleteRegistration);
|
|
1100
|
-
} catch (error) {
|
|
1101
|
-
if (error instanceof RegistryError) {
|
|
1102
|
-
console.error(`Registry error: ${error.type}`, error.metadata);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// Strict validation mode (rejects incomplete registrations)
|
|
1107
|
-
const strictEngine = new PluginI18nEngine(languages, {
|
|
1108
|
-
validation: {
|
|
1109
|
-
requireCompleteStrings: true,
|
|
1110
|
-
allowPartialRegistration: false,
|
|
1111
|
-
fallbackLanguageId: 'en-US'
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
// Optional instance registration overrides (third constructor argument)
|
|
1116
|
-
const reportingEngine = new PluginI18nEngine(languages, {}, {
|
|
1117
|
-
instanceKey: 'reporting',
|
|
1118
|
-
registerInstance: false,
|
|
1119
|
-
setAsDefault: false
|
|
1120
|
-
});
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
#### Multi-Instance Support
|
|
1124
|
-
|
|
1125
|
-
```typescript
|
|
1126
|
-
// Create separate instances for different contexts
|
|
1127
|
-
const adminI18n = PluginI18nEngine.createInstance('admin', languages);
|
|
1128
|
-
const userI18n = PluginI18nEngine.createInstance('user', languages);
|
|
1129
|
-
|
|
1130
|
-
// Register different components for each
|
|
1131
|
-
adminI18n.registerComponent(adminComponentRegistration);
|
|
1132
|
-
userI18n.registerComponent(userComponentRegistration);
|
|
1133
|
-
|
|
1134
|
-
// Create an isolated instance without touching the global registry (handy for tests)
|
|
1135
|
-
const detachedI18n = new PluginI18nEngine(languages, {}, { registerInstance: false });
|
|
1136
|
-
detachedI18n.registerComponent(myTestRegistration);
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
For complete documentation on the plugin architecture, see [PLUGIN_ARCHITECTURE.md](./PLUGIN_ARCHITECTURE.md).
|
|
1140
|
-
|
|
1141
|
-
## Advanced Features
|
|
1142
|
-
|
|
1143
|
-
### Translatable Errors
|
|
1144
|
-
|
|
1145
|
-
The `TranslatableGenericError` class provides a simple way to create errors with translated messages that work across any component:
|
|
1146
|
-
|
|
1147
|
-
```typescript
|
|
1148
|
-
import { TranslatableGenericError, CoreStringKey, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1149
|
-
|
|
1150
|
-
// Define your error string keys
|
|
1151
|
-
enum UserErrorKey {
|
|
1152
|
-
UserNotFound = 'userNotFound',
|
|
1153
|
-
InvalidCredentials = 'invalidCredentials',
|
|
1154
|
-
AccountLocked = 'accountLocked',
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// Register your component with translations
|
|
1158
|
-
const userErrorComponent = {
|
|
1159
|
-
id: 'user-errors',
|
|
1160
|
-
name: 'User Errors',
|
|
1161
|
-
stringKeys: Object.values(UserErrorKey)
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
const registration = {
|
|
1165
|
-
component: userErrorComponent,
|
|
1166
|
-
strings: {
|
|
1167
|
-
'en-US': {
|
|
1168
|
-
[UserErrorKey.UserNotFound]: 'User "{username}" not found',
|
|
1169
|
-
[UserErrorKey.InvalidCredentials]: 'Invalid credentials provided',
|
|
1170
|
-
[UserErrorKey.AccountLocked]: 'Account locked until {unlockTime}'
|
|
1171
|
-
},
|
|
1172
|
-
'fr': {
|
|
1173
|
-
[UserErrorKey.UserNotFound]: 'Utilisateur "{username}" introuvable',
|
|
1174
|
-
[UserErrorKey.InvalidCredentials]: 'Identifiants invalides fournis',
|
|
1175
|
-
[UserErrorKey.AccountLocked]: 'Compte verrouillé jusqu\'à {unlockTime}'
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
};
|
|
1179
|
-
|
|
1180
|
-
i18n.registerComponent(registration);
|
|
1181
|
-
|
|
1182
|
-
// Throw translatable errors
|
|
1183
|
-
throw new TranslatableGenericError(
|
|
1184
|
-
'user-errors',
|
|
1185
|
-
UserErrorKey.UserNotFound,
|
|
1186
|
-
{ username: 'john_doe' },
|
|
1187
|
-
'en-US',
|
|
1188
|
-
{ userId: 123 }, // metadata
|
|
1189
|
-
'myapp' // engine instance key
|
|
1190
|
-
);
|
|
1191
|
-
|
|
1192
|
-
// Use with explicit engine instance
|
|
1193
|
-
const error = TranslatableGenericError.withEngine(
|
|
1194
|
-
i18n,
|
|
1195
|
-
'user-errors',
|
|
1196
|
-
UserErrorKey.InvalidCredentials,
|
|
1197
|
-
undefined,
|
|
1198
|
-
'fr'
|
|
1199
|
-
);
|
|
1200
|
-
|
|
1201
|
-
// Retranslate errors dynamically
|
|
1202
|
-
try {
|
|
1203
|
-
// ... code that throws TranslatableGenericError
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
if (error instanceof TranslatableGenericError) {
|
|
1206
|
-
const localizedMessage = error.retranslate(userLanguage, 'myapp');
|
|
1207
|
-
sendToUser(localizedMessage);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Use with core strings
|
|
1212
|
-
throw new TranslatableGenericError(
|
|
1213
|
-
'core',
|
|
1214
|
-
CoreStringKey.Error_AccessDenied,
|
|
1215
|
-
undefined,
|
|
1216
|
-
LanguageCodes.EN_US,
|
|
1217
|
-
{ requestId: '12345' },
|
|
1218
|
-
'myapp'
|
|
1219
|
-
);
|
|
1220
|
-
```
|
|
1221
|
-
|
|
1222
|
-
**Key Features:**
|
|
1223
|
-
|
|
1224
|
-
- Works with any registered component and string keys
|
|
1225
|
-
- Uses `safeTranslate` for consistent fallback behavior (`[componentId.stringKey]`)
|
|
1226
|
-
- Stores error context: stringKey, componentId, language, variables, metadata
|
|
1227
|
-
- Supports dynamic retranslation with `retranslate()` method
|
|
1228
|
-
- Never throws during construction - always returns a valid error
|
|
1229
|
-
- Compatible with both constructor and static factory methods
|
|
1230
|
-
|
|
1231
|
-
For complete documentation, see [TRANSLATABLE_ERROR_GUIDE.md](./TRANSLATABLE_ERROR_GUIDE.md).
|
|
1232
|
-
|
|
1233
|
-
### Enum Translation Registry
|
|
1234
|
-
|
|
1235
|
-
```typescript
|
|
1236
|
-
enum Status {
|
|
1237
|
-
Active = 'active',
|
|
1238
|
-
Inactive = 'inactive',
|
|
1239
|
-
Pending = 'pending'
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
// Register enum translations (requires complete translations)
|
|
1243
|
-
i18n.registerEnum(Status, {
|
|
1244
|
-
[MyLanguages.English]: {
|
|
1245
|
-
[Status.Active]: 'Active',
|
|
1246
|
-
[Status.Inactive]: 'Inactive',
|
|
1247
|
-
[Status.Pending]: 'Pending'
|
|
1248
|
-
},
|
|
1249
|
-
[MyLanguages.Spanish]: {
|
|
1250
|
-
[Status.Active]: 'Activo',
|
|
1251
|
-
[Status.Inactive]: 'Inactivo',
|
|
1252
|
-
[Status.Pending]: 'Pendiente'
|
|
1253
|
-
}
|
|
1254
|
-
}, 'Status');
|
|
1255
|
-
|
|
1256
|
-
// Translate enum values
|
|
1257
|
-
const statusText = i18n.translateEnum(Status, Status.Active, MyLanguages.Spanish);
|
|
1258
|
-
// "Activo"
|
|
1259
|
-
```
|
|
1260
|
-
|
|
1261
|
-
### Template Processing
|
|
1262
|
-
|
|
1263
|
-
```typescript
|
|
1264
|
-
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1265
|
-
|
|
1266
|
-
const engine = PluginI18nEngine.getInstance<'en-US' | 'es'>();
|
|
1267
|
-
|
|
1268
|
-
// Component placeholders are resolved before variable replacement
|
|
1269
|
-
const message = engine.t(
|
|
1270
|
-
'{{core.System_Welcome}} {name}! {{core.System_PleaseWait}}',
|
|
1271
|
-
'en-US',
|
|
1272
|
-
{ name: 'John' }
|
|
1273
|
-
);
|
|
1274
|
-
// "Welcome John! Please wait..."
|
|
1275
|
-
|
|
1276
|
-
// Templates ending with "Template" automatically receive the first var map
|
|
1277
|
-
const greeting = engine.t(
|
|
1278
|
-
'{{my-component.UserGreetingTemplate}}',
|
|
1279
|
-
'es',
|
|
1280
|
-
{ name: 'Laura' },
|
|
1281
|
-
{ fallbackName: 'Usuario' }
|
|
1282
|
-
);
|
|
1283
|
-
// "¡Hola, Laura!"
|
|
1284
|
-
|
|
1285
|
-
// Additional variable objects are merged left→right
|
|
1286
|
-
const merged = engine.t(
|
|
1287
|
-
'User {username} has {count} notifications',
|
|
1288
|
-
'en-US',
|
|
1289
|
-
{ username: 'alice' },
|
|
1290
|
-
{ count: 5 }
|
|
1291
|
-
);
|
|
1292
|
-
// "User alice has 5 notifications"
|
|
1293
|
-
```
|
|
1294
|
-
|
|
1295
|
-
### Context Management
|
|
1296
|
-
|
|
1297
|
-
```typescript
|
|
1298
|
-
import { PluginI18nEngine, CurrencyCode, Timezone } from '@digitaldefiance/i18n-lib';
|
|
1299
|
-
|
|
1300
|
-
const engine = PluginI18nEngine.getInstance<'en-US' | 'es'>();
|
|
1301
|
-
|
|
1302
|
-
engine.updateContext({
|
|
1303
|
-
language: 'es',
|
|
1304
|
-
adminLanguage: 'en-US',
|
|
1305
|
-
currencyCode: new CurrencyCode('EUR'),
|
|
1306
|
-
timezone: new Timezone('Europe/Madrid'),
|
|
1307
|
-
adminTimezone: new Timezone('UTC'),
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
engine.updateContext({ currentContext: 'admin' });
|
|
1311
|
-
|
|
1312
|
-
const context = engine.getContext();
|
|
1313
|
-
console.log(context.currentContext); // 'admin'
|
|
1314
|
-
console.log(context.language); // 'es'
|
|
1315
|
-
|
|
1316
|
-
engine.updateContext({ currentContext: 'user' });
|
|
1317
|
-
engine.setLanguage('en-US');
|
|
1318
|
-
```
|
|
1319
|
-
|
|
1320
|
-
### Context Change Monitoring
|
|
1321
|
-
|
|
1322
|
-
```typescript
|
|
1323
|
-
import { ContextManager } from '@digitaldefiance/i18n-lib';
|
|
1324
|
-
|
|
1325
|
-
interface AppContext {
|
|
1326
|
-
language: string;
|
|
1327
|
-
theme: string;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
const manager = new ContextManager<AppContext>();
|
|
1331
|
-
|
|
1332
|
-
// Add listeners for context changes
|
|
1333
|
-
manager.addListener((property, oldValue, newValue) => {
|
|
1334
|
-
console.log(`${property} changed from ${oldValue} to ${newValue}`);
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
// Create reactive context
|
|
1338
|
-
const context = { language: 'en', theme: 'dark' };
|
|
1339
|
-
const reactiveContext = manager.createProxy(context);
|
|
1340
|
-
|
|
1341
|
-
// Changes are automatically detected
|
|
1342
|
-
reactiveContext.language = 'es'; // Triggers listener
|
|
1343
|
-
```
|
|
1344
|
-
|
|
1345
|
-
### Currency Formatting
|
|
1346
|
-
|
|
1347
|
-
```typescript
|
|
1348
|
-
import { getCurrencyFormat } from '@digitaldefiance/i18n-lib';
|
|
1349
|
-
|
|
1350
|
-
// Get currency formatting information
|
|
1351
|
-
const usdFormat = getCurrencyFormat('en-US', 'USD');
|
|
1352
|
-
// { symbol: '$', position: 'prefix', groupSeparator: ',', decimalSeparator: '.' }
|
|
1353
|
-
|
|
1354
|
-
const eurFormat = getCurrencyFormat('de-DE', 'EUR');
|
|
1355
|
-
// { symbol: '€', position: 'postfix', groupSeparator: '.', decimalSeparator: ',' }
|
|
1356
|
-
```
|
|
1357
|
-
|
|
1358
|
-
### Instance Management
|
|
1359
|
-
|
|
1360
|
-
```typescript
|
|
1361
|
-
import { PluginI18nEngine, LanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
1362
|
-
|
|
1363
|
-
const languages: ReadonlyArray<LanguageDefinition> = [
|
|
1364
|
-
{ id: 'en-US', name: 'English (US)', code: 'en-US', isDefault: true },
|
|
1365
|
-
{ id: 'fr', name: 'Français', code: 'fr' },
|
|
1366
|
-
];
|
|
1367
|
-
|
|
1368
|
-
// Create named instances
|
|
1369
|
-
const mainI18n = PluginI18nEngine.createInstance('main', languages);
|
|
1370
|
-
const adminI18n = PluginI18nEngine.createInstance('admin', languages);
|
|
1371
|
-
|
|
1372
|
-
// Get instances by key
|
|
1373
|
-
const instance = PluginI18nEngine.getInstance('main');
|
|
1374
|
-
|
|
1375
|
-
// Clean up instances (useful for testing)
|
|
1376
|
-
PluginI18nEngine.removeInstance('main');
|
|
1377
|
-
PluginI18nEngine.resetAll();
|
|
1378
|
-
```
|
|
1379
|
-
|
|
1380
|
-
## Supported Languages
|
|
1381
|
-
|
|
1382
|
-
The library includes pre-built translations for 8 languages in the core component:
|
|
1383
|
-
|
|
1384
|
-
| Language Code | Display Name | ISO Code |
|
|
1385
|
-
|--------------|--------------|----------|
|
|
1386
|
-
| `en-US` | English (US) | en-US |
|
|
1387
|
-
| `en-GB` | English (UK) | en-GB |
|
|
1388
|
-
| `fr` | Français | fr |
|
|
1389
|
-
| `es` | Español | es |
|
|
1390
|
-
| `de` | Deutsch | de |
|
|
1391
|
-
| `zh-CN` | 中文 (简体) | zh-CN |
|
|
1392
|
-
| `ja` | 日本語 | ja |
|
|
1393
|
-
| `uk` | Українська | uk |
|
|
1394
|
-
|
|
1395
|
-
### Language Code Constants
|
|
1396
|
-
|
|
1397
|
-
```typescript
|
|
1398
|
-
import { LanguageCodes, LanguageDisplayNames, getCoreLanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1399
|
-
|
|
1400
|
-
// Use constants for type safety
|
|
1401
|
-
const lang = LanguageCodes.FR; // 'fr'
|
|
1402
|
-
const displayName = LanguageDisplayNames[LanguageCodes.FR]; // 'Français'
|
|
1403
|
-
|
|
1404
|
-
// Get all core language codes as array (for Mongoose enums, etc.)
|
|
1405
|
-
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1406
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1407
|
-
|
|
1408
|
-
// All available codes
|
|
1409
|
-
const codes = {
|
|
1410
|
-
EN_US: 'en-US',
|
|
1411
|
-
EN_GB: 'en-GB',
|
|
1412
|
-
FR: 'fr',
|
|
1413
|
-
ES: 'es',
|
|
1414
|
-
DE: 'de',
|
|
1415
|
-
ZH_CN: 'zh-CN',
|
|
1416
|
-
JA: 'ja',
|
|
1417
|
-
UK: 'uk'
|
|
1418
|
-
};
|
|
1419
|
-
```
|
|
1420
|
-
|
|
1421
|
-
### Custom Language Codes
|
|
1422
|
-
|
|
1423
|
-
Extend core languages with custom ones while maintaining type safety:
|
|
1424
|
-
|
|
1425
|
-
```typescript
|
|
1426
|
-
import {
|
|
1427
|
-
CoreLanguageCode,
|
|
1428
|
-
getCoreLanguageDefinitions,
|
|
1429
|
-
createLanguageDefinition,
|
|
1430
|
-
PluginI18nEngine
|
|
1431
|
-
} from '@digitaldefiance/i18n-lib';
|
|
1432
|
-
|
|
1433
|
-
// Define custom language type extending core
|
|
1434
|
-
type MyLanguageCode = CoreLanguageCode | 'pt-BR' | 'it';
|
|
1435
|
-
|
|
1436
|
-
// Create engine with extended languages
|
|
1437
|
-
const engine = PluginI18nEngine.createInstance<MyLanguageCode>(
|
|
1438
|
-
'myapp',
|
|
1439
|
-
[
|
|
1440
|
-
...getCoreLanguageDefinitions(),
|
|
1441
|
-
createLanguageDefinition('pt-BR', 'Português (Brasil)', 'pt-BR'),
|
|
1442
|
-
createLanguageDefinition('it', 'Italiano', 'it')
|
|
1443
|
-
]
|
|
1444
|
-
);
|
|
1445
|
-
|
|
1446
|
-
// Type-safe language usage
|
|
1447
|
-
const lang1: MyLanguageCode = 'en-US'; // ✓ Core language
|
|
1448
|
-
const lang2: MyLanguageCode = 'pt-BR'; // ✓ Custom language
|
|
1449
|
-
const lang3: MyLanguageCode = 'invalid'; // ✗ Type error!
|
|
1450
|
-
```
|
|
1451
|
-
|
|
1452
|
-
### Helper Functions for Mongoose Schemas
|
|
1453
|
-
|
|
1454
|
-
Extract language codes from the registry (single source of truth):
|
|
1455
|
-
|
|
1456
|
-
```typescript
|
|
1457
|
-
import { PluginI18nEngine, LanguageRegistry, getCoreLanguageCodes, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1458
|
-
import { Schema } from 'mongoose';
|
|
1459
|
-
|
|
1460
|
-
// Static approach: Get core language codes as array
|
|
1461
|
-
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1462
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1463
|
-
|
|
1464
|
-
// Dynamic approach: Get from engine instance (includes custom languages)
|
|
1465
|
-
const engine = PluginI18nEngine.getInstance<string>();
|
|
1466
|
-
const languageDefinitions = engine.getLanguages();
|
|
1467
|
-
const languageIds = languageDefinitions.map((lang) => lang.id);
|
|
1468
|
-
const isoCodes = languageDefinitions.map((lang) => lang.code);
|
|
1469
|
-
|
|
1470
|
-
// Use in Mongoose schema
|
|
1471
|
-
const userSchema = new Schema({
|
|
1472
|
-
language: {
|
|
1473
|
-
type: String,
|
|
1474
|
-
enum: coreLanguageCodes, // Static core languages
|
|
1475
|
-
default: LanguageCodes.EN_US
|
|
1476
|
-
},
|
|
1477
|
-
adminLanguage: {
|
|
1478
|
-
type: String,
|
|
1479
|
-
enum: languageIds, // Dynamic from registry
|
|
1480
|
-
default: LanguageCodes.EN_US
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
|
-
// Get display names for validation messages
|
|
1485
|
-
const displayNames = LanguageRegistry.getLanguageDisplayNames();
|
|
1486
|
-
// { 'en-US': 'English (US)', 'fr': 'Français', ... }
|
|
1487
|
-
```
|
|
1488
|
-
|
|
1489
|
-
## Core String Keys
|
|
1490
|
-
|
|
1491
|
-
The core component provides 40+ system strings organized by category:
|
|
1492
|
-
|
|
1493
|
-
### Common Strings
|
|
1494
|
-
|
|
1495
|
-
- `Common_Yes`, `Common_No`, `Common_Cancel`, `Common_OK`
|
|
1496
|
-
- `Common_Save`, `Common_Delete`, `Common_Edit`, `Common_Create`, `Common_Update`
|
|
1497
|
-
- `Common_Loading`, `Common_Error`, `Common_Success`, `Common_Warning`, `Common_Info`
|
|
1498
|
-
- `Common_Disposed`
|
|
1499
|
-
|
|
1500
|
-
### Error Messages
|
|
1501
|
-
|
|
1502
|
-
- `Error_InvalidInput`, `Error_NetworkError`, `Error_NotFound`
|
|
1503
|
-
- `Error_AccessDenied`, `Error_InternalServer`, `Error_ValidationFailed`
|
|
1504
|
-
- `Error_RequiredField`, `Error_InvalidContextTemplate`
|
|
1505
|
-
- `Error_MissingTranslationKeyTemplate`
|
|
1506
|
-
|
|
1507
|
-
### Registry Error Templates
|
|
1508
|
-
|
|
1509
|
-
- `Error_ComponentNotFoundTemplate`
|
|
1510
|
-
- `Error_LanguageNotFoundTemplate`
|
|
1511
|
-
- `Error_StringKeyNotFoundTemplate`
|
|
1512
|
-
- `Error_IncompleteRegistrationTemplate`
|
|
1513
|
-
- `Error_DuplicateComponentTemplate`
|
|
1514
|
-
- `Error_DuplicateLanguageTemplate`
|
|
1515
|
-
- `Error_ValidationFailedTemplate`
|
|
1516
|
-
|
|
1517
|
-
### System Messages
|
|
1518
|
-
|
|
1519
|
-
- `System_Welcome`, `System_Goodbye`, `System_PleaseWait`
|
|
1520
|
-
- `System_ProcessingRequest`, `System_OperationComplete`
|
|
1521
|
-
- `System_NoDataAvailable`
|
|
1522
|
-
|
|
1523
|
-
## API Reference
|
|
1524
|
-
|
|
1525
|
-
### Plugin Architecture API
|
|
1526
|
-
|
|
1527
|
-
#### PluginI18nEngine
|
|
1528
|
-
|
|
1529
|
-
**Constructor**
|
|
1530
|
-
|
|
1531
|
-
- `new PluginI18nEngine<TLanguages>(languages, config?, options?)` - Create new plugin engine
|
|
1532
|
-
- `PluginI18nEngine.createInstance<TLanguages>(key, languages, config?)` - Create named instance
|
|
1533
|
-
- `PluginI18nEngine.getInstance<TLanguages>(key?)` - Get existing instance (throws error if not found)
|
|
1534
|
-
|
|
1535
|
-
`options` enables fine-grained control over how the engine participates in the static registry:
|
|
1536
|
-
|
|
1537
|
-
- `instanceKey` – Override the implicit registry key when instantiating directly (defaults to `'default'`).
|
|
1538
|
-
- `registerInstance` – Set to `false` to keep the engine out of the shared instance map (useful for tests or short-lived contexts).
|
|
1539
|
-
- `setAsDefault` – Force this instance to become the default lookup target even if another default exists.
|
|
1540
|
-
|
|
1541
|
-
**Component Management**
|
|
1542
|
-
|
|
1543
|
-
- `registerComponent<TStringKeys>(registration)` - Register component with translations
|
|
1544
|
-
- `updateComponentStrings<TStringKeys>(componentId, strings)` - Update existing component strings
|
|
1545
|
-
- `getComponents()` - Get all registered components
|
|
1546
|
-
- `hasComponent(componentId)` - Check if component exists
|
|
1547
|
-
|
|
1548
|
-
**Translation Methods**
|
|
1549
|
-
|
|
1550
|
-
- `translate<TStringKeys>(componentId, stringKey, variables?, language?)` - Translate component string
|
|
1551
|
-
- `safeTranslate(componentId, stringKey, variables?, language?)` - Safe translate with fallback
|
|
1552
|
-
- `getTranslationDetails<TStringKeys>(componentId, stringKey, variables?, language?)` - Get detailed translation response
|
|
1553
|
-
|
|
1554
|
-
**Language Management**
|
|
1555
|
-
|
|
1556
|
-
- `registerLanguage(language)` - Register new language
|
|
1557
|
-
- `registerLanguages(languages)` - Register multiple languages
|
|
1558
|
-
- `getLanguages()` - Get all registered languages
|
|
1559
|
-
- `hasLanguage(language)` - Check if language exists
|
|
1560
|
-
- `setLanguage(language)` - Set current language
|
|
1561
|
-
- `getLanguageByCode(code)` - Get language by ISO code
|
|
1562
|
-
- `getMatchingLanguageCode(requestedCode?, userDefaultCode?)` - Get matching language code with fallback logic
|
|
1563
|
-
|
|
1564
|
-
**Context & Validation**
|
|
1565
|
-
|
|
1566
|
-
- `getContext()` - Read the active context (language, adminLanguage, currency, timezone)
|
|
1567
|
-
- `updateContext(updates)` - Apply partial context updates
|
|
1568
|
-
- `validateAllComponents()` - Validate all registered components
|
|
1569
|
-
- `getComponentRegistry()` - Access component registry directly
|
|
1570
|
-
- `getEnumRegistry()` - Access enum registry directly
|
|
1571
|
-
- Use `LanguageRegistry` statics (e.g. `LanguageRegistry.getLanguageIds()`) for low-level language metadata
|
|
1572
|
-
|
|
1573
|
-
#### Core I18n Functions
|
|
1574
|
-
|
|
1575
|
-
- `createCoreI18nEngine(instanceKey?)` - Create engine with core components
|
|
1576
|
-
- `getCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Get core translation
|
|
1577
|
-
- `safeCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Safe core translation
|
|
1578
|
-
|
|
1579
|
-
#### Component Registration Types
|
|
1580
|
-
|
|
1581
|
-
```typescript
|
|
1582
|
-
interface ComponentDefinition<TStringKeys extends string> {
|
|
1583
|
-
readonly id: string;
|
|
1584
|
-
readonly name: string;
|
|
1585
|
-
readonly stringKeys: readonly TStringKeys[];
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
interface ComponentRegistration<TStringKeys extends string, TLanguages extends string> {
|
|
1589
|
-
readonly component: ComponentDefinition<TStringKeys>;
|
|
1590
|
-
readonly strings: PartialComponentLanguageStrings<TStringKeys, TLanguages>;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
interface LanguageDefinition {
|
|
1594
|
-
readonly id: string;
|
|
1595
|
-
readonly name: string;
|
|
1596
|
-
readonly code: string;
|
|
1597
|
-
readonly isDefault?: boolean;
|
|
1598
|
-
}
|
|
1599
|
-
```
|
|
1600
|
-
|
|
1601
|
-
### Legacy I18nEngine (Still Supported)
|
|
1602
|
-
|
|
1603
|
-
#### Constructor
|
|
1604
|
-
|
|
1605
|
-
- `new I18nEngine<TStringKey, TLanguage>(config, key?)` - Create new engine instance
|
|
1606
|
-
|
|
1607
|
-
#### Translation Methods
|
|
1608
|
-
|
|
1609
|
-
- `translate(key, vars?, language?, fallbackLanguage?)` - Translate string with optional variables
|
|
1610
|
-
- `translateEnum(enumObj, value, language)` - Translate enum value
|
|
1611
|
-
- `t(templateString, language?, ...vars)` - Process template string with `{{EnumName.EnumKey}}` patterns
|
|
1612
|
-
|
|
1613
|
-
#### Registration
|
|
1614
|
-
|
|
1615
|
-
- `registerEnum(enumObj, translations, enumName)` - Register enum translations
|
|
1616
|
-
|
|
1617
|
-
#### Language Management
|
|
1618
|
-
|
|
1619
|
-
- `getLanguageCode(language)` - Get language code for language
|
|
1620
|
-
- `getLanguageFromCode(code)` - Get language from code
|
|
1621
|
-
- `getAllLanguageCodes()` - Get all language codes
|
|
1622
|
-
- `getAvailableLanguages()` - Get available languages
|
|
1623
|
-
- `isLanguageAvailable(language)` - Check if language is available
|
|
1624
|
-
|
|
1625
|
-
#### Context Management
|
|
1626
|
-
|
|
1627
|
-
- `get context()` - Get current context
|
|
1628
|
-
- `set context(context)` - Set context properties
|
|
1629
|
-
|
|
1630
|
-
#### Static Methods
|
|
1631
|
-
|
|
1632
|
-
- `getInstance(key?)` - Get instance by key
|
|
1633
|
-
- `clearInstances()` - Clear all instances
|
|
1634
|
-
- `removeInstance(key?)` - Remove specific instance
|
|
1635
|
-
|
|
1636
|
-
### Context Utilities
|
|
1637
|
-
|
|
1638
|
-
- `createContext(defaultLanguage, defaultContext)` - Create new context
|
|
1639
|
-
- `setLanguage(context, language)` - Set user language
|
|
1640
|
-
- `setAdminLanguage(context, language)` - Set admin language
|
|
1641
|
-
- `setContext(context, contextType)` - Set context type
|
|
1642
|
-
|
|
1643
|
-
### ContextManager
|
|
1644
|
-
|
|
1645
|
-
- `addListener(listener)` - Add change listener
|
|
1646
|
-
- `removeListener(listener)` - Remove change listener
|
|
1647
|
-
- `createProxy(context)` - Create reactive context proxy
|
|
1648
|
-
|
|
1649
|
-
### Currency Utilities
|
|
1650
|
-
|
|
1651
|
-
- `getCurrencyFormat(locale, currencyCode)` - Get currency formatting info
|
|
1652
|
-
|
|
1653
|
-
### Type Utilities
|
|
1654
|
-
|
|
1655
|
-
- `createTranslations(translations)` - Helper for creating typed translations
|
|
1656
|
-
|
|
1657
|
-
## Type Definitions
|
|
1658
|
-
|
|
1659
|
-
### Core Types
|
|
1660
|
-
|
|
1661
|
-
```typescript
|
|
1662
|
-
type EnumTranslation<T extends string | number> = {
|
|
1663
|
-
[K in T]: string;
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
|
-
type EnumLanguageTranslation<T extends string | number, TLanguage extends string> = Partial<{
|
|
1667
|
-
[L in TLanguage]: EnumTranslation<T>;
|
|
1668
|
-
}>;
|
|
1669
|
-
|
|
1670
|
-
interface I18nConfig<TStringKey, TLanguage, TConstants?, TTranslationContext?> {
|
|
1671
|
-
stringNames: TStringKey[];
|
|
1672
|
-
strings: MasterStringsCollection<TStringKey, TLanguage>;
|
|
1673
|
-
defaultLanguage: TLanguage;
|
|
1674
|
-
defaultTranslationContext: TTranslationContext;
|
|
1675
|
-
defaultCurrencyCode: CurrencyCode;
|
|
1676
|
-
languageCodes: LanguageCodeCollection<TLanguage>;
|
|
1677
|
-
languages: TLanguage[];
|
|
1678
|
-
constants?: TConstants;
|
|
1679
|
-
enumName?: string;
|
|
1680
|
-
enumObj?: Record<string, TStringKey>;
|
|
1681
|
-
timezone: Timezone;
|
|
1682
|
-
adminTimezone: Timezone;
|
|
1683
|
-
}
|
|
1684
|
-
```
|
|
1685
|
-
|
|
1686
|
-
## Testing
|
|
1687
|
-
|
|
1688
|
-
The library includes comprehensive test coverage:
|
|
1689
|
-
|
|
1690
|
-
```bash
|
|
1691
|
-
# Run tests
|
|
1692
|
-
yarn test
|
|
1693
|
-
|
|
1694
|
-
# Run specific test suites
|
|
1695
|
-
yarn test context-manager.spec.ts
|
|
1696
|
-
yarn test enum-registry.spec.ts
|
|
1697
|
-
yarn test i18n-engine.spec.ts
|
|
1698
|
-
```
|
|
1699
|
-
|
|
1700
|
-
### Test Cleanup and Instance Management
|
|
1701
|
-
|
|
1702
|
-
For proper test isolation when using the plugin-based architecture, use the cleanup utilities:
|
|
1703
|
-
|
|
1704
|
-
```typescript
|
|
1705
|
-
import { PluginI18nEngine, resetAllI18nEngines } from '@digitaldefiance/i18n-lib';
|
|
1706
|
-
|
|
1707
|
-
describe('My tests', () => {
|
|
1708
|
-
beforeEach(() => {
|
|
1709
|
-
// Clean up any existing instances before each test
|
|
1710
|
-
PluginI18nEngine.clearAllInstances();
|
|
1711
|
-
});
|
|
1712
|
-
|
|
1713
|
-
afterEach(() => {
|
|
1714
|
-
// Or use the convenience function
|
|
1715
|
-
resetAllI18nEngines();
|
|
1716
|
-
});
|
|
1717
|
-
|
|
1718
|
-
// Or use specific cleanup methods
|
|
1719
|
-
it('should manage instances', () => {
|
|
1720
|
-
const engine1 = PluginI18nEngine.createInstance('app1', [englishLang]);
|
|
1721
|
-
const engine2 = PluginI18nEngine.createInstance('app2', [frenchLang]);
|
|
5
|
+
## Features
|
|
1722
6
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
7
|
+
- **Component-Based Architecture**: Register translation components with full type safety
|
|
8
|
+
- **8 Built-in Languages**: English (US/UK), French, Spanish, German, Chinese, Japanese, Ukrainian
|
|
9
|
+
- **Template Processing**: Support for variable substitution and component references
|
|
10
|
+
- **Multiple Instances**: Create isolated i18n engines for different contexts
|
|
11
|
+
- **Core System Strings**: Pre-built translations for common UI elements and errors
|
|
12
|
+
- **Type Safety**: Full TypeScript support with generic types
|
|
13
|
+
- **Error Handling**: Comprehensive error classes with translation support
|
|
14
|
+
- **Zero Dependencies**: Lightweight with no external runtime dependencies
|
|
1726
15
|
|
|
1727
|
-
|
|
1728
|
-
PluginI18nEngine.removeInstance('app1');
|
|
1729
|
-
expect(PluginI18nEngine.hasInstance('app1')).toBe(false);
|
|
16
|
+
## Installation
|
|
1730
17
|
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
});
|
|
18
|
+
```bash
|
|
19
|
+
npm install @digitaldefiance/i18n-lib
|
|
20
|
+
# or
|
|
21
|
+
yarn add @digitaldefiance/i18n-lib
|
|
1736
22
|
```
|
|
1737
23
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
- `PluginI18nEngine.clearAllInstances()` - Remove all engine instances
|
|
1741
|
-
- `PluginI18nEngine.removeInstance(key?)` - Remove specific instance by key (returns boolean)
|
|
1742
|
-
- `PluginI18nEngine.hasInstance(key?)` - Check if instance exists (returns boolean)
|
|
1743
|
-
- `PluginI18nEngine.getInstance(key?)` - Get existing instance (throws RegistryError if not found)
|
|
1744
|
-
- `PluginI18nEngine.resetAll()` - Clear instances and component registrations
|
|
1745
|
-
- `resetAllI18nEngines()` - Convenience function that calls `resetAll()`
|
|
1746
|
-
|
|
1747
|
-
## Extensible Configuration
|
|
1748
|
-
|
|
1749
|
-
The library supports layered extension across multiple libraries using TypeScript module augmentation:
|
|
1750
|
-
|
|
1751
|
-
### Base Library Setup
|
|
24
|
+
## Quick Start
|
|
1752
25
|
|
|
1753
26
|
```typescript
|
|
1754
|
-
|
|
1755
|
-
import { DefaultStringKey, DefaultLanguage } from '@digitaldefiance/i18n-lib';
|
|
27
|
+
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1756
28
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
}
|
|
29
|
+
// Create engine with languages
|
|
30
|
+
const engine = PluginI18nEngine.createInstance('myapp', [
|
|
31
|
+
{ id: LanguageCodes.EN_US, name: 'English (US)', code: 'en-US', isDefault: true },
|
|
32
|
+
{ id: LanguageCodes.FR, name: 'Français', code: 'fr' }
|
|
33
|
+
]);
|
|
1761
34
|
|
|
1762
|
-
//
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
35
|
+
// Register component with translations
|
|
36
|
+
engine.registerComponent({
|
|
37
|
+
component: {
|
|
38
|
+
id: 'app',
|
|
39
|
+
name: 'Application',
|
|
40
|
+
stringKeys: ['welcome', 'goodbye']
|
|
41
|
+
},
|
|
42
|
+
strings: {
|
|
43
|
+
[LanguageCodes.EN_US]: {
|
|
44
|
+
welcome: 'Welcome to {appName}!',
|
|
45
|
+
goodbye: 'Goodbye!'
|
|
46
|
+
},
|
|
47
|
+
[LanguageCodes.FR]: {
|
|
48
|
+
welcome: 'Bienvenue sur {appName}!',
|
|
49
|
+
goodbye: 'Au revoir!'
|
|
1767
50
|
}
|
|
1768
51
|
}
|
|
1769
|
-
}
|
|
1770
|
-
```
|
|
1771
|
-
|
|
1772
|
-
### Extending Another Library
|
|
1773
|
-
|
|
1774
|
-
```typescript
|
|
1775
|
-
// In a library that extends the base library
|
|
1776
|
-
import { DefaultStringKey } from '@digitaldefiance/i18n-lib';
|
|
1777
|
-
import { MyLibStringKey } from 'my-base-lib';
|
|
52
|
+
});
|
|
1778
53
|
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
54
|
+
// Translate
|
|
55
|
+
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
|
|
56
|
+
// Output: "Welcome to MyApp!"
|
|
1783
57
|
|
|
1784
|
-
//
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
StringKey: DefaultStringKey | MyLibStringKey | AdvancedStringKey;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
58
|
+
// Switch language
|
|
59
|
+
engine.setLanguage(LanguageCodes.FR);
|
|
60
|
+
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
|
|
61
|
+
// Output: "Bienvenue sur MyApp!"
|
|
1792
62
|
```
|
|
1793
63
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
```typescript
|
|
1797
|
-
// In your final application
|
|
1798
|
-
import { StringKey, Language, getI18nEngine } from 'my-extended-lib';
|
|
1799
|
-
|
|
1800
|
-
// All string keys from all layers are now available
|
|
1801
|
-
const engine = getI18nEngine();
|
|
1802
|
-
const translation = engine.translate('advanced_export' as StringKey);
|
|
1803
|
-
```
|
|
64
|
+
## Core Concepts
|
|
1804
65
|
|
|
1805
|
-
###
|
|
66
|
+
### PluginI18nEngine
|
|
1806
67
|
|
|
1807
|
-
|
|
68
|
+
The main engine class that manages translations, languages, and components.
|
|
1808
69
|
|
|
1809
70
|
```typescript
|
|
1810
|
-
import {
|
|
1811
|
-
|
|
1812
|
-
// Create engine with default configuration
|
|
1813
|
-
const engine = getDefaultI18nEngine(
|
|
1814
|
-
{ APP_NAME: 'MyApp' }, // constants
|
|
1815
|
-
new Timezone('America/New_York'), // user timezone
|
|
1816
|
-
new Timezone('UTC') // admin timezone
|
|
1817
|
-
);
|
|
1818
|
-
```
|
|
1819
|
-
|
|
1820
|
-
## Configuration Validation
|
|
71
|
+
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1821
72
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
### Validation Rules
|
|
73
|
+
// Create instance
|
|
74
|
+
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
1825
75
|
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
76
|
+
// Or use constructor
|
|
77
|
+
const engine = new PluginI18nEngine(languages, config);
|
|
78
|
+
```
|
|
1829
79
|
|
|
1830
|
-
###
|
|
80
|
+
### Component Registration
|
|
1831
81
|
|
|
1832
|
-
|
|
82
|
+
Components group related translations together:
|
|
1833
83
|
|
|
1834
84
|
```typescript
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
const config: I18nConfig<MyStrings, MyLanguages> = {
|
|
1844
|
-
stringNames: Object.values(MyStrings),
|
|
85
|
+
engine.registerComponent({
|
|
86
|
+
component: {
|
|
87
|
+
id: 'auth',
|
|
88
|
+
name: 'Authentication',
|
|
89
|
+
stringKeys: ['login', 'logout', 'error']
|
|
90
|
+
},
|
|
1845
91
|
strings: {
|
|
1846
|
-
[
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
92
|
+
[LanguageCodes.EN_US]: {
|
|
93
|
+
login: 'Login',
|
|
94
|
+
logout: 'Logout',
|
|
95
|
+
error: 'Authentication failed'
|
|
96
|
+
},
|
|
97
|
+
[LanguageCodes.FR]: {
|
|
98
|
+
login: 'Connexion',
|
|
99
|
+
logout: 'Déconnexion',
|
|
100
|
+
error: 'Échec de l\'authentification'
|
|
1851
101
|
}
|
|
1852
102
|
},
|
|
1853
|
-
|
|
1854
|
-
};
|
|
103
|
+
aliases: ['authentication'] // Optional aliases
|
|
104
|
+
});
|
|
1855
105
|
```
|
|
1856
106
|
|
|
1857
|
-
###
|
|
1858
|
-
|
|
1859
|
-
If localized error messages aren't provided, the engine falls back to English templates:
|
|
1860
|
-
|
|
1861
|
-
- `Missing string collection for language: {language}`
|
|
1862
|
-
- `Missing translation for key '{key}' in language '{language}'`
|
|
1863
|
-
- `Default language '{language}' has no string collection`
|
|
1864
|
-
|
|
1865
|
-
## Error Handling
|
|
1866
|
-
|
|
1867
|
-
The library provides comprehensive error handling with localized error messages:
|
|
1868
|
-
|
|
1869
|
-
### Registry Errors
|
|
107
|
+
### Translation
|
|
1870
108
|
|
|
1871
109
|
```typescript
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
try {
|
|
1875
|
-
engine.registerComponent(invalidRegistration);
|
|
1876
|
-
} catch (error) {
|
|
1877
|
-
if (error instanceof RegistryError) {
|
|
1878
|
-
console.error(`Error type: ${error.type}`);
|
|
1879
|
-
console.error(`Metadata:`, error.metadata);
|
|
1880
|
-
|
|
1881
|
-
switch (error.type) {
|
|
1882
|
-
case RegistryErrorType.ComponentNotFound:
|
|
1883
|
-
// Handle missing component
|
|
1884
|
-
break;
|
|
1885
|
-
case RegistryErrorType.DuplicateComponent:
|
|
1886
|
-
// Handle duplicate registration
|
|
1887
|
-
break;
|
|
1888
|
-
case RegistryErrorType.ValidationFailed:
|
|
1889
|
-
// Handle validation failure
|
|
1890
|
-
break;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
```
|
|
1895
|
-
|
|
1896
|
-
**Error Types:**
|
|
110
|
+
// Simple translation
|
|
111
|
+
const text = engine.translate('auth', 'login');
|
|
1897
112
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
- `StringKeyNotFound`: Translation key not found
|
|
1901
|
-
- `IncompleteRegistration`: Missing translations detected
|
|
1902
|
-
- `DuplicateComponent`: Component already registered
|
|
1903
|
-
- `DuplicateLanguage`: Language already registered
|
|
1904
|
-
- `ValidationFailed`: Validation check failed
|
|
113
|
+
// With variables
|
|
114
|
+
const greeting = engine.translate('app', 'welcome', { name: 'John' });
|
|
1905
115
|
|
|
1906
|
-
|
|
116
|
+
// Specific language
|
|
117
|
+
const french = engine.translate('auth', 'login', {}, LanguageCodes.FR);
|
|
1907
118
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
try {
|
|
1912
|
-
const context = globalContext.getContext('invalid-key');
|
|
1913
|
-
} catch (error) {
|
|
1914
|
-
if (error instanceof ContextError) {
|
|
1915
|
-
console.error(`Invalid context: ${error.contextKey}`);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
119
|
+
// Safe translation (returns fallback on error)
|
|
120
|
+
const safe = engine.safeTranslate('missing', 'key'); // Returns "[missing.key]"
|
|
1918
121
|
```
|
|
1919
122
|
|
|
1920
|
-
###
|
|
1921
|
-
|
|
1922
|
-
Use `safeTranslate` to prevent errors:
|
|
123
|
+
### Template Processing
|
|
1923
124
|
|
|
1924
125
|
```typescript
|
|
1925
|
-
//
|
|
1926
|
-
|
|
1927
|
-
const text = engine.translate('component', 'missingKey');
|
|
1928
|
-
} catch (error) {
|
|
1929
|
-
// Handle error
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
// Safe translate returns placeholder
|
|
1933
|
-
const text = engine.safeTranslate('component', 'missingKey');
|
|
1934
|
-
// Returns: "[component.missingKey]"
|
|
1935
|
-
```
|
|
1936
|
-
|
|
1937
|
-
## Validation
|
|
126
|
+
// Component references: {{componentId.stringKey}}
|
|
127
|
+
engine.t('Click {{auth.login}} to continue');
|
|
1938
128
|
|
|
1939
|
-
|
|
129
|
+
// Variables: {variableName}
|
|
130
|
+
engine.t('Hello, {username}!', { username: 'Alice' });
|
|
1940
131
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const result = engine.registerComponent(registration);
|
|
1944
|
-
|
|
1945
|
-
if (!result.isValid) {
|
|
1946
|
-
console.error('Validation errors:', result.errors);
|
|
1947
|
-
console.warn('Missing keys:', result.missingKeys);
|
|
1948
|
-
|
|
1949
|
-
result.missingKeys.forEach(missing => {
|
|
1950
|
-
console.log(`Missing: ${missing.stringKey} for ${missing.languageId} in ${missing.componentId}`);
|
|
1951
|
-
});
|
|
1952
|
-
}
|
|
132
|
+
// Mixed
|
|
133
|
+
engine.t('{{auth.login}}: {username}', { username: 'admin' });
|
|
1953
134
|
```
|
|
1954
135
|
|
|
1955
|
-
###
|
|
136
|
+
### Language Management
|
|
1956
137
|
|
|
1957
138
|
```typescript
|
|
1958
|
-
//
|
|
1959
|
-
|
|
139
|
+
// Set current language
|
|
140
|
+
engine.setLanguage(LanguageCodes.FR);
|
|
141
|
+
|
|
142
|
+
// Get current language
|
|
143
|
+
const lang = engine.getCurrentLanguage();
|
|
1960
144
|
|
|
1961
|
-
if
|
|
1962
|
-
|
|
1963
|
-
|
|
145
|
+
// Check if language exists
|
|
146
|
+
if (engine.hasLanguage(LanguageCodes.ES)) {
|
|
147
|
+
engine.setLanguage(LanguageCodes.ES);
|
|
1964
148
|
}
|
|
1965
149
|
|
|
1966
|
-
//
|
|
1967
|
-
|
|
1968
|
-
// Warnings: ["Component 'user' missing key 'greeting' for language 'fr'"]
|
|
150
|
+
// Get all languages
|
|
151
|
+
const languages = engine.getLanguages();
|
|
1969
152
|
```
|
|
1970
153
|
|
|
1971
|
-
###
|
|
154
|
+
### Admin Context
|
|
1972
155
|
|
|
1973
|
-
|
|
1974
|
-
import { ValidationConfig } from '@digitaldefiance/i18n-lib';
|
|
156
|
+
Separate language for admin interfaces:
|
|
1975
157
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
fallbackLanguageId: 'en-US'
|
|
1980
|
-
};
|
|
158
|
+
```typescript
|
|
159
|
+
// Set admin language
|
|
160
|
+
engine.setAdminLanguage(LanguageCodes.EN_US);
|
|
1981
161
|
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
allowPartialRegistration: true,
|
|
1985
|
-
fallbackLanguageId: 'en-US'
|
|
1986
|
-
};
|
|
162
|
+
// Switch to admin context
|
|
163
|
+
engine.switchToAdmin();
|
|
1987
164
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
});
|
|
165
|
+
// Switch back to user context
|
|
166
|
+
engine.switchToUser();
|
|
1991
167
|
```
|
|
1992
168
|
|
|
1993
|
-
##
|
|
1994
|
-
|
|
1995
|
-
### Plugin Architecture (Recommended for New Projects)
|
|
169
|
+
## Core System Strings
|
|
1996
170
|
|
|
1997
|
-
|
|
1998
|
-
2. **Define String Key Enums**: Always use TypeScript enums for string keys to ensure compile-time type safety
|
|
1999
|
-
3. **Complete Component Translations**: Provide translations for all supported languages in each component
|
|
2000
|
-
4. **Validate Registrations**: Check validation results and handle missing translations appropriately
|
|
2001
|
-
5. **Use Core Components**: Start with `createCoreI18nEngine()` for built-in system strings
|
|
2002
|
-
6. **Fallback Strategy**: Configure appropriate fallback languages for missing translations
|
|
2003
|
-
7. **Component Isolation**: Keep related strings together in the same component
|
|
2004
|
-
8. **Template Variables**: Use template strings with variables for dynamic content
|
|
2005
|
-
9. **Multi-Instance Architecture**: Use named instances for different application contexts (admin, user, etc.)
|
|
171
|
+
Pre-built translations for common UI elements:
|
|
2006
172
|
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
1. **Complete Translations**: EnumTranslation requires all enum values to be translated
|
|
2010
|
-
2. **Type Safety**: Use TypeScript enums for string keys and languages
|
|
2011
|
-
3. **Context Separation**: Use different contexts for admin and user interfaces
|
|
2012
|
-
4. **Instance Management**: Use named instances for different parts of your application
|
|
2013
|
-
5. **Error Handling**: Handle missing translations gracefully with fallback languages
|
|
2014
|
-
6. **Layered Extension**: Use union types when extending configurations across libraries
|
|
2015
|
-
7. **Default Configuration**: Use `getDefaultI18nEngine()` for standard setups
|
|
2016
|
-
|
|
2017
|
-
### Migration Strategy
|
|
173
|
+
```typescript
|
|
174
|
+
import { getCoreI18nEngine, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
|
|
2018
175
|
|
|
2019
|
-
|
|
176
|
+
const coreEngine = getCoreI18nEngine();
|
|
2020
177
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
const
|
|
2024
|
-
const text = legacy.translate(MyStrings.Welcome);
|
|
2025
|
-
|
|
2026
|
-
// New plugin approach
|
|
2027
|
-
const modern = createCoreI18nEngine();
|
|
2028
|
-
modern.registerComponent(myComponentRegistration);
|
|
2029
|
-
const text = modern.translate('my-component', MyStrings.Welcome);
|
|
178
|
+
// Use core strings
|
|
179
|
+
const yes = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Common_Yes);
|
|
180
|
+
const error = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Error_NotFound);
|
|
2030
181
|
```
|
|
2031
182
|
|
|
2032
|
-
|
|
183
|
+
Available core string categories:
|
|
2033
184
|
|
|
2034
|
-
|
|
185
|
+
- **Common**: Yes, No, Cancel, OK, Save, Delete, Edit, Create, Update, Loading, etc.
|
|
186
|
+
- **Errors**: InvalidInput, NetworkError, NotFound, AccessDenied, ValidationFailed, etc.
|
|
187
|
+
- **System**: Welcome, Goodbye, PleaseWait, ProcessingRequest, OperationComplete, etc.
|
|
2035
188
|
|
|
2036
|
-
|
|
189
|
+
## Multiple Instances
|
|
2037
190
|
|
|
2038
|
-
|
|
2039
|
-
- Reuse instances rather than creating new ones
|
|
2040
|
-
- Clean up instances in tests to prevent memory leaks
|
|
191
|
+
Create isolated engines for different parts of your application:
|
|
2041
192
|
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
- Component strings are validated once during registration
|
|
2046
|
-
- Fallback translations are generated during registration, not at runtime
|
|
193
|
+
```typescript
|
|
194
|
+
// Admin engine
|
|
195
|
+
const adminEngine = PluginI18nEngine.createInstance('admin', adminLanguages);
|
|
2047
196
|
|
|
2048
|
-
|
|
197
|
+
// User engine
|
|
198
|
+
const userEngine = PluginI18nEngine.createInstance('user', userLanguages);
|
|
2049
199
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
- Validation runs only during registration, not translation
|
|
200
|
+
// Get instance by key
|
|
201
|
+
const admin = PluginI18nEngine.getInstance('admin');
|
|
2053
202
|
|
|
2054
|
-
|
|
203
|
+
// Check if instance exists
|
|
204
|
+
if (PluginI18nEngine.hasInstance('admin')) {
|
|
205
|
+
// ...
|
|
206
|
+
}
|
|
2055
207
|
|
|
2056
|
-
|
|
208
|
+
// Remove instance
|
|
209
|
+
PluginI18nEngine.removeInstance('admin');
|
|
2057
210
|
|
|
2058
|
-
|
|
211
|
+
// Reset all instances
|
|
212
|
+
PluginI18nEngine.resetAll();
|
|
213
|
+
```
|
|
2059
214
|
|
|
2060
|
-
|
|
2061
|
-
- All translations are stored locally
|
|
2062
|
-
- No telemetry or analytics
|
|
2063
|
-
- Suitable for sensitive applications
|
|
215
|
+
## Error Handling
|
|
2064
216
|
|
|
2065
|
-
###
|
|
217
|
+
### RegistryError
|
|
2066
218
|
|
|
2067
|
-
|
|
219
|
+
Errors related to component/language registration:
|
|
2068
220
|
|
|
2069
221
|
```typescript
|
|
2070
|
-
import {
|
|
2071
|
-
|
|
2072
|
-
// BAD: Direct user input
|
|
2073
|
-
const message = engine.translate('component', 'template', {
|
|
2074
|
-
userInput: req.body.unsafeInput
|
|
2075
|
-
});
|
|
222
|
+
import { RegistryError, RegistryErrorType } from '@digitaldefiance/i18n-lib';
|
|
2076
223
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
224
|
+
try {
|
|
225
|
+
engine.translate('missing', 'key');
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (error instanceof RegistryError) {
|
|
228
|
+
console.log(error.type); // RegistryErrorType.COMPONENT_NOT_FOUND
|
|
229
|
+
console.log(error.message); // "Component 'missing' not found"
|
|
230
|
+
console.log(error.metadata); // { componentId: 'missing' }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
2081
233
|
```
|
|
2082
234
|
|
|
2083
|
-
###
|
|
235
|
+
### TranslatableError
|
|
2084
236
|
|
|
2085
|
-
|
|
237
|
+
Base class for errors with translated messages:
|
|
2086
238
|
|
|
2087
239
|
```typescript
|
|
2088
|
-
|
|
2089
|
-
throw new TranslatableGenericError(
|
|
2090
|
-
'auth',
|
|
2091
|
-
'loginFailed',
|
|
2092
|
-
{ password: user.password }, // Don't include sensitive data
|
|
2093
|
-
language
|
|
2094
|
-
);
|
|
2095
|
-
|
|
2096
|
-
// GOOD: Safe error message
|
|
2097
|
-
throw new TranslatableGenericError(
|
|
2098
|
-
'auth',
|
|
2099
|
-
'loginFailed',
|
|
2100
|
-
{ username: user.username },
|
|
2101
|
-
language,
|
|
2102
|
-
{ userId: user.id } // Metadata for logging only
|
|
2103
|
-
);
|
|
2104
|
-
```
|
|
240
|
+
import { TranslatableError, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
|
|
2105
241
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
// Get core language codes from helper (single source of truth)
|
|
2115
|
-
const supportedLanguages = getCoreLanguageCodes();
|
|
2116
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
2117
|
-
|
|
2118
|
-
const userSchema = new Schema({
|
|
2119
|
-
language: {
|
|
2120
|
-
type: String,
|
|
2121
|
-
enum: supportedLanguages,
|
|
2122
|
-
default: LanguageCodes.EN_US,
|
|
2123
|
-
required: true
|
|
2124
|
-
},
|
|
2125
|
-
adminLanguage: {
|
|
2126
|
-
type: String,
|
|
2127
|
-
enum: supportedLanguages,
|
|
2128
|
-
default: LanguageCodes.EN_US
|
|
242
|
+
class MyError extends TranslatableError {
|
|
243
|
+
constructor(language?: string) {
|
|
244
|
+
super(
|
|
245
|
+
CoreI18nComponentId,
|
|
246
|
+
CoreStringKey.Error_AccessDenied,
|
|
247
|
+
{},
|
|
248
|
+
language
|
|
249
|
+
);
|
|
2129
250
|
}
|
|
2130
|
-
}
|
|
251
|
+
}
|
|
2131
252
|
|
|
2132
|
-
|
|
253
|
+
throw new MyError(LanguageCodes.FR); // Throws with French error message
|
|
2133
254
|
```
|
|
2134
255
|
|
|
2135
|
-
|
|
256
|
+
## Translation Adapter
|
|
257
|
+
|
|
258
|
+
Adapt PluginI18nEngine to simpler TranslationEngine interface:
|
|
2136
259
|
|
|
2137
260
|
```typescript
|
|
2138
|
-
import {
|
|
2139
|
-
|
|
2140
|
-
// Ensure the engine has registered its languages
|
|
2141
|
-
const engine = PluginI18nEngine.getInstance();
|
|
2142
|
-
const languageEnum = LanguageRegistry.getLanguageIds();
|
|
2143
|
-
const defaultLanguage =
|
|
2144
|
-
LanguageRegistry.getDefaultLanguageId() ?? engine.getContext().language;
|
|
2145
|
-
|
|
2146
|
-
const contentSchema = new Schema({
|
|
2147
|
-
language: {
|
|
2148
|
-
type: String,
|
|
2149
|
-
enum: languageEnum,
|
|
2150
|
-
default: defaultLanguage
|
|
2151
|
-
}
|
|
2152
|
-
});
|
|
2153
|
-
```
|
|
261
|
+
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
2154
262
|
|
|
2155
|
-
|
|
263
|
+
const adapter = createTranslationAdapter(engine, 'componentId');
|
|
2156
264
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
const displayNames = LanguageRegistry.getLanguageDisplayNames();
|
|
2161
|
-
|
|
2162
|
-
const settingsSchema = new Schema({
|
|
2163
|
-
language: {
|
|
2164
|
-
type: String,
|
|
2165
|
-
enum: Object.keys(displayNames),
|
|
2166
|
-
validate: {
|
|
2167
|
-
validator: (v: string) => v in displayNames,
|
|
2168
|
-
message: (props) => {
|
|
2169
|
-
const validLanguages = Object.entries(displayNames)
|
|
2170
|
-
.map(([code, name]) => `${name} (${code})`)
|
|
2171
|
-
.join(', ');
|
|
2172
|
-
return `${props.value} is not a valid language. Valid options: ${validLanguages}`;
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
});
|
|
265
|
+
// Use adapter where TranslationEngine is expected
|
|
266
|
+
const message = adapter.translate('key', { var: 'value' });
|
|
2177
267
|
```
|
|
2178
268
|
|
|
2179
|
-
##
|
|
269
|
+
## Language Codes
|
|
2180
270
|
|
|
2181
|
-
|
|
271
|
+
Built-in language codes following BCP 47 standard:
|
|
2182
272
|
|
|
2183
273
|
```typescript
|
|
2184
|
-
import {
|
|
2185
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2186
|
-
|
|
2187
|
-
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2188
|
-
|
|
2189
|
-
function i18nMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
2190
|
-
// Get language from header, query, or cookie
|
|
2191
|
-
const language = (req.headers['accept-language'] ||
|
|
2192
|
-
req.query.lang ||
|
|
2193
|
-
req.cookies.language ||
|
|
2194
|
-
'en-US') as CoreLanguageCode;
|
|
2195
|
-
|
|
2196
|
-
// Set language for this request (type-safe)
|
|
2197
|
-
engine.setLanguage(language);
|
|
2198
|
-
|
|
2199
|
-
// Add translation helper to response locals
|
|
2200
|
-
res.locals.t = (componentId: string, key: string, vars?: any) => {
|
|
2201
|
-
return engine.translate(componentId, key, vars, language);
|
|
2202
|
-
};
|
|
2203
|
-
|
|
2204
|
-
next();
|
|
2205
|
-
}
|
|
274
|
+
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
2206
275
|
|
|
2207
|
-
|
|
276
|
+
LanguageCodes.EN_US // 'en-US'
|
|
277
|
+
LanguageCodes.EN_GB // 'en-GB'
|
|
278
|
+
LanguageCodes.FR // 'fr'
|
|
279
|
+
LanguageCodes.ES // 'es'
|
|
280
|
+
LanguageCodes.DE // 'de'
|
|
281
|
+
LanguageCodes.ZH_CN // 'zh-CN'
|
|
282
|
+
LanguageCodes.JA // 'ja'
|
|
283
|
+
LanguageCodes.UK // 'uk'
|
|
2208
284
|
```
|
|
2209
285
|
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
```typescript
|
|
2213
|
-
import React, { createContext, useContext, useState } from 'react';
|
|
2214
|
-
import { PluginI18nEngine, CoreLanguageCode, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
2215
|
-
|
|
2216
|
-
const I18nContext = createContext<{
|
|
2217
|
-
engine: PluginI18nEngine<CoreLanguageCode>;
|
|
2218
|
-
language: CoreLanguageCode;
|
|
2219
|
-
setLanguage: (lang: CoreLanguageCode) => void;
|
|
2220
|
-
} | null>(null);
|
|
2221
|
-
|
|
2222
|
-
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
|
2223
|
-
const [engine] = useState(() => PluginI18nEngine.getInstance<CoreLanguageCode>());
|
|
2224
|
-
const [language, setLanguageState] = useState<CoreLanguageCode>(LanguageCodes.EN_US);
|
|
2225
|
-
|
|
2226
|
-
const setLanguage = (lang: CoreLanguageCode) => {
|
|
2227
|
-
engine.setLanguage(lang);
|
|
2228
|
-
setLanguageState(lang);
|
|
2229
|
-
};
|
|
2230
|
-
|
|
2231
|
-
return (
|
|
2232
|
-
<I18nContext.Provider value={{ engine, language, setLanguage }}>
|
|
2233
|
-
{children}
|
|
2234
|
-
</I18nContext.Provider>
|
|
2235
|
-
);
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
export function useI18n() {
|
|
2239
|
-
const context = useContext(I18nContext);
|
|
2240
|
-
if (!context) throw new Error('useI18n must be used within I18nProvider');
|
|
2241
|
-
|
|
2242
|
-
const t = (componentId: string, key: string, vars?: any) => {
|
|
2243
|
-
return context.engine.translate(componentId, key, vars, context.language);
|
|
2244
|
-
};
|
|
2245
|
-
|
|
2246
|
-
return { ...context, t };
|
|
2247
|
-
}
|
|
286
|
+
## API Reference
|
|
2248
287
|
|
|
2249
|
-
|
|
2250
|
-
function MyComponent() {
|
|
2251
|
-
const { t, language, setLanguage } = useI18n();
|
|
2252
|
-
|
|
2253
|
-
return (
|
|
2254
|
-
<div>
|
|
2255
|
-
<h1>{t('core', CoreStringKey.System_Welcome)}</h1>
|
|
2256
|
-
<button onClick={() => setLanguage('fr')}>
|
|
2257
|
-
Switch to French
|
|
2258
|
-
</button>
|
|
2259
|
-
</div>
|
|
2260
|
-
);
|
|
2261
|
-
}
|
|
2262
|
-
```
|
|
288
|
+
### PluginI18nEngine
|
|
2263
289
|
|
|
2264
|
-
|
|
290
|
+
**Static Methods**
|
|
2265
291
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
install(app, options) {
|
|
2272
|
-
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2273
|
-
|
|
2274
|
-
app.config.globalProperties.$t = (
|
|
2275
|
-
componentId: string,
|
|
2276
|
-
key: string,
|
|
2277
|
-
vars?: any
|
|
2278
|
-
) => {
|
|
2279
|
-
return engine.translate(componentId, key, vars);
|
|
2280
|
-
};
|
|
2281
|
-
|
|
2282
|
-
app.config.globalProperties.$i18n = engine;
|
|
2283
|
-
}
|
|
2284
|
-
};
|
|
292
|
+
- `createInstance<TLanguage>(key: string, languages: LanguageDefinition[], config?: RegistryConfig)` - Create named instance
|
|
293
|
+
- `getInstance<TLanguage>(key?: string)` - Get instance by key
|
|
294
|
+
- `hasInstance(key?: string)` - Check if instance exists
|
|
295
|
+
- `removeInstance(key?: string)` - Remove instance
|
|
296
|
+
- `resetAll()` - Reset all instances
|
|
2285
297
|
|
|
2286
|
-
|
|
298
|
+
**Instance Methods**
|
|
2287
299
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
300
|
+
- `registerComponent(registration: ComponentRegistration)` - Register component
|
|
301
|
+
- `translate(componentId: string, key: string, variables?, language?)` - Translate string
|
|
302
|
+
- `safeTranslate(componentId: string, key: string, variables?, language?)` - Safe translate with fallback
|
|
303
|
+
- `t(template: string, variables?, language?)` - Process template string
|
|
304
|
+
- `setLanguage(language: TLanguage)` - Set current language
|
|
305
|
+
- `setAdminLanguage(language: TLanguage)` - Set admin language
|
|
306
|
+
- `getCurrentLanguage()` - Get current language
|
|
307
|
+
- `getLanguages()` - Get all languages
|
|
308
|
+
- `hasLanguage(language: TLanguage)` - Check if language exists
|
|
309
|
+
- `switchToAdmin()` - Switch to admin context
|
|
310
|
+
- `switchToUser()` - Switch to user context
|
|
311
|
+
- `validate()` - Validate all components
|
|
2293
312
|
|
|
2294
|
-
|
|
313
|
+
### Core Functions
|
|
2295
314
|
|
|
2296
|
-
|
|
315
|
+
- `getCoreI18nEngine()` - Get core engine with system strings
|
|
316
|
+
- `createCoreI18nEngine(instanceKey?)` - Create core engine instance
|
|
317
|
+
- `getCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Get core translation
|
|
318
|
+
- `safeCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Safe core translation
|
|
319
|
+
- `getCoreLanguageCodes()` - Get array of core language codes
|
|
320
|
+
- `getCoreLanguageDefinitions()` - Get core language definitions
|
|
2297
321
|
|
|
2298
|
-
|
|
322
|
+
## Testing
|
|
2299
323
|
|
|
2300
324
|
```typescript
|
|
2301
|
-
|
|
2302
|
-
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
2303
|
-
// Or use default instance
|
|
2304
|
-
const engine = PluginI18nEngine.getInstance(); // Uses 'default' key
|
|
2305
|
-
```
|
|
2306
|
-
|
|
2307
|
-
**Issue: "Component 'X' not found"**
|
|
325
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
2308
326
|
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
```
|
|
327
|
+
describe('My Tests', () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
PluginI18nEngine.resetAll();
|
|
330
|
+
});
|
|
2314
331
|
|
|
2315
|
-
|
|
332
|
+
afterEach(() => {
|
|
333
|
+
PluginI18nEngine.resetAll();
|
|
334
|
+
});
|
|
2316
335
|
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
engine.
|
|
2320
|
-
engine.
|
|
336
|
+
it('should translate', () => {
|
|
337
|
+
const engine = PluginI18nEngine.createInstance('test', languages);
|
|
338
|
+
engine.registerComponent(registration);
|
|
339
|
+
expect(engine.translate('app', 'hello')).toBe('Hello');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
2321
342
|
```
|
|
2322
343
|
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
```typescript
|
|
2326
|
-
// Solution: Use validation to catch missing translations
|
|
2327
|
-
const validation = engine.validateAllComponents();
|
|
2328
|
-
if (!validation.isValid) {
|
|
2329
|
-
console.error('Missing translations:', validation.errors);
|
|
2330
|
-
// Fix translations before deploying
|
|
2331
|
-
}
|
|
2332
|
-
```
|
|
344
|
+
## TypeScript Support
|
|
2333
345
|
|
|
2334
|
-
|
|
346
|
+
Full TypeScript support with generic types:
|
|
2335
347
|
|
|
2336
348
|
```typescript
|
|
2337
|
-
//
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
}
|
|
349
|
+
// Type-safe language codes
|
|
350
|
+
type MyLanguages = 'en-US' | 'fr' | 'es';
|
|
351
|
+
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', languages);
|
|
2341
352
|
|
|
2342
|
-
//
|
|
2343
|
-
|
|
353
|
+
// Type-safe string keys
|
|
354
|
+
enum MyStringKeys {
|
|
355
|
+
Welcome = 'welcome',
|
|
356
|
+
Goodbye = 'goodbye'
|
|
357
|
+
}
|
|
2344
358
|
|
|
2345
|
-
//
|
|
2346
|
-
|
|
359
|
+
// Type-safe component registration
|
|
360
|
+
const registration: ComponentRegistration<MyStringKeys, MyLanguages> = {
|
|
361
|
+
component: {
|
|
362
|
+
id: 'app',
|
|
363
|
+
name: 'App',
|
|
364
|
+
stringKeys: Object.values(MyStringKeys)
|
|
365
|
+
},
|
|
366
|
+
strings: {
|
|
367
|
+
'en-US': {
|
|
368
|
+
[MyStringKeys.Welcome]: 'Welcome',
|
|
369
|
+
[MyStringKeys.Goodbye]: 'Goodbye'
|
|
370
|
+
},
|
|
371
|
+
'fr': {
|
|
372
|
+
[MyStringKeys.Welcome]: 'Bienvenue',
|
|
373
|
+
[MyStringKeys.Goodbye]: 'Au revoir'
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
};
|
|
2347
377
|
```
|
|
2348
378
|
|
|
2349
|
-
##
|
|
379
|
+
## Browser Support
|
|
2350
380
|
|
|
2351
|
-
|
|
381
|
+
- Chrome/Edge: Latest 2 versions
|
|
382
|
+
- Firefox: Latest 2 versions
|
|
383
|
+
- Safari: Latest 2 versions
|
|
384
|
+
- Node.js: 18+
|
|
2352
385
|
|
|
2353
|
-
##
|
|
386
|
+
## License
|
|
2354
387
|
|
|
2355
|
-
|
|
388
|
+
MIT License - See LICENSE file for details
|
|
2356
389
|
|
|
2357
390
|
## Contributing
|
|
2358
391
|
|
|
2359
|
-
Contributions
|
|
392
|
+
Contributions welcome! Please:
|
|
2360
393
|
|
|
2361
394
|
1. Fork the repository
|
|
2362
395
|
2. Create a feature branch
|
|
2363
396
|
3. Add tests for new functionality
|
|
2364
|
-
4. Ensure all tests pass
|
|
397
|
+
4. Ensure all tests pass (`npm test`)
|
|
2365
398
|
5. Submit a pull request
|
|
2366
399
|
|
|
2367
|
-
|
|
400
|
+
## Support
|
|
2368
401
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
cd DigitalBurnbag/packages/digitaldefiance-i18n-lib
|
|
402
|
+
- **Issues**: <https://github.com/Digital-Defiance/i18n-lib/issues>
|
|
403
|
+
- **Documentation**: See docs/ directory
|
|
404
|
+
- **Examples**: See tests/ directory
|
|
2373
405
|
|
|
2374
|
-
|
|
2375
|
-
yarn install
|
|
406
|
+
## ChangeLog
|
|
2376
407
|
|
|
2377
|
-
|
|
2378
|
-
yarn test
|
|
408
|
+
### Version 2.0.0
|
|
2379
409
|
|
|
2380
|
-
|
|
2381
|
-
yarn build
|
|
2382
|
-
```
|
|
410
|
+
**Major Release** - Architecture improvements and bug fixes
|
|
2383
411
|
|
|
2384
|
-
|
|
412
|
+
**Fixed**:
|
|
2385
413
|
|
|
2386
|
-
|
|
414
|
+
- Fixed `RegistryError.createWithEngine` to use correct parameter order (removed redundant componentId parameter)
|
|
415
|
+
- Fixed `PluginTranslatableGenericError.withEngine` to accept engine as first parameter instead of instanceKey
|
|
416
|
+
- Fixed `LanguageRegistry.getMatchingCode` to check and return language codes instead of IDs
|
|
417
|
+
- Fixed `createTranslationAdapter` to support both full TranslationEngine interface and simplified bound interface
|
|
418
|
+
- Fixed `TypedError` constructor to use correct componentId parameter
|
|
419
|
+
- Added missing `Common_Test` to CoreStringKey enum
|
|
420
|
+
- Fixed mock TranslationEngine signatures in tests to include componentId parameter
|
|
2387
421
|
|
|
2388
|
-
|
|
2389
|
-
- Documentation: See README.md and inline code documentation
|
|
2390
|
-
- Examples: See `examples/` directory in repository
|
|
422
|
+
**Improved**:
|
|
2391
423
|
|
|
2392
|
-
|
|
424
|
+
- Enhanced `createTranslationAdapter` with dual interface support (4-param and 3-param calling conventions)
|
|
425
|
+
- Improved JSDoc documentation for `createTranslationAdapter` with usage examples
|
|
426
|
+
- Added French language support to typed-error tests
|
|
427
|
+
- Updated all test files to use v2 LanguageRegistry APIs
|
|
428
|
+
- Added `PluginI18nEngine.resetAll()` calls in test beforeEach hooks for better isolation
|
|
429
|
+
|
|
430
|
+
**Internal**:
|
|
431
|
+
|
|
432
|
+
- Refactored core-i18n.ts to use lazy initialization pattern with Proxy for backward compatibility
|
|
433
|
+
- Updated error classes to use `getCoreI18nEngine()` instead of direct singleton import
|
|
434
|
+
- Fixed duplicate LanguageRegistry exports by commenting out v1 export
|
|
435
|
+
- Added v1 compatibility methods to v2 LanguageRegistry
|
|
436
|
+
- Changed `getDefault()` to return `LanguageDefinition | null` instead of throwing
|
|
437
|
+
|
|
438
|
+
**Testing**:
|
|
439
|
+
|
|
440
|
+
- All 511 tests passing
|
|
441
|
+
- Fixed registry-error.spec.ts mock expectations
|
|
442
|
+
- Fixed language-registry.spec.ts to handle null default language
|
|
443
|
+
- Fixed create-translation-adapter.spec.ts parameter detection
|
|
444
|
+
- Fixed typed-error.spec.ts to import and use PluginI18nEngine
|
|
2393
445
|
|
|
2394
446
|
### Version 1.3.27
|
|
2395
447
|
|
|
@@ -2478,7 +530,7 @@ For issues, questions, or contributions:
|
|
|
2478
530
|
|
|
2479
531
|
### Version 1.2.5
|
|
2480
532
|
|
|
2481
|
-
- Sat Oct 25 2025 15:
|
|
533
|
+
- Sat Oct 25 2025 15:01:00 GMT-0700 (Pacific Daylight Time)
|
|
2482
534
|
|
|
2483
535
|
#### Added
|
|
2484
536
|
|
|
@@ -2487,6 +539,7 @@ For issues, questions, or contributions:
|
|
|
2487
539
|
- Provides graceful error handling with fallback to key strings
|
|
2488
540
|
- Zero overhead - direct delegation to underlying `PluginI18nEngine`
|
|
2489
541
|
- Comprehensive test coverage (19 tests)
|
|
542
|
+
- Supports both full TranslationEngine interface and simplified bound interface
|
|
2490
543
|
|
|
2491
544
|
#### Benefits
|
|
2492
545
|
|
|
@@ -2501,7 +554,8 @@ Packages using custom translation adapters can now replace them with:
|
|
|
2501
554
|
```typescript
|
|
2502
555
|
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
2503
556
|
const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
2504
|
-
|
|
557
|
+
```
|
|
558
|
+
|
|
2505
559
|
### Version 1.2.4
|
|
2506
560
|
|
|
2507
561
|
- Sat Oct 25 2025 14:29:00 GMT-0700 (Pacific Daylight Time)
|
|
@@ -2525,6 +579,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2525
579
|
- Updated test mocks to implement both required `translate` and `safeTranslate` methods
|
|
2526
580
|
|
|
2527
581
|
**Breaking Changes:**
|
|
582
|
+
|
|
2528
583
|
- Any code implementing `TranslationEngine` must now provide both `translate` and `safeTranslate` methods (previously optional)
|
|
2529
584
|
- `TranslationEngine` now requires explicit type parameter when used (e.g., `TranslationEngine<EciesStringKey>`)
|
|
2530
585
|
|
|
@@ -2538,6 +593,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2538
593
|
- Thu Oct 23 2025 14:13:00 GMT-0700 (Pacific Daylight Time)
|
|
2539
594
|
|
|
2540
595
|
#### Breaking Changes
|
|
596
|
+
|
|
2541
597
|
- **Removed `CoreLanguage` enum** - Replaced with `CoreLanguageCode` type and `LanguageCodes` constants
|
|
2542
598
|
- **Language identifiers now use BCP 47 codes** - Changed from descriptive names (e.g., `'English (US)'`) to standard codes (e.g., `'en-US'`)
|
|
2543
599
|
- **API changes**:
|
|
@@ -2546,6 +602,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2546
602
|
- All language references updated to use `LanguageCodes` constants
|
|
2547
603
|
|
|
2548
604
|
#### Added
|
|
605
|
+
|
|
2549
606
|
- **`LanguageCodes` constants object** - Provides standard BCP 47 language codes:
|
|
2550
607
|
- `EN_US`, `EN_GB`, `FR`, `ES`, `DE`, `ZH_CN`, `JA`, `UK`
|
|
2551
608
|
- **`LanguageDisplayNames` mapping** - Maps language codes to human-readable names
|
|
@@ -2554,11 +611,13 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2554
611
|
- **Custom language code support** - Any string can now be used as a language code
|
|
2555
612
|
|
|
2556
613
|
#### Changed
|
|
614
|
+
|
|
2557
615
|
- **Language code format** - All language identifiers now use BCP 47 standard (e.g., `'en-US'` instead of `'English (US)'`)
|
|
2558
616
|
- **Type system** - Languages are now string-based types instead of enums, allowing custom language codes
|
|
2559
617
|
- **Documentation** - Updated README with new API usage examples and language code constants
|
|
2560
618
|
|
|
2561
619
|
#### Migration Guide
|
|
620
|
+
|
|
2562
621
|
```typescript
|
|
2563
622
|
// Before (v1.1.x)
|
|
2564
623
|
import { CoreLanguage } from '@digitaldefiance/i18n-lib';
|
|
@@ -2592,12 +651,7 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2592
651
|
|
|
2593
652
|
- Wed Oct 15 2025 16:13:00 GMT-0700 (Pacific Daylight Time)
|
|
2594
653
|
**Fixed:**
|
|
2595
|
-
- Corrected safeCoreTranslation fallback format to use
|
|
2596
|
-
|
|
2597
|
-
```plaintext
|
|
2598
|
-
[CoreStringKey.${stringKey}]
|
|
2599
|
-
```
|
|
2600
|
-
|
|
654
|
+
- Corrected safeCoreTranslation fallback format to use `[CoreStringKey.${stringKey}]`
|
|
2601
655
|
- Fixed import issues with DefaultInstanceKey
|
|
2602
656
|
**Added:**
|
|
2603
657
|
- New TranslatableGenericError class for generic translatable errors across any component
|
|
@@ -2605,12 +659,7 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2605
659
|
- 130+ new tests for comprehensive coverage
|
|
2606
660
|
- Complete usage documentation
|
|
2607
661
|
**Changed:**
|
|
2608
|
-
- Standardized all fallback formats to use square brackets
|
|
2609
|
-
|
|
2610
|
-
```plaintext
|
|
2611
|
-
[componentId.stringKey]
|
|
2612
|
-
```
|
|
2613
|
-
|
|
662
|
+
- Standardized all fallback formats to use square brackets `[componentId.stringKey]`
|
|
2614
663
|
- Refactored to use CoreI18nComponentId constant
|
|
2615
664
|
All tests pass and backward compatibility is maintained.
|
|
2616
665
|
|
|
@@ -2662,3 +711,86 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2662
711
|
|
|
2663
712
|
- Wed Sep 24 2025 15:20:07 GMT-0700 (Pacific Daylight Time)
|
|
2664
713
|
- Initial release of the TypeScript internationalization library with enum translation, template processing, context management, and currency formatting.
|
|
714
|
+
PluginI18nEngine.resetAll();
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
afterEach(() => {
|
|
718
|
+
PluginI18nEngine.resetAll();
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it('should translate', () => {
|
|
722
|
+
const engine = PluginI18nEngine.createInstance('test', languages);
|
|
723
|
+
engine.registerComponent(registration);
|
|
724
|
+
expect(engine.translate('app', 'hello')).toBe('Hello');
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
## TypeScript Support
|
|
731
|
+
|
|
732
|
+
Full TypeScript support with generic types:
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
// Type-safe language codes
|
|
736
|
+
type MyLanguages = 'en-US' | 'fr' | 'es';
|
|
737
|
+
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', languages);
|
|
738
|
+
|
|
739
|
+
// Type-safe string keys
|
|
740
|
+
enum MyStringKeys {
|
|
741
|
+
Welcome = 'welcome',
|
|
742
|
+
Goodbye = 'goodbye'
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Type-safe component registration
|
|
746
|
+
const registration: ComponentRegistration<MyStringKeys, MyLanguages> = {
|
|
747
|
+
component: {
|
|
748
|
+
id: 'app',
|
|
749
|
+
name: 'App',
|
|
750
|
+
stringKeys: Object.values(MyStringKeys)
|
|
751
|
+
},
|
|
752
|
+
strings: {
|
|
753
|
+
'en-US': {
|
|
754
|
+
[MyStringKeys.Welcome]: 'Welcome',
|
|
755
|
+
[MyStringKeys.Goodbye]: 'Goodbye'
|
|
756
|
+
},
|
|
757
|
+
'fr': {
|
|
758
|
+
[MyStringKeys.Welcome]: 'Bienvenue',
|
|
759
|
+
[MyStringKeys.Goodbye]: 'Au revoir'
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
## Browser Support
|
|
766
|
+
|
|
767
|
+
- Chrome/Edge: Latest 2 versions
|
|
768
|
+
- Firefox: Latest 2 versions
|
|
769
|
+
- Safari: Latest 2 versions
|
|
770
|
+
- Node.js: 18+
|
|
771
|
+
|
|
772
|
+
## License
|
|
773
|
+
|
|
774
|
+
MIT License - See LICENSE file for details
|
|
775
|
+
|
|
776
|
+
## Contributing
|
|
777
|
+
|
|
778
|
+
Contributions welcome! Please:
|
|
779
|
+
|
|
780
|
+
1. Fork the repository
|
|
781
|
+
2. Create a feature branch
|
|
782
|
+
3. Add tests for new functionality
|
|
783
|
+
4. Ensure all tests pass (`npm test`)
|
|
784
|
+
5. Submit a pull request
|
|
785
|
+
|
|
786
|
+
## Support
|
|
787
|
+
|
|
788
|
+
- **Issues**: <https://github.com/Digital-Defiance/i18n-lib/issues>
|
|
789
|
+
- **Documentation**: See docs/ directory
|
|
790
|
+
- **Examples**: See tests/ directory
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
**Version**: 2.0.0
|
|
795
|
+
**Status**: Production Ready
|
|
796
|
+
**Bundle Size**: ~25KB (minified + gzipped)
|