@digitaldefiance/i18n-lib 1.3.20 → 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 +399 -2218
- 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/component-registry.d.ts +2 -1
- package/src/component-registry.d.ts.map +1 -1
- package/src/component-registry.js +5 -3
- package/src/component-registry.js.map +1 -1
- 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 +20 -8
- package/src/plugin-i18n-engine.js.map +1 -1
- package/src/registry-config.d.ts +3 -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 -102
- 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,2351 +1,452 @@
|
|
|
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
|
-
### Currency Formatting
|
|
775
|
-
|
|
776
|
-
Locale-aware currency formatting:
|
|
777
|
-
|
|
778
|
-
```typescript
|
|
779
|
-
import { getCurrencyFormat, CurrencyCode } from '@digitaldefiance/i18n-lib';
|
|
780
|
-
|
|
781
|
-
// Get format details
|
|
782
|
-
const usdFormat = getCurrencyFormat('en-US', 'USD');
|
|
783
|
-
console.log(usdFormat);
|
|
784
|
-
// {
|
|
785
|
-
// symbol: '$',
|
|
786
|
-
// position: 'prefix',
|
|
787
|
-
// groupSeparator: ',',
|
|
788
|
-
// decimalSeparator: '.'
|
|
789
|
-
// }
|
|
790
|
-
|
|
791
|
-
const eurFormat = getCurrencyFormat('de-DE', 'EUR');
|
|
792
|
-
console.log(eurFormat);
|
|
793
|
-
// {
|
|
794
|
-
// symbol: '€',
|
|
795
|
-
// position: 'postfix',
|
|
796
|
-
// groupSeparator: '.',
|
|
797
|
-
// decimalSeparator: ','
|
|
798
|
-
// }
|
|
799
|
-
|
|
800
|
-
// Validate currency codes
|
|
801
|
-
const currencyCode = new CurrencyCode('USD');
|
|
802
|
-
console.log(currencyCode.value); // 'USD'
|
|
803
|
-
console.log(CurrencyCode.values); // Array of all valid ISO 4217 codes
|
|
804
|
-
```
|
|
805
|
-
|
|
806
|
-
**Key Features:**
|
|
807
|
-
|
|
808
|
-
- ISO 4217 currency code validation
|
|
809
|
-
- Locale-aware symbol positioning
|
|
810
|
-
- Group and decimal separator detection
|
|
811
|
-
- Intl.NumberFormat integration
|
|
812
|
-
|
|
813
|
-
### Timezone Handling
|
|
814
|
-
|
|
815
|
-
Validated timezone management:
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
import { Timezone, isValidTimezone } from '@digitaldefiance/i18n-lib';
|
|
819
|
-
|
|
820
|
-
// Create validated timezone
|
|
821
|
-
const tz = new Timezone('America/New_York');
|
|
822
|
-
console.log(tz.value); // 'America/New_York'
|
|
823
|
-
|
|
824
|
-
// Validate timezone strings
|
|
825
|
-
if (isValidTimezone('Europe/Paris')) {
|
|
826
|
-
const parisTz = new Timezone('Europe/Paris');
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Invalid timezone throws error
|
|
830
|
-
try {
|
|
831
|
-
new Timezone('Invalid/Timezone');
|
|
832
|
-
} catch (error) {
|
|
833
|
-
console.error('Invalid timezone');
|
|
834
|
-
}
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
**Key Features:**
|
|
838
|
-
|
|
839
|
-
- Moment-timezone validation
|
|
840
|
-
- Immutable timezone values
|
|
841
|
-
- Validation utilities
|
|
842
|
-
- IANA timezone database support
|
|
843
|
-
|
|
844
|
-
### Utility Functions
|
|
845
|
-
|
|
846
|
-
Helper functions for common operations:
|
|
847
|
-
|
|
848
|
-
```typescript
|
|
849
|
-
import {
|
|
850
|
-
replaceVariables,
|
|
851
|
-
isTemplate,
|
|
852
|
-
toStringKey,
|
|
853
|
-
buildReasonMap,
|
|
854
|
-
validateReasonMap,
|
|
855
|
-
createCompleteReasonMap
|
|
856
|
-
} from '@digitaldefiance/i18n-lib';
|
|
857
|
-
|
|
858
|
-
// Variable replacement
|
|
859
|
-
const result = replaceVariables(
|
|
860
|
-
'Hello, {name}! You have {count} messages.',
|
|
861
|
-
{ name: 'John', count: 5 }
|
|
862
|
-
);
|
|
863
|
-
// "Hello, John! You have 5 messages."
|
|
864
|
-
|
|
865
|
-
// Template detection
|
|
866
|
-
if (isTemplate('userGreetingTemplate')) {
|
|
867
|
-
// Handle as template
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// String key construction
|
|
871
|
-
const key = toStringKey('error', 'validation', 'failed');
|
|
872
|
-
// 'error_validation_failed'
|
|
873
|
-
|
|
874
|
-
// Reason map building
|
|
875
|
-
enum ErrorType {
|
|
876
|
-
NotFound = 'notFound',
|
|
877
|
-
AccessDenied = 'accessDenied'
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const reasonMap = buildReasonMap(ErrorType, ['error']);
|
|
881
|
-
// {
|
|
882
|
-
// notFound: 'error_notFound',
|
|
883
|
-
// accessDenied: 'error_accessDenied'
|
|
884
|
-
// }
|
|
885
|
-
|
|
886
|
-
// Validate reason map completeness
|
|
887
|
-
if (validateReasonMap(ErrorType, reasonMap)) {
|
|
888
|
-
// All enum values are mapped
|
|
889
|
-
}
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
### Advanced Plugin Usage
|
|
893
|
-
|
|
894
|
-
#### Compile-Time Completeness Enforcement (Strict Mode)
|
|
895
|
-
|
|
896
|
-
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`:
|
|
897
|
-
|
|
898
|
-
```typescript
|
|
899
|
-
import { createCompleteComponentStrings } from '@digitaldefiance/i18n-lib';
|
|
900
|
-
|
|
901
|
-
enum MyStrings {
|
|
902
|
-
Welcome = 'welcome',
|
|
903
|
-
Farewell = 'farewell'
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
type AppLang = 'en' | 'fr';
|
|
907
|
-
|
|
908
|
-
// This will only compile if BOTH languages contain BOTH keys.
|
|
909
|
-
const myStrictStrings = createCompleteComponentStrings<MyStrings, AppLang>({
|
|
910
|
-
en: {
|
|
911
|
-
[MyStrings.Welcome]: 'Welcome',
|
|
912
|
-
[MyStrings.Farewell]: 'Goodbye'
|
|
913
|
-
},
|
|
914
|
-
fr: {
|
|
915
|
-
[MyStrings.Welcome]: 'Bienvenue',
|
|
916
|
-
[MyStrings.Farewell]: 'Au revoir'
|
|
917
|
-
}
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
// If any key is missing, TypeScript reports an error before runtime.
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
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.
|
|
924
|
-
|
|
925
|
-
#### Adding New Languages
|
|
926
|
-
|
|
927
|
-
```typescript
|
|
928
|
-
import { createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
929
|
-
|
|
930
|
-
// Add Italian support
|
|
931
|
-
const italian = createLanguageDefinition('it', 'Italiano', 'it');
|
|
932
|
-
i18n.registerLanguage(italian);
|
|
933
|
-
|
|
934
|
-
// Update existing components with Italian translations
|
|
935
|
-
i18n.updateComponentStrings('my-component', {
|
|
936
|
-
it: {
|
|
937
|
-
[MyComponentStringKey.Welcome]: 'Benvenuto nel mio componente!',
|
|
938
|
-
[MyComponentStringKey.Goodbye]: 'Arrivederci dal mio componente!',
|
|
939
|
-
[MyComponentStringKey.UserGreetingTemplate]: 'Ciao, {name}!'
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
#### Component Registration Validation
|
|
945
|
-
|
|
946
|
-
The plugin engine provides comprehensive validation to ensure translation completeness:
|
|
947
|
-
|
|
948
|
-
```typescript
|
|
949
|
-
// Each component is validated against ALL system languages
|
|
950
|
-
enum MyStrings {
|
|
951
|
-
Welcome = 'welcome',
|
|
952
|
-
Goodbye = 'goodbye'
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const myComponent: ComponentDefinition<MyStrings> = {
|
|
956
|
-
id: 'my-component',
|
|
957
|
-
name: 'My Component',
|
|
958
|
-
stringKeys: Object.values(MyStrings)
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
// System has EN, FR, ES languages - component must provide translations for all three
|
|
962
|
-
const registration: ComponentRegistration<MyStrings, CoreLanguageCode> = {
|
|
963
|
-
component: myComponent,
|
|
964
|
-
strings: {
|
|
965
|
-
[LanguageCodes.EN_US]: {
|
|
966
|
-
[MyStrings.Welcome]: 'Welcome',
|
|
967
|
-
[MyStrings.Goodbye]: 'Goodbye'
|
|
968
|
-
},
|
|
969
|
-
[LanguageCodes.FR]: {
|
|
970
|
-
[MyStrings.Welcome]: 'Bienvenue',
|
|
971
|
-
[MyStrings.Goodbye]: 'Au revoir'
|
|
972
|
-
},
|
|
973
|
-
[LanguageCodes.ES]: {
|
|
974
|
-
[MyStrings.Welcome]: 'Bienvenido',
|
|
975
|
-
[MyStrings.Goodbye]: 'Adiós'
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
};
|
|
979
|
-
|
|
980
|
-
const result = i18n.registerComponent(registration);
|
|
981
|
-
if (!result.isValid) {
|
|
982
|
-
console.log('Missing translations:', result.missingKeys);
|
|
983
|
-
// Shows exactly which string keys are missing for which languages
|
|
984
|
-
}
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
#### Flexible Language Support
|
|
988
|
-
|
|
989
|
-
Components can support different subsets of system languages:
|
|
990
|
-
|
|
991
|
-
```typescript
|
|
992
|
-
// Component A supports EN, FR, ES
|
|
993
|
-
const componentA = {
|
|
994
|
-
component: { id: 'comp-a', name: 'Component A', stringKeys: ['hello'] },
|
|
995
|
-
strings: {
|
|
996
|
-
'en-US': { hello: 'Hello' },
|
|
997
|
-
'fr': { hello: 'Bonjour' },
|
|
998
|
-
'es': { hello: 'Hola' }
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
// Component B only supports EN and DE (added later)
|
|
1003
|
-
const componentB = {
|
|
1004
|
-
component: { id: 'comp-b', name: 'Component B', stringKeys: ['save'] },
|
|
1005
|
-
strings: {
|
|
1006
|
-
'en-US': { save: 'Save' },
|
|
1007
|
-
'de': { save: 'Speichern' }
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
// Both components can coexist - missing translations use fallback
|
|
1012
|
-
i18n.registerComponent(componentA); // ✓ Complete
|
|
1013
|
-
i18n.registerComponent(componentB); // ⚠ Missing FR, ES - uses fallback
|
|
1014
|
-
|
|
1015
|
-
// Usage automatically handles fallbacks
|
|
1016
|
-
i18n.translate('comp-b', 'save', {}, 'fr'); // Returns 'Save' (en-US fallback)
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
|
-
#### Dynamic Language Addition
|
|
1020
|
-
|
|
1021
|
-
```typescript
|
|
1022
|
-
import { createLanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
1023
|
-
|
|
1024
|
-
// Add new language to system
|
|
1025
|
-
const germanLang = createLanguageDefinition('de', 'Deutsch', 'de');
|
|
1026
|
-
i18n.registerLanguage(germanLang);
|
|
1027
|
-
|
|
1028
|
-
// New component registrations now require German translations
|
|
1029
|
-
const newRegistration = {
|
|
1030
|
-
component: { id: 'new-comp', name: 'New Component', stringKeys: ['test'] },
|
|
1031
|
-
strings: {
|
|
1032
|
-
'en-US': { test: 'Test' },
|
|
1033
|
-
'fr': { test: 'Test' },
|
|
1034
|
-
'es': { test: 'Prueba' }
|
|
1035
|
-
// Missing 'de' - validation will flag this
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
|
-
const result = i18n.registerComponent(newRegistration);
|
|
1040
|
-
console.log(result.missingKeys); // Shows missing German translations
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
#### Validation and Error Handling
|
|
1044
|
-
|
|
1045
|
-
```typescript
|
|
1046
|
-
// Comprehensive validation
|
|
1047
|
-
const globalValidation = i18n.validateAllComponents();
|
|
1048
|
-
if (!globalValidation.isValid) {
|
|
1049
|
-
console.error('Validation errors:', globalValidation.errors);
|
|
1050
|
-
console.warn('Warnings:', globalValidation.warnings);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Handle registration errors
|
|
1054
|
-
try {
|
|
1055
|
-
i18n.registerComponent(incompleteRegistration);
|
|
1056
|
-
} catch (error) {
|
|
1057
|
-
if (error instanceof RegistryError) {
|
|
1058
|
-
console.error(`Registry error: ${error.type}`, error.metadata);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Strict validation mode (rejects incomplete registrations)
|
|
1063
|
-
const strictEngine = new PluginI18nEngine(languages, {
|
|
1064
|
-
validation: {
|
|
1065
|
-
requireCompleteStrings: true,
|
|
1066
|
-
allowPartialRegistration: false,
|
|
1067
|
-
fallbackLanguageId: 'en-US'
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
|
|
1071
|
-
// Optional instance registration overrides (third constructor argument)
|
|
1072
|
-
const reportingEngine = new PluginI18nEngine(languages, {}, {
|
|
1073
|
-
instanceKey: 'reporting',
|
|
1074
|
-
registerInstance: false,
|
|
1075
|
-
setAsDefault: false
|
|
1076
|
-
});
|
|
1077
|
-
```
|
|
1078
|
-
|
|
1079
|
-
#### Multi-Instance Support
|
|
1080
|
-
|
|
1081
|
-
```typescript
|
|
1082
|
-
// Create separate instances for different contexts
|
|
1083
|
-
const adminI18n = PluginI18nEngine.createInstance('admin', languages);
|
|
1084
|
-
const userI18n = PluginI18nEngine.createInstance('user', languages);
|
|
1085
|
-
|
|
1086
|
-
// Register different components for each
|
|
1087
|
-
adminI18n.registerComponent(adminComponentRegistration);
|
|
1088
|
-
userI18n.registerComponent(userComponentRegistration);
|
|
1089
|
-
|
|
1090
|
-
// Create an isolated instance without touching the global registry (handy for tests)
|
|
1091
|
-
const detachedI18n = new PluginI18nEngine(languages, {}, { registerInstance: false });
|
|
1092
|
-
detachedI18n.registerComponent(myTestRegistration);
|
|
1093
|
-
```
|
|
1094
|
-
|
|
1095
|
-
For complete documentation on the plugin architecture, see [PLUGIN_ARCHITECTURE.md](./PLUGIN_ARCHITECTURE.md).
|
|
1096
|
-
|
|
1097
|
-
## Advanced Features
|
|
1098
|
-
|
|
1099
|
-
### Translatable Errors
|
|
1100
|
-
|
|
1101
|
-
The `TranslatableGenericError` class provides a simple way to create errors with translated messages that work across any component:
|
|
1102
|
-
|
|
1103
|
-
```typescript
|
|
1104
|
-
import { TranslatableGenericError, CoreStringKey, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1105
|
-
|
|
1106
|
-
// Define your error string keys
|
|
1107
|
-
enum UserErrorKey {
|
|
1108
|
-
UserNotFound = 'userNotFound',
|
|
1109
|
-
InvalidCredentials = 'invalidCredentials',
|
|
1110
|
-
AccountLocked = 'accountLocked',
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// Register your component with translations
|
|
1114
|
-
const userErrorComponent = {
|
|
1115
|
-
id: 'user-errors',
|
|
1116
|
-
name: 'User Errors',
|
|
1117
|
-
stringKeys: Object.values(UserErrorKey)
|
|
1118
|
-
};
|
|
1119
|
-
|
|
1120
|
-
const registration = {
|
|
1121
|
-
component: userErrorComponent,
|
|
1122
|
-
strings: {
|
|
1123
|
-
'en-US': {
|
|
1124
|
-
[UserErrorKey.UserNotFound]: 'User "{username}" not found',
|
|
1125
|
-
[UserErrorKey.InvalidCredentials]: 'Invalid credentials provided',
|
|
1126
|
-
[UserErrorKey.AccountLocked]: 'Account locked until {unlockTime}'
|
|
1127
|
-
},
|
|
1128
|
-
'fr': {
|
|
1129
|
-
[UserErrorKey.UserNotFound]: 'Utilisateur "{username}" introuvable',
|
|
1130
|
-
[UserErrorKey.InvalidCredentials]: 'Identifiants invalides fournis',
|
|
1131
|
-
[UserErrorKey.AccountLocked]: 'Compte verrouillé jusqu\'à {unlockTime}'
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
|
|
1136
|
-
i18n.registerComponent(registration);
|
|
1137
|
-
|
|
1138
|
-
// Throw translatable errors
|
|
1139
|
-
throw new TranslatableGenericError(
|
|
1140
|
-
'user-errors',
|
|
1141
|
-
UserErrorKey.UserNotFound,
|
|
1142
|
-
{ username: 'john_doe' },
|
|
1143
|
-
'en-US',
|
|
1144
|
-
{ userId: 123 }, // metadata
|
|
1145
|
-
'myapp' // engine instance key
|
|
1146
|
-
);
|
|
1147
|
-
|
|
1148
|
-
// Use with explicit engine instance
|
|
1149
|
-
const error = TranslatableGenericError.withEngine(
|
|
1150
|
-
i18n,
|
|
1151
|
-
'user-errors',
|
|
1152
|
-
UserErrorKey.InvalidCredentials,
|
|
1153
|
-
undefined,
|
|
1154
|
-
'fr'
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
// Retranslate errors dynamically
|
|
1158
|
-
try {
|
|
1159
|
-
// ... code that throws TranslatableGenericError
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
if (error instanceof TranslatableGenericError) {
|
|
1162
|
-
const localizedMessage = error.retranslate(userLanguage, 'myapp');
|
|
1163
|
-
sendToUser(localizedMessage);
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Use with core strings
|
|
1168
|
-
throw new TranslatableGenericError(
|
|
1169
|
-
'core',
|
|
1170
|
-
CoreStringKey.Error_AccessDenied,
|
|
1171
|
-
undefined,
|
|
1172
|
-
LanguageCodes.EN_US,
|
|
1173
|
-
{ requestId: '12345' },
|
|
1174
|
-
'myapp'
|
|
1175
|
-
);
|
|
1176
|
-
```
|
|
1177
|
-
|
|
1178
|
-
**Key Features:**
|
|
1179
|
-
|
|
1180
|
-
- Works with any registered component and string keys
|
|
1181
|
-
- Uses `safeTranslate` for consistent fallback behavior (`[componentId.stringKey]`)
|
|
1182
|
-
- Stores error context: stringKey, componentId, language, variables, metadata
|
|
1183
|
-
- Supports dynamic retranslation with `retranslate()` method
|
|
1184
|
-
- Never throws during construction - always returns a valid error
|
|
1185
|
-
- Compatible with both constructor and static factory methods
|
|
1186
|
-
|
|
1187
|
-
For complete documentation, see [TRANSLATABLE_ERROR_GUIDE.md](./TRANSLATABLE_ERROR_GUIDE.md).
|
|
1188
|
-
|
|
1189
|
-
### Enum Translation Registry
|
|
1190
|
-
|
|
1191
|
-
```typescript
|
|
1192
|
-
enum Status {
|
|
1193
|
-
Active = 'active',
|
|
1194
|
-
Inactive = 'inactive',
|
|
1195
|
-
Pending = 'pending'
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// Register enum translations (requires complete translations)
|
|
1199
|
-
i18n.registerEnum(Status, {
|
|
1200
|
-
[MyLanguages.English]: {
|
|
1201
|
-
[Status.Active]: 'Active',
|
|
1202
|
-
[Status.Inactive]: 'Inactive',
|
|
1203
|
-
[Status.Pending]: 'Pending'
|
|
1204
|
-
},
|
|
1205
|
-
[MyLanguages.Spanish]: {
|
|
1206
|
-
[Status.Active]: 'Activo',
|
|
1207
|
-
[Status.Inactive]: 'Inactivo',
|
|
1208
|
-
[Status.Pending]: 'Pendiente'
|
|
1209
|
-
}
|
|
1210
|
-
}, 'Status');
|
|
1211
|
-
|
|
1212
|
-
// Translate enum values
|
|
1213
|
-
const statusText = i18n.translateEnum(Status, Status.Active, MyLanguages.Spanish);
|
|
1214
|
-
// "Activo"
|
|
1215
|
-
```
|
|
1216
|
-
|
|
1217
|
-
### Template Processing
|
|
1218
|
-
|
|
1219
|
-
```typescript
|
|
1220
|
-
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
1221
|
-
|
|
1222
|
-
const engine = PluginI18nEngine.getInstance<'en-US' | 'es'>();
|
|
1223
|
-
|
|
1224
|
-
// Component placeholders are resolved before variable replacement
|
|
1225
|
-
const message = engine.t(
|
|
1226
|
-
'{{core.System_Welcome}} {name}! {{core.System_PleaseWait}}',
|
|
1227
|
-
'en-US',
|
|
1228
|
-
{ name: 'John' }
|
|
1229
|
-
);
|
|
1230
|
-
// "Welcome John! Please wait..."
|
|
1231
|
-
|
|
1232
|
-
// Templates ending with "Template" automatically receive the first var map
|
|
1233
|
-
const greeting = engine.t(
|
|
1234
|
-
'{{my-component.UserGreetingTemplate}}',
|
|
1235
|
-
'es',
|
|
1236
|
-
{ name: 'Laura' },
|
|
1237
|
-
{ fallbackName: 'Usuario' }
|
|
1238
|
-
);
|
|
1239
|
-
// "¡Hola, Laura!"
|
|
1240
|
-
|
|
1241
|
-
// Additional variable objects are merged left→right
|
|
1242
|
-
const merged = engine.t(
|
|
1243
|
-
'User {username} has {count} notifications',
|
|
1244
|
-
'en-US',
|
|
1245
|
-
{ username: 'alice' },
|
|
1246
|
-
{ count: 5 }
|
|
1247
|
-
);
|
|
1248
|
-
// "User alice has 5 notifications"
|
|
1249
|
-
```
|
|
1250
|
-
|
|
1251
|
-
### Context Management
|
|
1252
|
-
|
|
1253
|
-
```typescript
|
|
1254
|
-
import { PluginI18nEngine, CurrencyCode, Timezone } from '@digitaldefiance/i18n-lib';
|
|
1255
|
-
|
|
1256
|
-
const engine = PluginI18nEngine.getInstance<'en-US' | 'es'>();
|
|
1257
|
-
|
|
1258
|
-
engine.updateContext({
|
|
1259
|
-
language: 'es',
|
|
1260
|
-
adminLanguage: 'en-US',
|
|
1261
|
-
currencyCode: new CurrencyCode('EUR'),
|
|
1262
|
-
timezone: new Timezone('Europe/Madrid'),
|
|
1263
|
-
adminTimezone: new Timezone('UTC'),
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
engine.updateContext({ currentContext: 'admin' });
|
|
1267
|
-
|
|
1268
|
-
const context = engine.getContext();
|
|
1269
|
-
console.log(context.currentContext); // 'admin'
|
|
1270
|
-
console.log(context.language); // 'es'
|
|
1271
|
-
|
|
1272
|
-
engine.updateContext({ currentContext: 'user' });
|
|
1273
|
-
engine.setLanguage('en-US');
|
|
1274
|
-
```
|
|
1275
|
-
|
|
1276
|
-
### Context Change Monitoring
|
|
1277
|
-
|
|
1278
|
-
```typescript
|
|
1279
|
-
import { ContextManager } from '@digitaldefiance/i18n-lib';
|
|
1280
|
-
|
|
1281
|
-
interface AppContext {
|
|
1282
|
-
language: string;
|
|
1283
|
-
theme: string;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
const manager = new ContextManager<AppContext>();
|
|
1287
|
-
|
|
1288
|
-
// Add listeners for context changes
|
|
1289
|
-
manager.addListener((property, oldValue, newValue) => {
|
|
1290
|
-
console.log(`${property} changed from ${oldValue} to ${newValue}`);
|
|
1291
|
-
});
|
|
1292
|
-
|
|
1293
|
-
// Create reactive context
|
|
1294
|
-
const context = { language: 'en', theme: 'dark' };
|
|
1295
|
-
const reactiveContext = manager.createProxy(context);
|
|
1296
|
-
|
|
1297
|
-
// Changes are automatically detected
|
|
1298
|
-
reactiveContext.language = 'es'; // Triggers listener
|
|
1299
|
-
```
|
|
1300
|
-
|
|
1301
|
-
### Currency Formatting
|
|
1302
|
-
|
|
1303
|
-
```typescript
|
|
1304
|
-
import { getCurrencyFormat } from '@digitaldefiance/i18n-lib';
|
|
1305
|
-
|
|
1306
|
-
// Get currency formatting information
|
|
1307
|
-
const usdFormat = getCurrencyFormat('en-US', 'USD');
|
|
1308
|
-
// { symbol: '$', position: 'prefix', groupSeparator: ',', decimalSeparator: '.' }
|
|
1309
|
-
|
|
1310
|
-
const eurFormat = getCurrencyFormat('de-DE', 'EUR');
|
|
1311
|
-
// { symbol: '€', position: 'postfix', groupSeparator: '.', decimalSeparator: ',' }
|
|
1312
|
-
```
|
|
1313
|
-
|
|
1314
|
-
### Instance Management
|
|
1315
|
-
|
|
1316
|
-
```typescript
|
|
1317
|
-
import { PluginI18nEngine, LanguageDefinition } from '@digitaldefiance/i18n-lib';
|
|
1318
|
-
|
|
1319
|
-
const languages: ReadonlyArray<LanguageDefinition> = [
|
|
1320
|
-
{ id: 'en-US', name: 'English (US)', code: 'en-US', isDefault: true },
|
|
1321
|
-
{ id: 'fr', name: 'Français', code: 'fr' },
|
|
1322
|
-
];
|
|
1323
|
-
|
|
1324
|
-
// Create named instances
|
|
1325
|
-
const mainI18n = PluginI18nEngine.createInstance('main', languages);
|
|
1326
|
-
const adminI18n = PluginI18nEngine.createInstance('admin', languages);
|
|
1327
|
-
|
|
1328
|
-
// Get instances by key
|
|
1329
|
-
const instance = PluginI18nEngine.getInstance('main');
|
|
1330
|
-
|
|
1331
|
-
// Clean up instances (useful for testing)
|
|
1332
|
-
PluginI18nEngine.removeInstance('main');
|
|
1333
|
-
PluginI18nEngine.resetAll();
|
|
1334
|
-
```
|
|
1335
|
-
|
|
1336
|
-
## Supported Languages
|
|
1337
|
-
|
|
1338
|
-
The library includes pre-built translations for 8 languages in the core component:
|
|
1339
|
-
|
|
1340
|
-
| Language Code | Display Name | ISO Code |
|
|
1341
|
-
|--------------|--------------|----------|
|
|
1342
|
-
| `en-US` | English (US) | en-US |
|
|
1343
|
-
| `en-GB` | English (UK) | en-GB |
|
|
1344
|
-
| `fr` | Français | fr |
|
|
1345
|
-
| `es` | Español | es |
|
|
1346
|
-
| `de` | Deutsch | de |
|
|
1347
|
-
| `zh-CN` | 中文 (简体) | zh-CN |
|
|
1348
|
-
| `ja` | 日本語 | ja |
|
|
1349
|
-
| `uk` | Українська | uk |
|
|
1350
|
-
|
|
1351
|
-
### Language Code Constants
|
|
1352
|
-
|
|
1353
|
-
```typescript
|
|
1354
|
-
import { LanguageCodes, LanguageDisplayNames, getCoreLanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1355
|
-
|
|
1356
|
-
// Use constants for type safety
|
|
1357
|
-
const lang = LanguageCodes.FR; // 'fr'
|
|
1358
|
-
const displayName = LanguageDisplayNames[LanguageCodes.FR]; // 'Français'
|
|
1359
|
-
|
|
1360
|
-
// Get all core language codes as array (for Mongoose enums, etc.)
|
|
1361
|
-
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1362
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1363
|
-
|
|
1364
|
-
// All available codes
|
|
1365
|
-
const codes = {
|
|
1366
|
-
EN_US: 'en-US',
|
|
1367
|
-
EN_GB: 'en-GB',
|
|
1368
|
-
FR: 'fr',
|
|
1369
|
-
ES: 'es',
|
|
1370
|
-
DE: 'de',
|
|
1371
|
-
ZH_CN: 'zh-CN',
|
|
1372
|
-
JA: 'ja',
|
|
1373
|
-
UK: 'uk'
|
|
1374
|
-
};
|
|
1375
|
-
```
|
|
1376
|
-
|
|
1377
|
-
### Custom Language Codes
|
|
1378
|
-
|
|
1379
|
-
Extend core languages with custom ones while maintaining type safety:
|
|
1380
|
-
|
|
1381
|
-
```typescript
|
|
1382
|
-
import {
|
|
1383
|
-
CoreLanguageCode,
|
|
1384
|
-
getCoreLanguageDefinitions,
|
|
1385
|
-
createLanguageDefinition,
|
|
1386
|
-
PluginI18nEngine
|
|
1387
|
-
} from '@digitaldefiance/i18n-lib';
|
|
1388
|
-
|
|
1389
|
-
// Define custom language type extending core
|
|
1390
|
-
type MyLanguageCode = CoreLanguageCode | 'pt-BR' | 'it';
|
|
1391
|
-
|
|
1392
|
-
// Create engine with extended languages
|
|
1393
|
-
const engine = PluginI18nEngine.createInstance<MyLanguageCode>(
|
|
1394
|
-
'myapp',
|
|
1395
|
-
[
|
|
1396
|
-
...getCoreLanguageDefinitions(),
|
|
1397
|
-
createLanguageDefinition('pt-BR', 'Português (Brasil)', 'pt-BR'),
|
|
1398
|
-
createLanguageDefinition('it', 'Italiano', 'it')
|
|
1399
|
-
]
|
|
1400
|
-
);
|
|
1401
|
-
|
|
1402
|
-
// Type-safe language usage
|
|
1403
|
-
const lang1: MyLanguageCode = 'en-US'; // ✓ Core language
|
|
1404
|
-
const lang2: MyLanguageCode = 'pt-BR'; // ✓ Custom language
|
|
1405
|
-
const lang3: MyLanguageCode = 'invalid'; // ✗ Type error!
|
|
1406
|
-
```
|
|
1407
|
-
|
|
1408
|
-
### Helper Functions for Mongoose Schemas
|
|
1409
|
-
|
|
1410
|
-
Extract language codes from the registry (single source of truth):
|
|
1411
|
-
|
|
1412
|
-
```typescript
|
|
1413
|
-
import { PluginI18nEngine, LanguageRegistry, getCoreLanguageCodes, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1414
|
-
import { Schema } from 'mongoose';
|
|
1415
|
-
|
|
1416
|
-
// Static approach: Get core language codes as array
|
|
1417
|
-
const coreLanguageCodes = getCoreLanguageCodes();
|
|
1418
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
1419
|
-
|
|
1420
|
-
// Dynamic approach: Get from engine instance (includes custom languages)
|
|
1421
|
-
const engine = PluginI18nEngine.getInstance<string>();
|
|
1422
|
-
const languageDefinitions = engine.getLanguages();
|
|
1423
|
-
const languageIds = languageDefinitions.map((lang) => lang.id);
|
|
1424
|
-
const isoCodes = languageDefinitions.map((lang) => lang.code);
|
|
1425
|
-
|
|
1426
|
-
// Use in Mongoose schema
|
|
1427
|
-
const userSchema = new Schema({
|
|
1428
|
-
language: {
|
|
1429
|
-
type: String,
|
|
1430
|
-
enum: coreLanguageCodes, // Static core languages
|
|
1431
|
-
default: LanguageCodes.EN_US
|
|
1432
|
-
},
|
|
1433
|
-
adminLanguage: {
|
|
1434
|
-
type: String,
|
|
1435
|
-
enum: languageIds, // Dynamic from registry
|
|
1436
|
-
default: LanguageCodes.EN_US
|
|
1437
|
-
}
|
|
1438
|
-
});
|
|
1439
|
-
|
|
1440
|
-
// Get display names for validation messages
|
|
1441
|
-
const displayNames = LanguageRegistry.getLanguageDisplayNames();
|
|
1442
|
-
// { 'en-US': 'English (US)', 'fr': 'Français', ... }
|
|
1443
|
-
```
|
|
1444
|
-
|
|
1445
|
-
## Core String Keys
|
|
1446
|
-
|
|
1447
|
-
The core component provides 40+ system strings organized by category:
|
|
1448
|
-
|
|
1449
|
-
### Common Strings
|
|
1450
|
-
|
|
1451
|
-
- `Common_Yes`, `Common_No`, `Common_Cancel`, `Common_OK`
|
|
1452
|
-
- `Common_Save`, `Common_Delete`, `Common_Edit`, `Common_Create`, `Common_Update`
|
|
1453
|
-
- `Common_Loading`, `Common_Error`, `Common_Success`, `Common_Warning`, `Common_Info`
|
|
1454
|
-
- `Common_Disposed`
|
|
1455
|
-
|
|
1456
|
-
### Error Messages
|
|
1457
|
-
|
|
1458
|
-
- `Error_InvalidInput`, `Error_NetworkError`, `Error_NotFound`
|
|
1459
|
-
- `Error_AccessDenied`, `Error_InternalServer`, `Error_ValidationFailed`
|
|
1460
|
-
- `Error_RequiredField`, `Error_InvalidContextTemplate`
|
|
1461
|
-
- `Error_MissingTranslationKeyTemplate`
|
|
1462
|
-
|
|
1463
|
-
### Registry Error Templates
|
|
1464
|
-
|
|
1465
|
-
- `Error_ComponentNotFoundTemplate`
|
|
1466
|
-
- `Error_LanguageNotFoundTemplate`
|
|
1467
|
-
- `Error_StringKeyNotFoundTemplate`
|
|
1468
|
-
- `Error_IncompleteRegistrationTemplate`
|
|
1469
|
-
- `Error_DuplicateComponentTemplate`
|
|
1470
|
-
- `Error_DuplicateLanguageTemplate`
|
|
1471
|
-
- `Error_ValidationFailedTemplate`
|
|
1472
|
-
|
|
1473
|
-
### System Messages
|
|
1474
|
-
|
|
1475
|
-
- `System_Welcome`, `System_Goodbye`, `System_PleaseWait`
|
|
1476
|
-
- `System_ProcessingRequest`, `System_OperationComplete`
|
|
1477
|
-
- `System_NoDataAvailable`
|
|
1478
|
-
|
|
1479
|
-
## API Reference
|
|
1480
|
-
|
|
1481
|
-
### Plugin Architecture API
|
|
1482
|
-
|
|
1483
|
-
#### PluginI18nEngine
|
|
1484
|
-
|
|
1485
|
-
**Constructor**
|
|
1486
|
-
|
|
1487
|
-
- `new PluginI18nEngine<TLanguages>(languages, config?, options?)` - Create new plugin engine
|
|
1488
|
-
- `PluginI18nEngine.createInstance<TLanguages>(key, languages, config?)` - Create named instance
|
|
1489
|
-
- `PluginI18nEngine.getInstance<TLanguages>(key?)` - Get existing instance (throws error if not found)
|
|
1490
|
-
|
|
1491
|
-
`options` enables fine-grained control over how the engine participates in the static registry:
|
|
1492
|
-
|
|
1493
|
-
- `instanceKey` – Override the implicit registry key when instantiating directly (defaults to `'default'`).
|
|
1494
|
-
- `registerInstance` – Set to `false` to keep the engine out of the shared instance map (useful for tests or short-lived contexts).
|
|
1495
|
-
- `setAsDefault` – Force this instance to become the default lookup target even if another default exists.
|
|
1496
|
-
|
|
1497
|
-
**Component Management**
|
|
1498
|
-
|
|
1499
|
-
- `registerComponent<TStringKeys>(registration)` - Register component with translations
|
|
1500
|
-
- `updateComponentStrings<TStringKeys>(componentId, strings)` - Update existing component strings
|
|
1501
|
-
- `getComponents()` - Get all registered components
|
|
1502
|
-
- `hasComponent(componentId)` - Check if component exists
|
|
1503
|
-
|
|
1504
|
-
**Translation Methods**
|
|
1505
|
-
|
|
1506
|
-
- `translate<TStringKeys>(componentId, stringKey, variables?, language?)` - Translate component string
|
|
1507
|
-
- `safeTranslate(componentId, stringKey, variables?, language?)` - Safe translate with fallback
|
|
1508
|
-
- `getTranslationDetails<TStringKeys>(componentId, stringKey, variables?, language?)` - Get detailed translation response
|
|
1509
|
-
|
|
1510
|
-
**Language Management**
|
|
1511
|
-
|
|
1512
|
-
- `registerLanguage(language)` - Register new language
|
|
1513
|
-
- `registerLanguages(languages)` - Register multiple languages
|
|
1514
|
-
- `getLanguages()` - Get all registered languages
|
|
1515
|
-
- `hasLanguage(language)` - Check if language exists
|
|
1516
|
-
- `setLanguage(language)` - Set current language
|
|
1517
|
-
- `getLanguageByCode(code)` - Get language by ISO code
|
|
1518
|
-
- `getMatchingLanguageCode(requestedCode?, userDefaultCode?)` - Get matching language code with fallback logic
|
|
1519
|
-
|
|
1520
|
-
**Context & Validation**
|
|
1521
|
-
|
|
1522
|
-
- `getContext()` - Read the active context (language, adminLanguage, currency, timezone)
|
|
1523
|
-
- `updateContext(updates)` - Apply partial context updates
|
|
1524
|
-
- `validateAllComponents()` - Validate all registered components
|
|
1525
|
-
- `getComponentRegistry()` - Access component registry directly
|
|
1526
|
-
- `getEnumRegistry()` - Access enum registry directly
|
|
1527
|
-
- Use `LanguageRegistry` statics (e.g. `LanguageRegistry.getLanguageIds()`) for low-level language metadata
|
|
1528
|
-
|
|
1529
|
-
#### Core I18n Functions
|
|
1530
|
-
|
|
1531
|
-
- `createCoreI18nEngine(instanceKey?)` - Create engine with core components
|
|
1532
|
-
- `getCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Get core translation
|
|
1533
|
-
- `safeCoreTranslation(stringKey, variables?, language?, instanceKey?)` - Safe core translation
|
|
1534
|
-
|
|
1535
|
-
#### Component Registration Types
|
|
1536
|
-
|
|
1537
|
-
```typescript
|
|
1538
|
-
interface ComponentDefinition<TStringKeys extends string> {
|
|
1539
|
-
readonly id: string;
|
|
1540
|
-
readonly name: string;
|
|
1541
|
-
readonly stringKeys: readonly TStringKeys[];
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
interface ComponentRegistration<TStringKeys extends string, TLanguages extends string> {
|
|
1545
|
-
readonly component: ComponentDefinition<TStringKeys>;
|
|
1546
|
-
readonly strings: PartialComponentLanguageStrings<TStringKeys, TLanguages>;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
interface LanguageDefinition {
|
|
1550
|
-
readonly id: string;
|
|
1551
|
-
readonly name: string;
|
|
1552
|
-
readonly code: string;
|
|
1553
|
-
readonly isDefault?: boolean;
|
|
1554
|
-
}
|
|
1555
|
-
```
|
|
1556
|
-
|
|
1557
|
-
### Legacy I18nEngine (Still Supported)
|
|
1558
|
-
|
|
1559
|
-
#### Constructor
|
|
1560
|
-
|
|
1561
|
-
- `new I18nEngine<TStringKey, TLanguage>(config, key?)` - Create new engine instance
|
|
1562
|
-
|
|
1563
|
-
#### Translation Methods
|
|
1564
|
-
|
|
1565
|
-
- `translate(key, vars?, language?, fallbackLanguage?)` - Translate string with optional variables
|
|
1566
|
-
- `translateEnum(enumObj, value, language)` - Translate enum value
|
|
1567
|
-
- `t(templateString, language?, ...vars)` - Process template string with `{{EnumName.EnumKey}}` patterns
|
|
1568
|
-
|
|
1569
|
-
#### Registration
|
|
1570
|
-
|
|
1571
|
-
- `registerEnum(enumObj, translations, enumName)` - Register enum translations
|
|
1572
|
-
|
|
1573
|
-
#### Language Management
|
|
1574
|
-
|
|
1575
|
-
- `getLanguageCode(language)` - Get language code for language
|
|
1576
|
-
- `getLanguageFromCode(code)` - Get language from code
|
|
1577
|
-
- `getAllLanguageCodes()` - Get all language codes
|
|
1578
|
-
- `getAvailableLanguages()` - Get available languages
|
|
1579
|
-
- `isLanguageAvailable(language)` - Check if language is available
|
|
1580
|
-
|
|
1581
|
-
#### Context Management
|
|
1582
|
-
|
|
1583
|
-
- `get context()` - Get current context
|
|
1584
|
-
- `set context(context)` - Set context properties
|
|
1585
|
-
|
|
1586
|
-
#### Static Methods
|
|
1587
|
-
|
|
1588
|
-
- `getInstance(key?)` - Get instance by key
|
|
1589
|
-
- `clearInstances()` - Clear all instances
|
|
1590
|
-
- `removeInstance(key?)` - Remove specific instance
|
|
1591
|
-
|
|
1592
|
-
### Context Utilities
|
|
1593
|
-
|
|
1594
|
-
- `createContext(defaultLanguage, defaultContext)` - Create new context
|
|
1595
|
-
- `setLanguage(context, language)` - Set user language
|
|
1596
|
-
- `setAdminLanguage(context, language)` - Set admin language
|
|
1597
|
-
- `setContext(context, contextType)` - Set context type
|
|
1598
|
-
|
|
1599
|
-
### ContextManager
|
|
1600
|
-
|
|
1601
|
-
- `addListener(listener)` - Add change listener
|
|
1602
|
-
- `removeListener(listener)` - Remove change listener
|
|
1603
|
-
- `createProxy(context)` - Create reactive context proxy
|
|
1604
|
-
|
|
1605
|
-
### Currency Utilities
|
|
1606
|
-
|
|
1607
|
-
- `getCurrencyFormat(locale, currencyCode)` - Get currency formatting info
|
|
1608
|
-
|
|
1609
|
-
### Type Utilities
|
|
1610
|
-
|
|
1611
|
-
- `createTranslations(translations)` - Helper for creating typed translations
|
|
1612
|
-
|
|
1613
|
-
## Type Definitions
|
|
1614
|
-
|
|
1615
|
-
### Core Types
|
|
1616
|
-
|
|
1617
|
-
```typescript
|
|
1618
|
-
type EnumTranslation<T extends string | number> = {
|
|
1619
|
-
[K in T]: string;
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
type EnumLanguageTranslation<T extends string | number, TLanguage extends string> = Partial<{
|
|
1623
|
-
[L in TLanguage]: EnumTranslation<T>;
|
|
1624
|
-
}>;
|
|
1625
|
-
|
|
1626
|
-
interface I18nConfig<TStringKey, TLanguage, TConstants?, TTranslationContext?> {
|
|
1627
|
-
stringNames: TStringKey[];
|
|
1628
|
-
strings: MasterStringsCollection<TStringKey, TLanguage>;
|
|
1629
|
-
defaultLanguage: TLanguage;
|
|
1630
|
-
defaultTranslationContext: TTranslationContext;
|
|
1631
|
-
defaultCurrencyCode: CurrencyCode;
|
|
1632
|
-
languageCodes: LanguageCodeCollection<TLanguage>;
|
|
1633
|
-
languages: TLanguage[];
|
|
1634
|
-
constants?: TConstants;
|
|
1635
|
-
enumName?: string;
|
|
1636
|
-
enumObj?: Record<string, TStringKey>;
|
|
1637
|
-
timezone: Timezone;
|
|
1638
|
-
adminTimezone: Timezone;
|
|
1639
|
-
}
|
|
1640
|
-
```
|
|
1641
|
-
|
|
1642
|
-
## Testing
|
|
1643
|
-
|
|
1644
|
-
The library includes comprehensive test coverage:
|
|
1645
|
-
|
|
1646
|
-
```bash
|
|
1647
|
-
# Run tests
|
|
1648
|
-
yarn test
|
|
1649
|
-
|
|
1650
|
-
# Run specific test suites
|
|
1651
|
-
yarn test context-manager.spec.ts
|
|
1652
|
-
yarn test enum-registry.spec.ts
|
|
1653
|
-
yarn test i18n-engine.spec.ts
|
|
1654
|
-
```
|
|
1655
|
-
|
|
1656
|
-
### Test Cleanup and Instance Management
|
|
1657
|
-
|
|
1658
|
-
For proper test isolation when using the plugin-based architecture, use the cleanup utilities:
|
|
1659
|
-
|
|
1660
|
-
```typescript
|
|
1661
|
-
import { PluginI18nEngine, resetAllI18nEngines } from '@digitaldefiance/i18n-lib';
|
|
1662
|
-
|
|
1663
|
-
describe('My tests', () => {
|
|
1664
|
-
beforeEach(() => {
|
|
1665
|
-
// Clean up any existing instances before each test
|
|
1666
|
-
PluginI18nEngine.clearAllInstances();
|
|
1667
|
-
});
|
|
1668
|
-
|
|
1669
|
-
afterEach(() => {
|
|
1670
|
-
// Or use the convenience function
|
|
1671
|
-
resetAllI18nEngines();
|
|
1672
|
-
});
|
|
1673
|
-
|
|
1674
|
-
// Or use specific cleanup methods
|
|
1675
|
-
it('should manage instances', () => {
|
|
1676
|
-
const engine1 = PluginI18nEngine.createInstance('app1', [englishLang]);
|
|
1677
|
-
const engine2 = PluginI18nEngine.createInstance('app2', [frenchLang]);
|
|
5
|
+
## Features
|
|
1678
6
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
|
1682
15
|
|
|
1683
|
-
|
|
1684
|
-
PluginI18nEngine.removeInstance('app1');
|
|
1685
|
-
expect(PluginI18nEngine.hasInstance('app1')).toBe(false);
|
|
16
|
+
## Installation
|
|
1686
17
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
});
|
|
18
|
+
```bash
|
|
19
|
+
npm install @digitaldefiance/i18n-lib
|
|
20
|
+
# or
|
|
21
|
+
yarn add @digitaldefiance/i18n-lib
|
|
1692
22
|
```
|
|
1693
23
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
- `PluginI18nEngine.clearAllInstances()` - Remove all engine instances
|
|
1697
|
-
- `PluginI18nEngine.removeInstance(key?)` - Remove specific instance by key (returns boolean)
|
|
1698
|
-
- `PluginI18nEngine.hasInstance(key?)` - Check if instance exists (returns boolean)
|
|
1699
|
-
- `PluginI18nEngine.getInstance(key?)` - Get existing instance (throws RegistryError if not found)
|
|
1700
|
-
- `PluginI18nEngine.resetAll()` - Clear instances and component registrations
|
|
1701
|
-
- `resetAllI18nEngines()` - Convenience function that calls `resetAll()`
|
|
1702
|
-
|
|
1703
|
-
## Extensible Configuration
|
|
1704
|
-
|
|
1705
|
-
The library supports layered extension across multiple libraries using TypeScript module augmentation:
|
|
1706
|
-
|
|
1707
|
-
### Base Library Setup
|
|
24
|
+
## Quick Start
|
|
1708
25
|
|
|
1709
26
|
```typescript
|
|
1710
|
-
|
|
1711
|
-
import { DefaultStringKey, DefaultLanguage } from '@digitaldefiance/i18n-lib';
|
|
27
|
+
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1712
28
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
}
|
|
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
|
+
]);
|
|
1717
34
|
|
|
1718
|
-
//
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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!'
|
|
1723
50
|
}
|
|
1724
51
|
}
|
|
1725
|
-
}
|
|
1726
|
-
```
|
|
1727
|
-
|
|
1728
|
-
### Extending Another Library
|
|
1729
|
-
|
|
1730
|
-
```typescript
|
|
1731
|
-
// In a library that extends the base library
|
|
1732
|
-
import { DefaultStringKey } from '@digitaldefiance/i18n-lib';
|
|
1733
|
-
import { MyLibStringKey } from 'my-base-lib';
|
|
52
|
+
});
|
|
1734
53
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
54
|
+
// Translate
|
|
55
|
+
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
|
|
56
|
+
// Output: "Welcome to MyApp!"
|
|
1739
57
|
|
|
1740
|
-
//
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
StringKey: DefaultStringKey | MyLibStringKey | AdvancedStringKey;
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
58
|
+
// Switch language
|
|
59
|
+
engine.setLanguage(LanguageCodes.FR);
|
|
60
|
+
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
|
|
61
|
+
// Output: "Bienvenue sur MyApp!"
|
|
1748
62
|
```
|
|
1749
63
|
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
```typescript
|
|
1753
|
-
// In your final application
|
|
1754
|
-
import { StringKey, Language, getI18nEngine } from 'my-extended-lib';
|
|
1755
|
-
|
|
1756
|
-
// All string keys from all layers are now available
|
|
1757
|
-
const engine = getI18nEngine();
|
|
1758
|
-
const translation = engine.translate('advanced_export' as StringKey);
|
|
1759
|
-
```
|
|
64
|
+
## Core Concepts
|
|
1760
65
|
|
|
1761
|
-
###
|
|
66
|
+
### PluginI18nEngine
|
|
1762
67
|
|
|
1763
|
-
|
|
68
|
+
The main engine class that manages translations, languages, and components.
|
|
1764
69
|
|
|
1765
70
|
```typescript
|
|
1766
|
-
import {
|
|
1767
|
-
|
|
1768
|
-
// Create engine with default configuration
|
|
1769
|
-
const engine = getDefaultI18nEngine(
|
|
1770
|
-
{ APP_NAME: 'MyApp' }, // constants
|
|
1771
|
-
new Timezone('America/New_York'), // user timezone
|
|
1772
|
-
new Timezone('UTC') // admin timezone
|
|
1773
|
-
);
|
|
1774
|
-
```
|
|
71
|
+
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1775
72
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
The engine automatically validates configurations during construction to ensure completeness:
|
|
1779
|
-
|
|
1780
|
-
### Validation Rules
|
|
73
|
+
// Create instance
|
|
74
|
+
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
1781
75
|
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
76
|
+
// Or use constructor
|
|
77
|
+
const engine = new PluginI18nEngine(languages, config);
|
|
78
|
+
```
|
|
1785
79
|
|
|
1786
|
-
###
|
|
80
|
+
### Component Registration
|
|
1787
81
|
|
|
1788
|
-
|
|
82
|
+
Components group related translations together:
|
|
1789
83
|
|
|
1790
84
|
```typescript
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
const config: I18nConfig<MyStrings, MyLanguages> = {
|
|
1800
|
-
stringNames: Object.values(MyStrings),
|
|
85
|
+
engine.registerComponent({
|
|
86
|
+
component: {
|
|
87
|
+
id: 'auth',
|
|
88
|
+
name: 'Authentication',
|
|
89
|
+
stringKeys: ['login', 'logout', 'error']
|
|
90
|
+
},
|
|
1801
91
|
strings: {
|
|
1802
|
-
[
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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'
|
|
1807
101
|
}
|
|
1808
102
|
},
|
|
1809
|
-
|
|
1810
|
-
};
|
|
103
|
+
aliases: ['authentication'] // Optional aliases
|
|
104
|
+
});
|
|
1811
105
|
```
|
|
1812
106
|
|
|
1813
|
-
###
|
|
1814
|
-
|
|
1815
|
-
If localized error messages aren't provided, the engine falls back to English templates:
|
|
1816
|
-
|
|
1817
|
-
- `Missing string collection for language: {language}`
|
|
1818
|
-
- `Missing translation for key '{key}' in language '{language}'`
|
|
1819
|
-
- `Default language '{language}' has no string collection`
|
|
1820
|
-
|
|
1821
|
-
## Error Handling
|
|
1822
|
-
|
|
1823
|
-
The library provides comprehensive error handling with localized error messages:
|
|
1824
|
-
|
|
1825
|
-
### Registry Errors
|
|
107
|
+
### Translation
|
|
1826
108
|
|
|
1827
109
|
```typescript
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
try {
|
|
1831
|
-
engine.registerComponent(invalidRegistration);
|
|
1832
|
-
} catch (error) {
|
|
1833
|
-
if (error instanceof RegistryError) {
|
|
1834
|
-
console.error(`Error type: ${error.type}`);
|
|
1835
|
-
console.error(`Metadata:`, error.metadata);
|
|
1836
|
-
|
|
1837
|
-
switch (error.type) {
|
|
1838
|
-
case RegistryErrorType.ComponentNotFound:
|
|
1839
|
-
// Handle missing component
|
|
1840
|
-
break;
|
|
1841
|
-
case RegistryErrorType.DuplicateComponent:
|
|
1842
|
-
// Handle duplicate registration
|
|
1843
|
-
break;
|
|
1844
|
-
case RegistryErrorType.ValidationFailed:
|
|
1845
|
-
// Handle validation failure
|
|
1846
|
-
break;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
```
|
|
110
|
+
// Simple translation
|
|
111
|
+
const text = engine.translate('auth', 'login');
|
|
1851
112
|
|
|
1852
|
-
|
|
113
|
+
// With variables
|
|
114
|
+
const greeting = engine.translate('app', 'welcome', { name: 'John' });
|
|
1853
115
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
- `StringKeyNotFound`: Translation key not found
|
|
1857
|
-
- `IncompleteRegistration`: Missing translations detected
|
|
1858
|
-
- `DuplicateComponent`: Component already registered
|
|
1859
|
-
- `DuplicateLanguage`: Language already registered
|
|
1860
|
-
- `ValidationFailed`: Validation check failed
|
|
116
|
+
// Specific language
|
|
117
|
+
const french = engine.translate('auth', 'login', {}, LanguageCodes.FR);
|
|
1861
118
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
```typescript
|
|
1865
|
-
import { ContextError, ContextErrorType } from '@digitaldefiance/i18n-lib';
|
|
1866
|
-
|
|
1867
|
-
try {
|
|
1868
|
-
const context = globalContext.getContext('invalid-key');
|
|
1869
|
-
} catch (error) {
|
|
1870
|
-
if (error instanceof ContextError) {
|
|
1871
|
-
console.error(`Invalid context: ${error.contextKey}`);
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
119
|
+
// Safe translation (returns fallback on error)
|
|
120
|
+
const safe = engine.safeTranslate('missing', 'key'); // Returns "[missing.key]"
|
|
1874
121
|
```
|
|
1875
122
|
|
|
1876
|
-
###
|
|
1877
|
-
|
|
1878
|
-
Use `safeTranslate` to prevent errors:
|
|
123
|
+
### Template Processing
|
|
1879
124
|
|
|
1880
125
|
```typescript
|
|
1881
|
-
//
|
|
1882
|
-
|
|
1883
|
-
const text = engine.translate('component', 'missingKey');
|
|
1884
|
-
} catch (error) {
|
|
1885
|
-
// Handle error
|
|
1886
|
-
}
|
|
126
|
+
// Component references: {{componentId.stringKey}}
|
|
127
|
+
engine.t('Click {{auth.login}} to continue');
|
|
1887
128
|
|
|
1888
|
-
//
|
|
1889
|
-
|
|
1890
|
-
// Returns: "[component.missingKey]"
|
|
1891
|
-
```
|
|
1892
|
-
|
|
1893
|
-
## Validation
|
|
129
|
+
// Variables: {variableName}
|
|
130
|
+
engine.t('Hello, {username}!', { username: 'Alice' });
|
|
1894
131
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
```typescript
|
|
1898
|
-
// Validate during registration
|
|
1899
|
-
const result = engine.registerComponent(registration);
|
|
1900
|
-
|
|
1901
|
-
if (!result.isValid) {
|
|
1902
|
-
console.error('Validation errors:', result.errors);
|
|
1903
|
-
console.warn('Missing keys:', result.missingKeys);
|
|
1904
|
-
|
|
1905
|
-
result.missingKeys.forEach(missing => {
|
|
1906
|
-
console.log(`Missing: ${missing.stringKey} for ${missing.languageId} in ${missing.componentId}`);
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
132
|
+
// Mixed
|
|
133
|
+
engine.t('{{auth.login}}: {username}', { username: 'admin' });
|
|
1909
134
|
```
|
|
1910
135
|
|
|
1911
|
-
###
|
|
136
|
+
### Language Management
|
|
1912
137
|
|
|
1913
138
|
```typescript
|
|
1914
|
-
//
|
|
1915
|
-
|
|
139
|
+
// Set current language
|
|
140
|
+
engine.setLanguage(LanguageCodes.FR);
|
|
1916
141
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
142
|
+
// Get current language
|
|
143
|
+
const lang = engine.getCurrentLanguage();
|
|
144
|
+
|
|
145
|
+
// Check if language exists
|
|
146
|
+
if (engine.hasLanguage(LanguageCodes.ES)) {
|
|
147
|
+
engine.setLanguage(LanguageCodes.ES);
|
|
1920
148
|
}
|
|
1921
149
|
|
|
1922
|
-
//
|
|
1923
|
-
|
|
1924
|
-
// Warnings: ["Component 'user' missing key 'greeting' for language 'fr'"]
|
|
150
|
+
// Get all languages
|
|
151
|
+
const languages = engine.getLanguages();
|
|
1925
152
|
```
|
|
1926
153
|
|
|
1927
|
-
###
|
|
154
|
+
### Admin Context
|
|
1928
155
|
|
|
1929
|
-
|
|
1930
|
-
import { ValidationConfig } from '@digitaldefiance/i18n-lib';
|
|
156
|
+
Separate language for admin interfaces:
|
|
1931
157
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
fallbackLanguageId: 'en-US'
|
|
1936
|
-
};
|
|
158
|
+
```typescript
|
|
159
|
+
// Set admin language
|
|
160
|
+
engine.setAdminLanguage(LanguageCodes.EN_US);
|
|
1937
161
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
allowPartialRegistration: true,
|
|
1941
|
-
fallbackLanguageId: 'en-US'
|
|
1942
|
-
};
|
|
162
|
+
// Switch to admin context
|
|
163
|
+
engine.switchToAdmin();
|
|
1943
164
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
});
|
|
165
|
+
// Switch back to user context
|
|
166
|
+
engine.switchToUser();
|
|
1947
167
|
```
|
|
1948
168
|
|
|
1949
|
-
##
|
|
169
|
+
## Core System Strings
|
|
1950
170
|
|
|
1951
|
-
|
|
171
|
+
Pre-built translations for common UI elements:
|
|
1952
172
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
3. **Complete Component Translations**: Provide translations for all supported languages in each component
|
|
1956
|
-
4. **Validate Registrations**: Check validation results and handle missing translations appropriately
|
|
1957
|
-
5. **Use Core Components**: Start with `createCoreI18nEngine()` for built-in system strings
|
|
1958
|
-
6. **Fallback Strategy**: Configure appropriate fallback languages for missing translations
|
|
1959
|
-
7. **Component Isolation**: Keep related strings together in the same component
|
|
1960
|
-
8. **Template Variables**: Use template strings with variables for dynamic content
|
|
1961
|
-
9. **Multi-Instance Architecture**: Use named instances for different application contexts (admin, user, etc.)
|
|
1962
|
-
|
|
1963
|
-
### Legacy System (Still Supported)
|
|
1964
|
-
|
|
1965
|
-
1. **Complete Translations**: EnumTranslation requires all enum values to be translated
|
|
1966
|
-
2. **Type Safety**: Use TypeScript enums for string keys and languages
|
|
1967
|
-
3. **Context Separation**: Use different contexts for admin and user interfaces
|
|
1968
|
-
4. **Instance Management**: Use named instances for different parts of your application
|
|
1969
|
-
5. **Error Handling**: Handle missing translations gracefully with fallback languages
|
|
1970
|
-
6. **Layered Extension**: Use union types when extending configurations across libraries
|
|
1971
|
-
7. **Default Configuration**: Use `getDefaultI18nEngine()` for standard setups
|
|
1972
|
-
|
|
1973
|
-
### Migration Strategy
|
|
173
|
+
```typescript
|
|
174
|
+
import { getCoreI18nEngine, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
|
|
1974
175
|
|
|
1975
|
-
|
|
176
|
+
const coreEngine = getCoreI18nEngine();
|
|
1976
177
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
const
|
|
1980
|
-
const text = legacy.translate(MyStrings.Welcome);
|
|
1981
|
-
|
|
1982
|
-
// New plugin approach
|
|
1983
|
-
const modern = createCoreI18nEngine();
|
|
1984
|
-
modern.registerComponent(myComponentRegistration);
|
|
1985
|
-
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);
|
|
1986
181
|
```
|
|
1987
182
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
## Performance Considerations
|
|
183
|
+
Available core string categories:
|
|
1991
184
|
|
|
1992
|
-
|
|
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.
|
|
1993
188
|
|
|
1994
|
-
|
|
1995
|
-
- Reuse instances rather than creating new ones
|
|
1996
|
-
- Clean up instances in tests to prevent memory leaks
|
|
189
|
+
## Multiple Instances
|
|
1997
190
|
|
|
1998
|
-
|
|
191
|
+
Create isolated engines for different parts of your application:
|
|
1999
192
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
193
|
+
```typescript
|
|
194
|
+
// Admin engine
|
|
195
|
+
const adminEngine = PluginI18nEngine.createInstance('admin', adminLanguages);
|
|
2003
196
|
|
|
2004
|
-
|
|
197
|
+
// User engine
|
|
198
|
+
const userEngine = PluginI18nEngine.createInstance('user', userLanguages);
|
|
2005
199
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
- Validation runs only during registration, not translation
|
|
200
|
+
// Get instance by key
|
|
201
|
+
const admin = PluginI18nEngine.getInstance('admin');
|
|
2009
202
|
|
|
2010
|
-
|
|
203
|
+
// Check if instance exists
|
|
204
|
+
if (PluginI18nEngine.hasInstance('admin')) {
|
|
205
|
+
// ...
|
|
206
|
+
}
|
|
2011
207
|
|
|
2012
|
-
|
|
208
|
+
// Remove instance
|
|
209
|
+
PluginI18nEngine.removeInstance('admin');
|
|
2013
210
|
|
|
2014
|
-
|
|
211
|
+
// Reset all instances
|
|
212
|
+
PluginI18nEngine.resetAll();
|
|
213
|
+
```
|
|
2015
214
|
|
|
2016
|
-
|
|
2017
|
-
- All translations are stored locally
|
|
2018
|
-
- No telemetry or analytics
|
|
2019
|
-
- Suitable for sensitive applications
|
|
215
|
+
## Error Handling
|
|
2020
216
|
|
|
2021
|
-
###
|
|
217
|
+
### RegistryError
|
|
2022
218
|
|
|
2023
|
-
|
|
219
|
+
Errors related to component/language registration:
|
|
2024
220
|
|
|
2025
221
|
```typescript
|
|
2026
|
-
import {
|
|
2027
|
-
|
|
2028
|
-
// BAD: Direct user input
|
|
2029
|
-
const message = engine.translate('component', 'template', {
|
|
2030
|
-
userInput: req.body.unsafeInput
|
|
2031
|
-
});
|
|
222
|
+
import { RegistryError, RegistryErrorType } from '@digitaldefiance/i18n-lib';
|
|
2032
223
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
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
|
+
}
|
|
2037
233
|
```
|
|
2038
234
|
|
|
2039
|
-
###
|
|
235
|
+
### TranslatableError
|
|
2040
236
|
|
|
2041
|
-
|
|
237
|
+
Base class for errors with translated messages:
|
|
2042
238
|
|
|
2043
239
|
```typescript
|
|
2044
|
-
|
|
2045
|
-
throw new TranslatableGenericError(
|
|
2046
|
-
'auth',
|
|
2047
|
-
'loginFailed',
|
|
2048
|
-
{ password: user.password }, // Don't include sensitive data
|
|
2049
|
-
language
|
|
2050
|
-
);
|
|
2051
|
-
|
|
2052
|
-
// GOOD: Safe error message
|
|
2053
|
-
throw new TranslatableGenericError(
|
|
2054
|
-
'auth',
|
|
2055
|
-
'loginFailed',
|
|
2056
|
-
{ username: user.username },
|
|
2057
|
-
language,
|
|
2058
|
-
{ userId: user.id } // Metadata for logging only
|
|
2059
|
-
);
|
|
2060
|
-
```
|
|
2061
|
-
|
|
2062
|
-
## Mongoose Integration
|
|
240
|
+
import { TranslatableError, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
|
|
2063
241
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
// ['en-US', 'en-GB', 'fr', 'es', 'de', 'zh-CN', 'ja', 'uk']
|
|
2073
|
-
|
|
2074
|
-
const userSchema = new Schema({
|
|
2075
|
-
language: {
|
|
2076
|
-
type: String,
|
|
2077
|
-
enum: supportedLanguages,
|
|
2078
|
-
default: LanguageCodes.EN_US,
|
|
2079
|
-
required: true
|
|
2080
|
-
},
|
|
2081
|
-
adminLanguage: {
|
|
2082
|
-
type: String,
|
|
2083
|
-
enum: supportedLanguages,
|
|
2084
|
-
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
|
+
);
|
|
2085
250
|
}
|
|
2086
|
-
}
|
|
251
|
+
}
|
|
2087
252
|
|
|
2088
|
-
|
|
253
|
+
throw new MyError(LanguageCodes.FR); // Throws with French error message
|
|
2089
254
|
```
|
|
2090
255
|
|
|
2091
|
-
|
|
256
|
+
## Translation Adapter
|
|
257
|
+
|
|
258
|
+
Adapt PluginI18nEngine to simpler TranslationEngine interface:
|
|
2092
259
|
|
|
2093
260
|
```typescript
|
|
2094
|
-
import {
|
|
2095
|
-
|
|
2096
|
-
// Ensure the engine has registered its languages
|
|
2097
|
-
const engine = PluginI18nEngine.getInstance();
|
|
2098
|
-
const languageEnum = LanguageRegistry.getLanguageIds();
|
|
2099
|
-
const defaultLanguage =
|
|
2100
|
-
LanguageRegistry.getDefaultLanguageId() ?? engine.getContext().language;
|
|
2101
|
-
|
|
2102
|
-
const contentSchema = new Schema({
|
|
2103
|
-
language: {
|
|
2104
|
-
type: String,
|
|
2105
|
-
enum: languageEnum,
|
|
2106
|
-
default: defaultLanguage
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
```
|
|
261
|
+
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
2110
262
|
|
|
2111
|
-
|
|
263
|
+
const adapter = createTranslationAdapter(engine, 'componentId');
|
|
2112
264
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
const displayNames = LanguageRegistry.getLanguageDisplayNames();
|
|
2117
|
-
|
|
2118
|
-
const settingsSchema = new Schema({
|
|
2119
|
-
language: {
|
|
2120
|
-
type: String,
|
|
2121
|
-
enum: Object.keys(displayNames),
|
|
2122
|
-
validate: {
|
|
2123
|
-
validator: (v: string) => v in displayNames,
|
|
2124
|
-
message: (props) => {
|
|
2125
|
-
const validLanguages = Object.entries(displayNames)
|
|
2126
|
-
.map(([code, name]) => `${name} (${code})`)
|
|
2127
|
-
.join(', ');
|
|
2128
|
-
return `${props.value} is not a valid language. Valid options: ${validLanguages}`;
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
265
|
+
// Use adapter where TranslationEngine is expected
|
|
266
|
+
const message = adapter.translate('key', { var: 'value' });
|
|
2133
267
|
```
|
|
2134
268
|
|
|
2135
|
-
##
|
|
269
|
+
## Language Codes
|
|
2136
270
|
|
|
2137
|
-
|
|
271
|
+
Built-in language codes following BCP 47 standard:
|
|
2138
272
|
|
|
2139
273
|
```typescript
|
|
2140
|
-
import {
|
|
2141
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2142
|
-
|
|
2143
|
-
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2144
|
-
|
|
2145
|
-
function i18nMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
2146
|
-
// Get language from header, query, or cookie
|
|
2147
|
-
const language = (req.headers['accept-language'] ||
|
|
2148
|
-
req.query.lang ||
|
|
2149
|
-
req.cookies.language ||
|
|
2150
|
-
'en-US') as CoreLanguageCode;
|
|
2151
|
-
|
|
2152
|
-
// Set language for this request (type-safe)
|
|
2153
|
-
engine.setLanguage(language);
|
|
2154
|
-
|
|
2155
|
-
// Add translation helper to response locals
|
|
2156
|
-
res.locals.t = (componentId: string, key: string, vars?: any) => {
|
|
2157
|
-
return engine.translate(componentId, key, vars, language);
|
|
2158
|
-
};
|
|
2159
|
-
|
|
2160
|
-
next();
|
|
2161
|
-
}
|
|
274
|
+
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
2162
275
|
|
|
2163
|
-
|
|
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'
|
|
2164
284
|
```
|
|
2165
285
|
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
```typescript
|
|
2169
|
-
import React, { createContext, useContext, useState } from 'react';
|
|
2170
|
-
import { PluginI18nEngine, CoreLanguageCode, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
2171
|
-
|
|
2172
|
-
const I18nContext = createContext<{
|
|
2173
|
-
engine: PluginI18nEngine<CoreLanguageCode>;
|
|
2174
|
-
language: CoreLanguageCode;
|
|
2175
|
-
setLanguage: (lang: CoreLanguageCode) => void;
|
|
2176
|
-
} | null>(null);
|
|
2177
|
-
|
|
2178
|
-
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
|
2179
|
-
const [engine] = useState(() => PluginI18nEngine.getInstance<CoreLanguageCode>());
|
|
2180
|
-
const [language, setLanguageState] = useState<CoreLanguageCode>(LanguageCodes.EN_US);
|
|
2181
|
-
|
|
2182
|
-
const setLanguage = (lang: CoreLanguageCode) => {
|
|
2183
|
-
engine.setLanguage(lang);
|
|
2184
|
-
setLanguageState(lang);
|
|
2185
|
-
};
|
|
2186
|
-
|
|
2187
|
-
return (
|
|
2188
|
-
<I18nContext.Provider value={{ engine, language, setLanguage }}>
|
|
2189
|
-
{children}
|
|
2190
|
-
</I18nContext.Provider>
|
|
2191
|
-
);
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
export function useI18n() {
|
|
2195
|
-
const context = useContext(I18nContext);
|
|
2196
|
-
if (!context) throw new Error('useI18n must be used within I18nProvider');
|
|
2197
|
-
|
|
2198
|
-
const t = (componentId: string, key: string, vars?: any) => {
|
|
2199
|
-
return context.engine.translate(componentId, key, vars, context.language);
|
|
2200
|
-
};
|
|
2201
|
-
|
|
2202
|
-
return { ...context, t };
|
|
2203
|
-
}
|
|
286
|
+
## API Reference
|
|
2204
287
|
|
|
2205
|
-
|
|
2206
|
-
function MyComponent() {
|
|
2207
|
-
const { t, language, setLanguage } = useI18n();
|
|
2208
|
-
|
|
2209
|
-
return (
|
|
2210
|
-
<div>
|
|
2211
|
-
<h1>{t('core', CoreStringKey.System_Welcome)}</h1>
|
|
2212
|
-
<button onClick={() => setLanguage('fr')}>
|
|
2213
|
-
Switch to French
|
|
2214
|
-
</button>
|
|
2215
|
-
</div>
|
|
2216
|
-
);
|
|
2217
|
-
}
|
|
2218
|
-
```
|
|
288
|
+
### PluginI18nEngine
|
|
2219
289
|
|
|
2220
|
-
|
|
290
|
+
**Static Methods**
|
|
2221
291
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
install(app, options) {
|
|
2228
|
-
const engine = PluginI18nEngine.getInstance<CoreLanguageCode>();
|
|
2229
|
-
|
|
2230
|
-
app.config.globalProperties.$t = (
|
|
2231
|
-
componentId: string,
|
|
2232
|
-
key: string,
|
|
2233
|
-
vars?: any
|
|
2234
|
-
) => {
|
|
2235
|
-
return engine.translate(componentId, key, vars);
|
|
2236
|
-
};
|
|
2237
|
-
|
|
2238
|
-
app.config.globalProperties.$i18n = engine;
|
|
2239
|
-
}
|
|
2240
|
-
};
|
|
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
|
|
2241
297
|
|
|
2242
|
-
|
|
298
|
+
**Instance Methods**
|
|
2243
299
|
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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
|
|
2249
312
|
|
|
2250
|
-
|
|
313
|
+
### Core Functions
|
|
2251
314
|
|
|
2252
|
-
|
|
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
|
|
2253
321
|
|
|
2254
|
-
|
|
322
|
+
## Testing
|
|
2255
323
|
|
|
2256
324
|
```typescript
|
|
2257
|
-
|
|
2258
|
-
const engine = PluginI18nEngine.createInstance('myapp', languages);
|
|
2259
|
-
// Or use default instance
|
|
2260
|
-
const engine = PluginI18nEngine.getInstance(); // Uses 'default' key
|
|
2261
|
-
```
|
|
2262
|
-
|
|
2263
|
-
**Issue: "Component 'X' not found"**
|
|
325
|
+
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
|
|
2264
326
|
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
```
|
|
327
|
+
describe('My Tests', () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
PluginI18nEngine.resetAll();
|
|
330
|
+
});
|
|
2270
331
|
|
|
2271
|
-
|
|
332
|
+
afterEach(() => {
|
|
333
|
+
PluginI18nEngine.resetAll();
|
|
334
|
+
});
|
|
2272
335
|
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
engine.
|
|
2276
|
-
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
|
+
});
|
|
2277
342
|
```
|
|
2278
343
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
```typescript
|
|
2282
|
-
// Solution: Use validation to catch missing translations
|
|
2283
|
-
const validation = engine.validateAllComponents();
|
|
2284
|
-
if (!validation.isValid) {
|
|
2285
|
-
console.error('Missing translations:', validation.errors);
|
|
2286
|
-
// Fix translations before deploying
|
|
2287
|
-
}
|
|
2288
|
-
```
|
|
344
|
+
## TypeScript Support
|
|
2289
345
|
|
|
2290
|
-
|
|
346
|
+
Full TypeScript support with generic types:
|
|
2291
347
|
|
|
2292
348
|
```typescript
|
|
2293
|
-
//
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
}
|
|
349
|
+
// Type-safe language codes
|
|
350
|
+
type MyLanguages = 'en-US' | 'fr' | 'es';
|
|
351
|
+
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', languages);
|
|
2297
352
|
|
|
2298
|
-
//
|
|
2299
|
-
|
|
353
|
+
// Type-safe string keys
|
|
354
|
+
enum MyStringKeys {
|
|
355
|
+
Welcome = 'welcome',
|
|
356
|
+
Goodbye = 'goodbye'
|
|
357
|
+
}
|
|
2300
358
|
|
|
2301
|
-
//
|
|
2302
|
-
|
|
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
|
+
};
|
|
2303
377
|
```
|
|
2304
378
|
|
|
2305
|
-
##
|
|
379
|
+
## Browser Support
|
|
2306
380
|
|
|
2307
|
-
|
|
381
|
+
- Chrome/Edge: Latest 2 versions
|
|
382
|
+
- Firefox: Latest 2 versions
|
|
383
|
+
- Safari: Latest 2 versions
|
|
384
|
+
- Node.js: 18+
|
|
2308
385
|
|
|
2309
|
-
##
|
|
386
|
+
## License
|
|
2310
387
|
|
|
2311
|
-
|
|
388
|
+
MIT License - See LICENSE file for details
|
|
2312
389
|
|
|
2313
390
|
## Contributing
|
|
2314
391
|
|
|
2315
|
-
Contributions
|
|
392
|
+
Contributions welcome! Please:
|
|
2316
393
|
|
|
2317
394
|
1. Fork the repository
|
|
2318
395
|
2. Create a feature branch
|
|
2319
396
|
3. Add tests for new functionality
|
|
2320
|
-
4. Ensure all tests pass
|
|
397
|
+
4. Ensure all tests pass (`npm test`)
|
|
2321
398
|
5. Submit a pull request
|
|
2322
399
|
|
|
2323
|
-
|
|
400
|
+
## Support
|
|
2324
401
|
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
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
|
|
2329
405
|
|
|
2330
|
-
|
|
2331
|
-
yarn install
|
|
406
|
+
## ChangeLog
|
|
2332
407
|
|
|
2333
|
-
|
|
2334
|
-
yarn test
|
|
408
|
+
### Version 2.0.0
|
|
2335
409
|
|
|
2336
|
-
|
|
2337
|
-
yarn build
|
|
2338
|
-
```
|
|
410
|
+
**Major Release** - Architecture improvements and bug fixes
|
|
2339
411
|
|
|
2340
|
-
|
|
412
|
+
**Fixed**:
|
|
2341
413
|
|
|
2342
|
-
|
|
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
|
|
2343
421
|
|
|
2344
|
-
|
|
2345
|
-
- Documentation: See README.md and inline code documentation
|
|
2346
|
-
- Examples: See `examples/` directory in repository
|
|
422
|
+
**Improved**:
|
|
2347
423
|
|
|
2348
|
-
|
|
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
|
|
445
|
+
|
|
446
|
+
### Version 1.3.27
|
|
447
|
+
|
|
448
|
+
- Version bump
|
|
449
|
+
- Fix plugin engine to pass constants to templates as legacy engine did
|
|
2349
450
|
|
|
2350
451
|
### Version 1.3.20
|
|
2351
452
|
|
|
@@ -2429,7 +530,7 @@ For issues, questions, or contributions:
|
|
|
2429
530
|
|
|
2430
531
|
### Version 1.2.5
|
|
2431
532
|
|
|
2432
|
-
- Sat Oct 25 2025 15:
|
|
533
|
+
- Sat Oct 25 2025 15:01:00 GMT-0700 (Pacific Daylight Time)
|
|
2433
534
|
|
|
2434
535
|
#### Added
|
|
2435
536
|
|
|
@@ -2438,6 +539,7 @@ For issues, questions, or contributions:
|
|
|
2438
539
|
- Provides graceful error handling with fallback to key strings
|
|
2439
540
|
- Zero overhead - direct delegation to underlying `PluginI18nEngine`
|
|
2440
541
|
- Comprehensive test coverage (19 tests)
|
|
542
|
+
- Supports both full TranslationEngine interface and simplified bound interface
|
|
2441
543
|
|
|
2442
544
|
#### Benefits
|
|
2443
545
|
|
|
@@ -2452,7 +554,8 @@ Packages using custom translation adapters can now replace them with:
|
|
|
2452
554
|
```typescript
|
|
2453
555
|
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
|
|
2454
556
|
const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
2455
|
-
|
|
557
|
+
```
|
|
558
|
+
|
|
2456
559
|
### Version 1.2.4
|
|
2457
560
|
|
|
2458
561
|
- Sat Oct 25 2025 14:29:00 GMT-0700 (Pacific Daylight Time)
|
|
@@ -2476,6 +579,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2476
579
|
- Updated test mocks to implement both required `translate` and `safeTranslate` methods
|
|
2477
580
|
|
|
2478
581
|
**Breaking Changes:**
|
|
582
|
+
|
|
2479
583
|
- Any code implementing `TranslationEngine` must now provide both `translate` and `safeTranslate` methods (previously optional)
|
|
2480
584
|
- `TranslationEngine` now requires explicit type parameter when used (e.g., `TranslationEngine<EciesStringKey>`)
|
|
2481
585
|
|
|
@@ -2489,6 +593,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2489
593
|
- Thu Oct 23 2025 14:13:00 GMT-0700 (Pacific Daylight Time)
|
|
2490
594
|
|
|
2491
595
|
#### Breaking Changes
|
|
596
|
+
|
|
2492
597
|
- **Removed `CoreLanguage` enum** - Replaced with `CoreLanguageCode` type and `LanguageCodes` constants
|
|
2493
598
|
- **Language identifiers now use BCP 47 codes** - Changed from descriptive names (e.g., `'English (US)'`) to standard codes (e.g., `'en-US'`)
|
|
2494
599
|
- **API changes**:
|
|
@@ -2497,6 +602,7 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2497
602
|
- All language references updated to use `LanguageCodes` constants
|
|
2498
603
|
|
|
2499
604
|
#### Added
|
|
605
|
+
|
|
2500
606
|
- **`LanguageCodes` constants object** - Provides standard BCP 47 language codes:
|
|
2501
607
|
- `EN_US`, `EN_GB`, `FR`, `ES`, `DE`, `ZH_CN`, `JA`, `UK`
|
|
2502
608
|
- **`LanguageDisplayNames` mapping** - Maps language codes to human-readable names
|
|
@@ -2505,11 +611,13 @@ const adapter = createTranslationAdapter(pluginEngine, 'component-id');
|
|
|
2505
611
|
- **Custom language code support** - Any string can now be used as a language code
|
|
2506
612
|
|
|
2507
613
|
#### Changed
|
|
614
|
+
|
|
2508
615
|
- **Language code format** - All language identifiers now use BCP 47 standard (e.g., `'en-US'` instead of `'English (US)'`)
|
|
2509
616
|
- **Type system** - Languages are now string-based types instead of enums, allowing custom language codes
|
|
2510
617
|
- **Documentation** - Updated README with new API usage examples and language code constants
|
|
2511
618
|
|
|
2512
619
|
#### Migration Guide
|
|
620
|
+
|
|
2513
621
|
```typescript
|
|
2514
622
|
// Before (v1.1.x)
|
|
2515
623
|
import { CoreLanguage } from '@digitaldefiance/i18n-lib';
|
|
@@ -2543,12 +651,7 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2543
651
|
|
|
2544
652
|
- Wed Oct 15 2025 16:13:00 GMT-0700 (Pacific Daylight Time)
|
|
2545
653
|
**Fixed:**
|
|
2546
|
-
- Corrected safeCoreTranslation fallback format to use
|
|
2547
|
-
|
|
2548
|
-
```plaintext
|
|
2549
|
-
[CoreStringKey.${stringKey}]
|
|
2550
|
-
```
|
|
2551
|
-
|
|
654
|
+
- Corrected safeCoreTranslation fallback format to use `[CoreStringKey.${stringKey}]`
|
|
2552
655
|
- Fixed import issues with DefaultInstanceKey
|
|
2553
656
|
**Added:**
|
|
2554
657
|
- New TranslatableGenericError class for generic translatable errors across any component
|
|
@@ -2556,12 +659,7 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2556
659
|
- 130+ new tests for comprehensive coverage
|
|
2557
660
|
- Complete usage documentation
|
|
2558
661
|
**Changed:**
|
|
2559
|
-
- Standardized all fallback formats to use square brackets
|
|
2560
|
-
|
|
2561
|
-
```plaintext
|
|
2562
|
-
[componentId.stringKey]
|
|
2563
|
-
```
|
|
2564
|
-
|
|
662
|
+
- Standardized all fallback formats to use square brackets `[componentId.stringKey]`
|
|
2565
663
|
- Refactored to use CoreI18nComponentId constant
|
|
2566
664
|
All tests pass and backward compatibility is maintained.
|
|
2567
665
|
|
|
@@ -2613,3 +711,86 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2613
711
|
|
|
2614
712
|
- Wed Sep 24 2025 15:20:07 GMT-0700 (Pacific Daylight Time)
|
|
2615
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)
|