A production-ready TypeScript internationalization library with component-based architecture, type-safe translations, and comprehensive error handling.
Part of Express Suite
- Production-Grade Security: Comprehensive protection against common attacks
- Prototype pollution prevention
- ReDoS (Regular Expression Denial of Service) mitigation
- XSS (Cross-Site Scripting) protection with HTML escaping
- Input validation with configurable limits
- Bounded resource usage (cache, recursion, input length)
- ICU MessageFormat: Industry-standard message formatting with plural, select, date/time/number formatting
- Component-Based Architecture: Register translation components with full type safety
- 37 Supported Languages: CLDR-compliant plural rules for world's most complex languages
- Pluralization Support: Automatic plural form selection based on count (one/few/many/other)
- Gender Support: Gender-aware translations (male/female/neutral/other)
- Advanced Number Formatting: Thousand separators, currency, percent with decimal precision
- 8 Built-in Languages: English (US/UK), French, Spanish, German, Chinese, Japanese, Ukrainian
- Advanced Template Processing:
- Component references:
{{Component.key}} - Alias resolution:
{{Alias.key}} - Enum name resolution:
{{EnumName.value}} - Variable substitution:
{variable} - Context variables:
{currency},{timezone},{language}
- Component references:
- Context Integration: Automatic injection of currency, timezone, and language from GlobalActiveContext
- Smart Object Handling: CurrencyCode and Timezone objects automatically extract values
- Multiple Instances: Create isolated i18n engines for different contexts
- Fluent Builder: I18nBuilder for clean, chainable engine configuration
- Core System Strings: Pre-built translations for common UI elements and errors
- Type Safety: Full TypeScript support with generic types
- Error Handling: Comprehensive error classes with translation support and ICU formatting
- 93.22% Test Coverage: 1,738 tests covering all features
- Security Hardened: See SECURITY.md for details
npm install @digitaldefiance/i18n-lib
# or
yarn add @digitaldefiance/i18n-libimport { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
// Create engine with languages
const engine = PluginI18nEngine.createInstance('myapp', [
{ id: LanguageCodes.EN_US, name: 'English (US)', code: 'en-US', isDefault: true },
{ id: LanguageCodes.FR, name: 'Français', code: 'fr' }
]);
// Register component with translations
engine.registerComponent({
component: {
id: 'app',
name: 'Application',
stringKeys: ['welcome', 'goodbye']
},
strings: {
[LanguageCodes.EN_US]: {
welcome: 'Welcome to {appName}!',
goodbye: 'Goodbye!'
},
[LanguageCodes.FR]: {
welcome: 'Bienvenue sur {appName}!',
goodbye: 'Au revoir!'
}
}
});
// Translate
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
// Output: "Welcome to MyApp!"
// Switch language
engine.setLanguage(LanguageCodes.FR);
console.log(engine.translate('app', 'welcome', { appName: 'MyApp' }));
// Output: "Bienvenue sur MyApp!"
// Pluralization (automatic form selection)
engine.register({
id: 'cart',
strings: {
'en-US': {
items: {
one: '1 item',
other: '{count} items'
}
}
}
});
console.log(engine.translate('cart', 'items', { count: 1 }));
// Output: "1 item"
console.log(engine.translate('cart', 'items', { count: 5 }));
// Output: "5 items"Industry-standard message formatting with powerful features. See @docs/ICU_MESSAGEFORMAT.md for complete guide.
import { formatICUMessage } from '@digitaldefiance/i18n-lib';
// Simple variable
formatICUMessage('Hello {name}', { name: 'Alice' });
// → "Hello Alice"
// Plural
formatICUMessage('{count, plural, one {# item} other {# items}}', { count: 1 });
// → "1 item"
// Select
formatICUMessage('{gender, select, male {He} female {She} other {They}}', { gender: 'male' });
// → "He"
// Number formatting
formatICUMessage('{price, number, currency}', { price: 99.99 }, 'en-US');
// → "$99.99"
// Complex nested
formatICUMessage(
'{gender, select, male {He has} female {She has}} {count, plural, one {# item} other {# items}}',
{ gender: 'female', count: 2 }
);
// → "She has 2 items"- ✅ Full ICU Syntax: Variables, plural, select, selectordinal
- ✅ Formatters: Number (integer, currency, percent), Date, Time
- ✅ 37 Languages: CLDR plural rules for all supported languages
- ✅ Nested Messages: Up to 4 levels deep
- ✅ Performance: <1ms per format, message caching
- ✅ Specification Compliant: Unicode ICU, CLDR, FormatJS compatible
- @docs/ICU_MESSAGEFORMAT.md - Complete guide with syntax reference and examples
- @docs/ICU_COMPREHENSIVE_VALIDATION.md - Validation report with test coverage
- @docs/ICU_PROJECT_COMPLETE.md - Implementation summary
import {
formatICUMessage, // One-line formatting
isICUMessage, // Detect ICU format
parseICUMessage, // Parse to AST
compileICUMessage, // Compile to function
validateICUMessage, // Validate syntax
Runtime // Advanced usage
} from '@digitaldefiance/i18n-lib';Automatic plural form selection based on count with CLDR-compliant rules for 37 languages:
import { createPluralString } from '@digitaldefiance/i18n-lib';
// English (one/other)
engine.register({
id: 'shop',
strings: {
'en-US': {
items: createPluralString({
one: '{count} item',
other: '{count} items'
})
}
}
});
// Russian (one/few/many)
engine.register({
id: 'shop',
strings: {
'ru': {
items: createPluralString({
one: '{count} товар',
few: '{count} товара',
many: '{count} товаров'
})
}
}
});
// Arabic (zero/one/two/few/many/other)
engine.register({
id: 'shop',
strings: {
'ar': {
items: createPluralString({
zero: 'لا عناصر',
one: 'عنصر واحد',
two: 'عنصران',
few: '{count} عناصر',
many: '{count} عنصرًا',
other: '{count} عنصر'
})
}
}
});
// Automatic form selection
engine.translate('shop', 'items', { count: 1 }); // "1 item"
engine.translate('shop', 'items', { count: 5 }); // "5 items"
engine.translate('shop', 'items', { count: 21 }, 'ru'); // "21 товар"Supported Languages (37 total):
- Simple (other only): Japanese, Chinese, Korean, Turkish, Vietnamese, Thai, Indonesian, Malay
- Two forms (one/other): English, German, Spanish, Italian, Portuguese, Dutch, Swedish, Norwegian, Danish, Finnish, Greek, Hebrew, Hindi
- Three forms (one/few/many): Russian, Ukrainian, Romanian, Latvian
- Four forms: Polish, Czech, Lithuanian, Slovenian, Scottish Gaelic
- Five forms: Irish, Breton
- Six forms: Arabic, Welsh
See PLURALIZATION_SUPPORT.md for complete language matrix.
Gender-aware translations with intelligent fallback:
import { createGenderedString } from '@digitaldefiance/i18n-lib';
engine.register({
id: 'profile',
strings: {
'en-US': {
greeting: createGenderedString({
male: 'Welcome, Mr. {name}',
female: 'Welcome, Ms. {name}',
neutral: 'Welcome, {name}'
})
}
}
});
engine.translate('profile', 'greeting', { name: 'Smith', gender: 'male' });
// Output: "Welcome, Mr. Smith"Nested plural and gender forms:
// Plural → Gender
const pluralGender = {
one: {
male: 'He has {count} item',
female: 'She has {count} item'
},
other: {
male: 'He has {count} items',
female: 'She has {count} items'
}
};
// Gender → Plural
const genderPlural = {
male: {
one: 'He has {count} item',
other: 'He has {count} items'
},
female: {
one: 'She has {count} item',
other: 'She has {count} items'
}
};import {
createPluralString,
createGenderedString,
getRequiredPluralForms
} from '@digitaldefiance/i18n-lib';
// Get required forms for a language
const forms = getRequiredPluralForms('ru');
// Returns: ['one', 'few', 'many']
// Type-safe plural string creation
const plural = createPluralString({
one: '1 item',
other: '{count} items'
});
// Type-safe gender string creation
const gender = createGenderedString({
male: 'He',
female: 'She',
neutral: 'They'
});import { validatePluralForms } from '@digitaldefiance/i18n-lib';
// Validate plural forms for a language
const result = validatePluralForms(
{ one: 'item', other: 'items' },
'en',
'items',
{ strict: true, checkUnused: true, checkVariables: true }
);
if (!result.isValid) {
console.error('Errors:', result.errors);
}
if (result.warnings.length > 0) {
console.warn('Warnings:', result.warnings);
}The main engine class that manages translations, languages, and components.
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
// Create instance
const engine = PluginI18nEngine.createInstance('myapp', languages);
// Or use constructor
const engine = new PluginI18nEngine(languages, config);Components group related translations together:
engine.registerComponent({
component: {
id: 'auth',
name: 'Authentication',
stringKeys: ['login', 'logout', 'error']
},
strings: {
[LanguageCodes.EN_US]: {
login: 'Login',
logout: 'Logout',
error: 'Authentication failed'
},
[LanguageCodes.FR]: {
login: 'Connexion',
logout: 'Déconnexion',
error: 'Échec de l\'authentification'
}
},
aliases: ['authentication'] // Optional aliases
});
// Safe registration (won't error if already registered)
engine.registerComponentIfNotExists({
component: { id: 'auth', /* ... */ },
strings: { /* ... */ }
});// Simple translation
const text = engine.translate('auth', 'login');
// With variables
const greeting = engine.translate('app', 'welcome', { name: 'John' });
// Specific language
const french = engine.translate('auth', 'login', {}, LanguageCodes.FR);
// Safe translation (returns fallback on error)
const safe = engine.safeTranslate('missing', 'key'); // Returns "[missing.key]"// Component references: {{componentId.stringKey}}
engine.t('Click {{auth.login}} to continue');
// Alias resolution: {{alias.stringKey}}
engine.registerComponent({
component: { id: 'authentication', /* ... */ },
aliases: ['auth', 'AuthModule']
});
engine.t('{{auth.login}}'); // Resolves via alias
// Variables: {variableName}
engine.t('Hello, {username}!', { username: 'Alice' });
// Context variables (automatic injection)
engine.t('Price in {currency}'); // Uses context currency
engine.t('Time: {timezone}'); // Uses context timezone
engine.t('Language: {language}'); // Uses current language
// CurrencyCode and Timezone objects
const currency = new CurrencyCode('EUR');
const timezone = new Timezone('America/New_York');
engine.t('Price: {amount} {currency}', { amount: 100, currency });
// Output: "Price: 100 EUR"
// Variable priority: provided > context > constants
engine.t('{AppName}'); // Uses constant
engine.t('{currency}'); // Uses context
engine.t('{currency}', { currency: 'GBP' }); // Uses provided (overrides context)
// Mixed patterns
engine.t('{{auth.login}}: {username} ({currency})', { username: 'admin' });import { I18nBuilder } from '@digitaldefiance/i18n-lib';
const engine = I18nBuilder.create()
.withLanguages([
{ id: 'en-US', name: 'English', code: 'en-US', isDefault: true },
{ id: 'fr', name: 'French', code: 'fr' }
])
.withDefaultLanguage('en-US')
.withFallbackLanguage('en-US')
.withConstants({
AppName: 'MyApp',
Version: '1.0.0'
})
.withValidation({
requireCompleteStrings: false,
allowPartialRegistration: true
})
.withInstanceKey('myapp')
.withRegisterInstance(true)
.withSetAsDefault(true)
.build();import { GlobalActiveContext, CurrencyCode, Timezone } from '@digitaldefiance/i18n-lib';
// Set context variables
const context = GlobalActiveContext.getInstance();
context.setCurrencyCode(new CurrencyCode('EUR'));
context.setUserTimezone(new Timezone('Europe/Paris'));
context.setUserLanguage('fr');
// Context variables automatically available in translations
engine.t('Price in {currency}'); // "Price in EUR"
engine.t('Timezone: {timezone}'); // "Timezone: Europe/Paris"
engine.t('Language: {language}'); // "Language: fr"
// Override context with provided variables
engine.t('Price in {currency}', { currency: 'USD' }); // "Price in USD"// Merge constants (adds/overwrites specific keys)
engine.mergeConstants({ Version: '2.0', NewKey: 'value' });
// Existing constants preserved, specified ones added/updated
// Update all constants (replaces everything)
engine.updateConstants({ Site: 'NewSite', Version: '2.0' });
// All previous constants removed, only these remain// Set current language
engine.setLanguage(LanguageCodes.FR);
// Get current language
const lang = engine.getCurrentLanguage();
// Check if language exists
if (engine.hasLanguage(LanguageCodes.ES)) {
engine.setLanguage(LanguageCodes.ES);
}
// Get all languages
const languages = engine.getLanguages();Separate language for admin interfaces:
// Set admin language
engine.setAdminLanguage(LanguageCodes.EN_US);
// Switch to admin context
engine.switchToAdmin();
// Switch back to user context
engine.switchToUser();Pre-built translations for common UI elements:
import { getCoreI18nEngine, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
const coreEngine = getCoreI18nEngine();
// Use core strings
const yes = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Common_Yes);
const error = coreEngine.translate(CoreI18nComponentId, CoreStringKey.Error_NotFound);Available core string categories:
- Common: Yes, No, Cancel, OK, Save, Delete, Edit, Create, Update, Loading, etc.
- Errors: InvalidInput, NetworkError, NotFound, AccessDenied, ValidationFailed, etc.
- System: Welcome, Goodbye, PleaseWait, ProcessingRequest, OperationComplete, etc.
Create isolated engines for different parts of your application:
// Admin engine
const adminEngine = PluginI18nEngine.createInstance('admin', adminLanguages);
// User engine
const userEngine = PluginI18nEngine.createInstance('user', userLanguages);
// Get instance by key
const admin = PluginI18nEngine.getInstance('admin');
// Check if instance exists
if (PluginI18nEngine.hasInstance('admin')) {
// ...
}
// Remove instance
PluginI18nEngine.removeInstance('admin');
// Reset all instances
PluginI18nEngine.resetAll();Errors related to component/language registration:
import { RegistryError, RegistryErrorType } from '@digitaldefiance/i18n-lib';
try {
engine.translate('missing', 'key');
} catch (error) {
if (error instanceof RegistryError) {
console.log(error.type); // RegistryErrorType.COMPONENT_NOT_FOUND
console.log(error.message); // "Component 'missing' not found"
console.log(error.metadata); // { componentId: 'missing' }
}
}Base class for errors with translated messages:
import { TranslatableError, CoreStringKey, CoreI18nComponentId } from '@digitaldefiance/i18n-lib';
class MyError extends TranslatableError {
constructor(language?: string) {
super(
CoreI18nComponentId,
CoreStringKey.Error_AccessDenied,
{},
language
);
}
}
throw new MyError(LanguageCodes.FR); // Throws with French error messageAdapt PluginI18nEngine to simpler TranslationEngine interface:
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
const adapter = createTranslationAdapter(engine, 'componentId');
// Use adapter where TranslationEngine is expected
const message = adapter.translate('key', { var: 'value' });Built-in language codes following BCP 47 standard:
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
LanguageCodes.EN_US // 'en-US'
LanguageCodes.EN_GB // 'en-GB'
LanguageCodes.FR // 'fr'
LanguageCodes.ES // 'es'
LanguageCodes.DE // 'de'
LanguageCodes.ZH_CN // 'zh-CN'
LanguageCodes.JA // 'ja'
LanguageCodes.UK // 'uk'Static Methods
createInstance<TLanguage>(key: string, languages: LanguageDefinition[], config?: RegistryConfig)- Create named instancegetInstance<TLanguage>(key?: string)- Get instance by keyhasInstance(key?: string)- Check if instance existsremoveInstance(key?: string)- Remove instanceresetAll()- Reset all instances
Instance Methods
registerComponent(registration: ComponentRegistration)- Register componenttranslate(componentId: string, key: string, variables?, language?)- Translate stringsafeTranslate(componentId: string, key: string, variables?, language?)- Safe translate with fallbackt(template: string, variables?, language?)- Process template stringsetLanguage(language: TLanguage)- Set current languagesetAdminLanguage(language: TLanguage)- Set admin languagegetCurrentLanguage()- Get current languagegetLanguages()- Get all languageshasLanguage(language: TLanguage)- Check if language existsswitchToAdmin()- Switch to admin contextswitchToUser()- Switch to user contextvalidate()- Validate all components
getCoreI18nEngine()- Get core engine with system stringscreateCoreI18nEngine(instanceKey?)- Create core engine instancegetCoreTranslation(stringKey, variables?, language?, instanceKey?)- Get core translationsafeCoreTranslation(stringKey, variables?, language?, instanceKey?)- Safe core translationgetCoreLanguageCodes()- Get array of core language codesgetCoreLanguageDefinitions()- Get core language definitions
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
describe('My Tests', () => {
beforeEach(() => {
PluginI18nEngine.resetAll();
});
afterEach(() => {
PluginI18nEngine.resetAll();
});
it('should translate', () => {
const engine = PluginI18nEngine.createInstance('test', languages);
engine.registerComponent(registration);
expect(engine.translate('app', 'hello')).toBe('Hello');
});
});Full TypeScript support with generic types:
// Type-safe language codes
type MyLanguages = 'en-US' | 'fr' | 'es';
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', languages);
// Type-safe string keys
enum MyStringKeys {
Welcome = 'welcome',
Goodbye = 'goodbye'
}
// Type-safe component registration
const registration: ComponentRegistration<MyStringKeys, MyLanguages> = {
component: {
id: 'app',
name: 'App',
stringKeys: Object.values(MyStringKeys)
},
strings: {
'en-US': {
[MyStringKeys.Welcome]: 'Welcome',
[MyStringKeys.Goodbye]: 'Goodbye'
},
'fr': {
[MyStringKeys.Welcome]: 'Bienvenue',
[MyStringKeys.Goodbye]: 'Au revoir'
}
}
};- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Node.js: 18+
MIT License - See LICENSE file for details
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass (
npm test) - Submit a pull request
- Issues: https://github.com/Digital-Defiance/i18n-lib/issues
- Documentation: See docs/ directory
- Examples: See tests/ directory
Type Safety Improvements Release
This release eliminates all unsafe type casts from the production codebase, improving type safety and maintainability.
Type Safety Enhancements:
-
New SimpleTypedError Class: Introduced a type-safe error class (
simple-typed-error.ts) that properly extends Error without using type casts- Supports
type,componentId,reasonMap,metadataproperties with full type safety - Includes error cause chaining (ES2022 standard)
- Provides
isTypedError()type guard andfromError()conversion utility - Maintains proper prototype chain for instanceof checks
- Supports
-
Refactored Error Helper Functions: Updated all error creation helpers to use SimpleTypedError
createComponentTypedError()- Now returns SimpleTypedError instead of casting propertiescreateCoreTypedError()- Type-safe error creation for core system errorscreateTranslatedError()- Type-safe translated error creation- All functions now have proper return types without
as anycasts
-
Fixed Empty Object Initializations: Replaced
{} as anywith properPartial<T>typesbuildTypeSafeReasonMap()in utils.ts now uses proper partial typesensureAllLanguagesHaveAllKeys()in component-registry.ts uses type-safe initialization
-
Improved Enum Translation Type Safety: Enhanced type safety in enum registry and plural handling
- Enum registry now uses
String()conversion instead ofas anycasts - Added type guards for PluralCategory validation in
hasPluralForm() - Fixed
getPluralCategory()return type to properly returnPluralCategory - Removed unsafe casts in plural category handling
- Enum registry now uses
-
Global Context Type Safety: Leveraged ambient type declarations for global objects
- Removed
(globalThis as any).GlobalActiveContextcasts - Uses ambient declarations from
types/global.d.tsfor type-safe global access - Fixed Proxy pattern in core-i18n.ts to use proper keyof access
- Removed
-
Enhanced I18n Engine Interface: Created comprehensive engine type definitions
- New
src/types/engine.tswith genericII18nEngine<TStringKeys>interface - Proper generic constraints for type-safe string key handling
- Re-exports main II18nEngine interface for centralized imports
- New
-
Renamed for Clarity: Improved naming to avoid confusion
- Renamed abstract
TypedErrortoAbstractTypedError(with deprecation notice) - New
SimpleTypedErrorclass is the recommended type-safe implementation - Maintains backward compatibility with existing code
- Renamed abstract
Code Quality:
- Zero Type Casts: Eliminated approximately 20 instances of
as anyandas unknowncasts from production code - Improved Type Inference: Better TypeScript type inference throughout the codebase
- Enhanced Maintainability: Clearer code structure with explicit types instead of casts
Testing:
- Added comprehensive unit tests for SimpleTypedError class
- Added property-based tests using fast-check for error property preservation
- Created type-safety-fixes.spec.ts with tests for all improvements
- All existing tests continue to pass with enhanced type safety
Documentation:
- Updated JSDoc comments with proper type information
- Added deprecation notices for legacy patterns
- Improved code examples in documentation
Migration Notes:
This release is fully backward compatible. Existing code will continue to work unchanged. To use the new type-safe patterns:
// New: Use SimpleTypedError directly
import { TypedError } from '@digitaldefiance/i18n-lib/errors/simple-typed-error';
const error = new TypedError('Error message', {
type: 'validation',
componentId: 'my-component',
metadata: { field: 'email' }
});
// Old abstract classes still work (now called AbstractTypedError)
// Helper functions automatically use the new SimpleTypedError internallyBreaking Changes:
None - This release maintains full backward compatibility while improving internal type safety.
Internal Refactoring Release - Circular Dependency Elimination
This release eliminates all circular dependencies from the codebase through internal architectural improvements. All changes are non-breaking and maintain full backward compatibility.
Internal Improvements:
-
Zero Circular Dependencies: Eliminated all 15 circular dependencies that existed in the codebase
- Broke core/i18n-engine ↔ core-i18n cycle
- Broke errors/translatable → core/i18n-engine → errors/index cycle
- Broke core-i18n ↔ plugin-i18n-engine cycle
- Broke context-related cycles
- Broke registry-error cycles
- Optimized barrel exports with type-only exports
-
Lazy Initialization Pattern: Error classes and core modules now use lazy initialization to avoid circular dependencies
TranslatableErrorusesgetInstance()at construction time instead of module loadTypedErrorand related classes use lazy engine lookup- Registry errors use lazy initialization for core dependencies
- Fallback behavior if engine not initialized
-
Factory Pattern: Introduced
core-plugin-factory.tsto break plugin engine cyclescreateCorePluginI18nEngine()moved to factory file- Factory imports both core-i18n and plugin-i18n-engine
- Breaks circular dependency between these modules
-
Type-Only Exports: Optimized barrel exports to eliminate runtime dependencies
src/interfaces/index.tsusesexport typefor all type exportssrc/errors/index.tssplit into base.ts and translatable-exports.tssrc/core/index.tsuses type-only exports where appropriate
-
Module Organization: Established clear dependency hierarchy
- Interfaces & Types (no implementation dependencies)
- Error Classes (lazy initialization for i18n)
- Core Modules (clear hierarchy, no cycles)
- High-Level Modules (core-i18n, plugin-i18n-engine)
Testing & Quality:
- Automated Circular Dependency Detection: Added test that fails if circular dependencies are introduced
- Module Independence Tests: Verify modules can be imported independently
- All Tests Passing: 1,779 tests passing with no regressions
- Zero Breaking Changes: Public API remains completely unchanged
Documentation:
- CIRCULAR_DEPENDENCY_FIXES.md - Comprehensive internal documentation
- Detailed explanation of all patterns used
- Best practices for maintaining zero circular dependencies
- Examples of lazy initialization, factory pattern, and type-only exports
- Guidelines for adding new modules without creating cycles
Files Modified:
src/core-plugin-factory.ts- New factory file for plugin engine creationsrc/core-i18n.ts- Lazy initialization with Proxy patternsrc/errors/*.ts- Lazy initialization in error constructorssrc/errors/index.ts- Split into separate barrel filessrc/interfaces/index.ts- Type-only exportssrc/core/index.ts- Optimized barrel exportssrc/registry-error.ts- Lazy initialization for core dependenciestests/circular-dependencies.spec.ts- Automated detection test
Breaking Changes:
None - This release is fully backward compatible. All changes are internal refactoring only.
Migration:
No migration required! Your existing code works unchanged. The improvements are entirely internal.
Benefits:
- Improved Maintainability: Clearer module structure and dependencies
- Better Tree-Shaking: Reduced runtime dependencies improve bundle optimization
- Predictable Initialization: No more initialization order issues
- Future-Proof: Established patterns prevent circular dependencies from being reintroduced
- Minor version bump to fix an export
- Minor version bump to fix an export
Comprehensive ICU Integration & Number Formatting Enhancements
Enhanced all I18nError methods and core ICU infrastructure to fully leverage 3.5.0 advanced ICU MessageFormat features:
Core Infrastructure Improvements:
-
ICU Compiler Enhancements:
- Fixed
#placeholder in plural/selectordinal to useIntl.NumberFormatwith thousand separators - Numbers in plural messages now properly formatted (1,500 instead of 1500)
- Locale-aware formatting respects regional preferences
- Fixed
-
Number Formatter Upgrades:
- Percent formatting now shows decimal precision (0-2 places): 5.67% instead of 5%
- Integer formatting maintains thousand separators automatically
- Currency formatting preserves decimal places with locale-specific symbols
Enhanced Error Methods (13 existing + 4 new):
Enhanced Existing Methods:
componentNotFound()- ICU select for namespaced componentsstringKeyNotFound()- SelectOrdinal for nested depth levelsduplicateComponent()- Nested select for namespace contextinstanceNotFound()- Select for default vs named instancesinstanceExists()- Nested select with detailed messagestranslationMissing()- Nested select detecting key pathsduplicateLanguage()- Template literal with proper quotingpluralFormNotFound()- Nested select + plural + number formatting (form count)invalidPluralCategory()- Nested plural + number formatting (category count)- And 4 more existing methods with ICU enhancements...
New Advanced Methods:
validationThresholdExceeded()- Number formatting: Currency ($1,500.50), Percent (5.67%), Integer (1,500)operationStepFailed()- SelectOrdinal: 1st, 2nd, 3rd, 4th, 21st, 22nd, 23rd...rateLimitExceeded()- 4-level nested messages: Plural + number + select with thousand separatorsnestedValidationError()- Complex nesting: Multiple select + plural for validation context
ICU Features Fully Integrated:
-
✅ Number Formatters: Currency ($1,500.50), percent (5.67%), integer (1,500) with thousand separators
-
✅ SelectOrdinal: Ordinal formatting (1st, 2nd, 3rd, 21st, 22nd, 23rd)
-
✅ Nested Messages: Up to 4 levels deep with combined plural, select, and number formatting
-
✅ ICU Plural:
#placeholder now formats with thousand separators -
✅ ICU Select: Nested within plural messages for complex conditional logic
-
✅ Decimal Precision: Percent values show up to 2 decimal places
-
✅ Locale-Aware: All formatting respects target language/locale
-
Real-World Use Cases:
- Validation threshold errors with formatted currency/percentages
- Multi-step operation failures with ordinal step numbers
- Rate limiting with nested request counts and retry timing
- Complex nested field validation with severity levels
Testing & Quality:
- 1,738 total tests passing (93.22% coverage)
- 250+ new tests for advanced ICU features:
- Currency formatting: $1,500.50, €1.500,50, ¥1,500
- Percent precision: 5.67%, 0.5%, 100%
- SelectOrdinal: 1st-100th with edge cases (11th, 21st, 22nd, 23rd)
- Nested messages: 4 levels deep validation
- Thousand separators: 1,000, 10,000, 1,000,000
- Multilingual: 8+ languages tested
- Real-world scenarios: API rate limits, validation thresholds, multi-step operations
Documentation:
- All error methods include comprehensive JSDoc with ICU pattern examples
- EnhancedErrorHelper base class with static utility methods
- Integration patterns for all error class types
- Migration guide showing before/after message formats
New Error Codes:
VALIDATION_THRESHOLD_EXCEEDED- Numeric threshold violations with formatted valuesOPERATION_STEP_FAILED- Step-based operation failures with ordinal formattingRATE_LIMIT_EXCEEDED- Rate limiting with nested plural/number formattingNESTED_VALIDATION_ERROR- Complex nested validation with 4-level messages
Files Modified:
src/icu/compiler.ts- Enhanced#placeholder withIntl.NumberFormatsrc/icu/formatters/number-formatter.ts- Added percent decimal precision (0-2 places)src/errors/i18n-error.ts- Enhanced all 17 error methods with ICU patternssrc/errors/enhanced-error-base.ts- New base class with static helper methodssrc/errors/*.ts- Enhanced 8 error classes with comprehensive JSDoctests/errors/*.spec.ts- 250+ new tests, updated expectations
Breaking Changes:
None - Fully backward compatible! All changes are enhancements:
- Enhanced error message formats (more detailed, better formatted)
- Metadata structure extended (formCount, count added where useful)
- New optional parameters for advanced error methods
- All existing code continues to work unchanged
Migration:
No migration required! Your existing code works as-is. To use new features:
// Use new number formatting in errors
const error = I18nError.validationThresholdExceeded(
'price', 99.99, 50.00, 'currency', 'en-US'
);
// Message: "Validation failed for price: value $99.99 exceeds maximum threshold of $50.00"
// Use selectordinal for steps
const error = I18nError.operationStepFailed(3, 'deployment', 'Connection timeout');
// Message: "Operation 'deployment' failed at 3rd step: Connection timeout"
// Numbers in plural messages now have thousand separators automatically
const error = I18nError.rateLimitExceeded(1500, 1000, 3600, 300);
// Message: "Rate limit exceeded: 1,500 requests made, exceeding limit of 1,000..."- Add DefaultLanguageCode
- make getCodeLabelMap() not readonly
- Add getCodeLabelMap() for use with react components
- Add documentation jsdocs
- Upgrade currency-codes
Security Hardening Release
- Prototype Pollution Prevention: Validates all object keys, filters dangerous properties
- ReDoS Mitigation: Limited regex patterns, input length validation
- XSS Protection: HTML escaping option, safe type coercion
- Input Validation: Length limits, character whitelisting
- Resource Limits: LRU cache (1000 entries), recursion depth (10), message length (10000)
- 101 New Tests: Comprehensive security test coverage
- New Utilities:
safe-object,html-escape,validation,lru-cache - Documentation: SECURITY.md, SECURITY_AUDIT.md, SECURITY_FIXES_COMPLETE.md
Major Feature Release - ICU MessageFormat Support
New Features:
-
ICU MessageFormat: Full industry-standard message formatting
- Parser with 6 AST node types (MESSAGE, LITERAL, ARGUMENT, PLURAL, SELECT, SELECTORDINAL)
- Tokenizer with sophisticated depth tracking
- Semantic validator with configurable options
- Message compiler (AST → executable function)
- Runtime with message caching
- 304 tests passing (100%)
-
Formatters: 6 built-in formatters
- NumberFormatter (integer, currency, percent)
- DateFormatter (short, medium, long, full)
- TimeFormatter (short, medium, long, full)
- PluralFormatter (37 languages via CLDR)
- SelectFormatter
- SelectOrdinalFormatter
- FormatterRegistry (pluggable system)
-
Helper Functions: Easy-to-use utilities
formatICUMessage()- One-line formattingisICUMessage()- Detect ICU formatparseICUMessage()- Parse to ASTcompileICUMessage()- Compile to functionvalidateICUMessage()- Validate syntax
-
Advanced Features:
- Nested messages (4 levels tested)
- Missing value handling
- Performance optimization (<1ms/format)
- Memory-efficient caching
- Multilingual validation (12 languages, 6 writing systems)
Documentation:
- ICU_MESSAGEFORMAT.md - Complete guide
- ICU_COMPREHENSIVE_VALIDATION.md - Validation report
- ICU_PROJECT_COMPLETE.md - Implementation summary
Testing:
- 304 ICU tests passing (100%)
- Specification compliance (Unicode ICU, CLDR)
- Industry compatibility (React Intl, Vue I18n, Angular)
- Edge case coverage (nesting, Unicode, RTL, special chars)
- Performance validation (<1ms, 1000 formats in <100ms)
Migration:
// Use ICU MessageFormat
import { formatICUMessage } from '@digitaldefiance/i18n-lib';
formatICUMessage('Hello {name}', { name: 'Alice' });
formatICUMessage('{count, plural, one {# item} other {# items}}', { count: 1 });Major Feature Release - Pluralization & Gender Support
New Features:
-
CLDR Pluralization: Full support for 37 languages with automatic plural form selection
- 19 unique plural rule implementations (English, Russian, Arabic, Polish, French, Spanish, Japanese, Ukrainian, Chinese, German, Scottish Gaelic, Welsh, Breton, Slovenian, Czech, Lithuanian, Latvian, Irish, Romanian)
- 18 additional languages reusing existing rules
- Handles world's most complex plural systems (Arabic 6 forms, Welsh 6 forms, Breton 5 forms)
- Intelligent fallback: requested form → 'other' → first available
- Type-safe
PluralStringtype with backward compatibility
-
Gender Support: Gender-aware translations with 4 categories
- Gender categories: male, female, neutral, other
GenderedStringtype for type-safe gender forms- Intelligent fallback: requested → neutral → other → first available
- Works seamlessly with pluralization
-
Combined Plural + Gender: Nested plural and gender resolution
- Supports both plural→gender and gender→plural nesting
- Full fallback support for missing forms
- Works with all 37 supported languages
-
Validation System: Comprehensive plural form validation
validatePluralForms()- Validates required forms per languagevalidateCountVariable()- Ensures count variable exists- Strict and lenient modes
- Variable consistency checking
- Unused form detection
-
Helper Functions: Utilities for easy plural/gender string creation
createPluralString()- Type-safe plural object creationcreateGenderedString()- Type-safe gender object creationgetRequiredPluralForms()- Get required forms for any language
-
Error Handling: New error codes for pluralization
PLURAL_FORM_NOT_FOUND- Missing plural form with suggestionsINVALID_PLURAL_CATEGORY- Invalid category with valid optionsMISSING_COUNT_VARIABLE- Count variable missing when plurals used
Documentation:
- PLURALIZATION_SUPPORT.md - Complete language support matrix
- PLURALIZATION_USAGE.md - Usage guide with examples
- ADDING_LANGUAGES.md - Guide for adding custom languages
- Updated README with pluralization and gender sections
Testing:
- 348 tests passing (92% of roadmap target)
- 100% coverage on pluralization, gender, and validation code
- Comprehensive edge case testing
- Integration tests for complex scenarios
Migration:
// Simple strings continue to work unchanged
engine.register({
id: 'app',
strings: {
'en-US': {
title: 'My App' // Still works
}
}
});
// Add pluralization
import { createPluralString } from '@digitaldefiance/i18n-lib';
engine.register({
id: 'cart',
strings: {
'en-US': {
items: createPluralString({
one: '{count} item',
other: '{count} items'
})
}
}
});
// Use with count variable
engine.translate('cart', 'items', { count: 5 });
// Output: "5 items"- Alignment
- Expose config instead of constants
- Add constants param to i18n creation
- Version alignment bump
- Improve test coverage
- Add static registerIfNotExists to i8nengine
- Add
registerIfNotExists()method to I18nEngine for safe component registration - Add
registerComponentIfNotExists()method to PluginI18nEngine - Prevents "Component already registered" errors when reusing engine instances
- Add constants updateConstants mergeConstants functions to engine
- Convergence bump
- Minor update to expose core keys via V2 engine
- Minor timezone/currency export fix
- Version bump
- Minor fix, set this.name in errors
Moderate Feature Release - Enhanced I18nEngine with context variables, comprehensive testing, and improved architecture
New Features:
- Context Variable Injection: Automatic injection of timezone, currency, and language from GlobalActiveContext
t()function now supports{currency},{timezone},{language},{adminLanguage}, etc.- CurrencyCode and Timezone objects automatically extract their values
- Variable priority: provided variables > context variables > constants
- Enhanced t() Function: Complete template processing with multiple resolution strategies
{{Component.key}}- Component reference resolution{{Alias.key}}- Alias resolution for components{{EnumName.key}}- Enum name resolution{variable}- Variable substitution with context awareness- Mixed patterns supported in single template
- Object Value Extraction: Smart handling of wrapper objects
- CurrencyCode objects:
.valueand.codegetters - Timezone objects:
.valueand.namegetters - Generic objects with
.valueproperty automatically extracted
- CurrencyCode objects:
- I18nBuilder: Fluent builder pattern for engine creation
withLanguages(),withConstants(),withValidation()withInstanceKey(),withRegisterInstance(),withSetAsDefault()- Method chaining for clean configuration
- Renamed Classes: Removed "Plugin" prefix for clarity
PluginTypedError→ComponentTypedError(with backward compatibility alias)createPluginTypedError()→createComponentTypedError()- Old names deprecated but still functional
Improved:
- Test Coverage: Increased from 87.81% to 91.81% overall
- string-utils: 0% → 100%
- typed.ts: 48.68% → 83.17%
- i18n-builder: 61.53% → 86.66%
- 714 total tests (all passing)
- Error Translation: All error classes now properly translate messages
- TypedError, TypedHandleableError, TranslatableError
- Multi-language support with template variables
- Context-aware language selection
- Type Safety: Enhanced generic types throughout
- Better inference for component IDs and string keys
- Stricter validation at compile time
- Documentation: Comprehensive test examples for all features
- t() function special cases
- Context variable integration
- Error translation patterns
- Builder pattern usage
Fixed:
- TranslatableError now requires componentId as first parameter (breaking change)
- I18nError.stringKeyNotFound() method added
- CurrencyCode error now uses correct TranslatableError signature
- Variable substitution in templates now works with all object types
- Context variables properly override constants
Testing:
- Added comprehensive test suites:
t-function.spec.ts- All t() function capabilitieserror-translation.spec.ts- Error class translationcontext-integration.spec.ts- Context variable injectionstring-utils.spec.ts- String utility functionsi18n-builder.spec.ts- Builder patterntyped-helpers.spec.ts- Typed error helpers
Migration Notes:
// Old TranslatableError signature
new TranslatableError(stringKey, variables, language);
// New signature (v2.1.0)
new TranslatableError(componentId, stringKey, variables, language);
// Context variables now automatically available in t()
engine.t('Price: {currency}'); // Uses context currency
engine.t('Price: {currency}', { currency: 'EUR' }); // Override with provided
// CurrencyCode and Timezone objects work seamlessly
const currency = new CurrencyCode('USD');
engine.translate('app', 'price', { currency }); // Extracts 'USD'- Export error classes
- Version bump
- Minor bugfix
Major Release - Architecture improvements and bug fixes
Fixed:
- Fixed
RegistryError.createWithEngineto use correct parameter order (removed redundant componentId parameter) - Fixed
PluginTranslatableGenericError.withEngineto accept engine as first parameter instead of instanceKey - Fixed
LanguageRegistry.getMatchingCodeto check and return language codes instead of IDs - Fixed
createTranslationAdapterto support both full TranslationEngine interface and simplified bound interface - Fixed
TypedErrorconstructor to use correct componentId parameter - Added missing
Common_Testto CoreStringKey enum - Fixed mock TranslationEngine signatures in tests to include componentId parameter
Improved:
- Enhanced
createTranslationAdapterwith dual interface support (4-param and 3-param calling conventions) - Improved JSDoc documentation for
createTranslationAdapterwith usage examples - Added French language support to typed-error tests
- Updated all test files to use v2 LanguageRegistry APIs
- Added
PluginI18nEngine.resetAll()calls in test beforeEach hooks for better isolation
Internal:
- Refactored core-i18n.ts to use lazy initialization pattern with Proxy for backward compatibility
- Updated error classes to use
getCoreI18nEngine()instead of direct singleton import - Fixed duplicate LanguageRegistry exports by commenting out v1 export
- Added v1 compatibility methods to v2 LanguageRegistry
- Changed
getDefault()to returnLanguageDefinition | nullinstead of throwing
Testing:
- All 511 tests passing
- Fixed registry-error.spec.ts mock expectations
- Fixed language-registry.spec.ts to handle null default language
- Fixed create-translation-adapter.spec.ts parameter detection
- Fixed typed-error.spec.ts to import and use PluginI18nEngine
- Version bump
- Fix plugin engine to pass constants to templates as legacy engine did
- Version bump
- Skip 1.3.16 for homogenization
- Add component registration aliases for t() func
- Update readme
- Improve constructor for default instances
- Update README
- Re-export with js again
- Migrate to es2022/nx monorepo
- Update typed-handleable to plugin i18n
- Export i18nconfig
- Add UnifiedTranslator
- Add more handleable/typed classes
- Add TypedHandleable
- Add handleable at i18n level
- Add TranslatableGenericHandleable at i18n level
- Simplify LanguageContextSpace and generics related to it
- Make plugin engine respect admin context
- CommonJS
- Deprecate clearAllInstances
- Make LanguageRegistry static
- Add functionality to Language Registry for getMatchingLanguageCode
- Changed:
CoreLanguageCodeis nowstring- Language Registry is single source of truth- Use
LanguageRegistry.getLanguageIds()for runtime validation - Use
getCoreLanguageCodes()for static arrays (Mongoose schemas, etc.) - Runtime validation via registry, not compile-time types
- Use
- Added:
getCoreLanguageCodes()- Get core language codes as runtime array - Added:
getCoreLanguageDefinitions()- Get core language definitions - Philosophy: Registry-based validation over hardcoded types
- Benefit: Maximum flexibility - add languages without code changes
- Sat Oct 25 2025 15:01:00 GMT-0700 (Pacific Daylight Time)
createTranslationAdapter- Generic utility function to adaptPluginI18nEngineinstances to theTranslationEngineinterface, enabling seamless integration with error classes and other components expecting the simpler interface- Maintains full type safety with generic string key and language types
- Provides graceful error handling with fallback to key strings
- Zero overhead - direct delegation to underlying
PluginI18nEngine - Comprehensive test coverage (19 tests)
- Supports both full TranslationEngine interface and simplified bound interface
- Eliminates need for custom adapter implementations in consuming packages
- Standardizes translation engine integration across the monorepo
- Simplifies error class constructors that require translation engines
Packages using custom translation adapters can now replace them with:
import { createTranslationAdapter } from '@digitaldefiance/i18n-lib';
const adapter = createTranslationAdapter(pluginEngine, 'component-id');- Sat Oct 25 2025 14:29:00 GMT-0700 (Pacific Daylight Time)
- Remove StringLanguage generic for TLanguage in GlobalActiveLanguage
- Thu Oct 23 2025 18:50:00 GMT-0700 (Pacific Daylight Time)
- Minor update to fix safeTranslate being private
- Thu Oct 23 2025 18:40:00 GMT-0700 (Pacific Daylight Time)
i18n Library - TranslationEngine Interface Refactoring
- Made
TranslationEngineinterface generic withTStringKeytype parameter for improved type safety - Changed
translateandsafeTranslatemethods from optional to required inTranslationEngineinterface - Exported
TranslationEnginefromtyped-error.tsfor consistent usage across packages - Updated
TypedHandleableErrorin ecies-lib to use genericTranslationEngine<TStringKey>instead of inline interface types - Updated test mocks to implement both required
translateandsafeTranslatemethods
Breaking Changes:
- Any code implementing
TranslationEnginemust now provide bothtranslateandsafeTranslatemethods (previously optional) TranslationEnginenow requires explicit type parameter when used (e.g.,TranslationEngine<EciesStringKey>)
- Thu Oct 23 2025 15:10:00 GMT-0700 (Pacific Daylight Time)
- Update README
- Thu Oct 23 2025 14:13:00 GMT-0700 (Pacific Daylight Time)
- Removed
CoreLanguageenum - Replaced withCoreLanguageCodetype andLanguageCodesconstants - Language identifiers now use BCP 47 codes - Changed from descriptive names (e.g.,
'English (US)') to standard codes (e.g.,'en-US') - API changes:
CoreLanguage→CoreLanguageCode(union type)DefaultLanguageenum →DefaultLanguageCodetype- All language references updated to use
LanguageCodesconstants
LanguageCodesconstants object - Provides standard BCP 47 language codes:EN_US,EN_GB,FR,ES,DE,ZH_CN,JA,UK
LanguageDisplayNamesmapping - Maps language codes to human-readable namesCommonLanguageCodetype - Type for built-in language codesLanguageCodetype - Generic string type for custom language codes- Custom language code support - Any string can now be used as a language code
- Language code format - All language identifiers now use BCP 47 standard (e.g.,
'en-US'instead of'English (US)') - Type system - Languages are now string-based types instead of enums, allowing custom language codes
- Documentation - Updated README with new API usage examples and language code constants
// Before (v1.1.x)
import { CoreLanguage } from '@digitaldefiance/i18n-lib';
i18n.setLanguage(CoreLanguage.French);
// After
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
i18n.setLanguage(LanguageCodes.FR);
// Extending with custom language codes
type MyLanguageCodes = CoreLanguageCode | 'pt-BR' | 'it';
const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', languages);- Fri Oct 17 2025 15:02:00 GMT-0700 (Pacific Daylight Time)
- Add String
- Fri Oct 17 2025 14:44:00 GMT-0700 (Pacific Daylight Time)
- Add String
- Wed Oct 15 2025 16:43:00 GMT-0700 (Pacific Daylight Time)
- Fix to translatable prototype inheritance
- Wed Oct 15 2025 16:13:00 GMT-0700 (Pacific Daylight Time)
Fixed:
- Corrected safeCoreTranslation fallback format to use
[CoreStringKey.${stringKey}] - Fixed import issues with DefaultInstanceKey
Added:
- New TranslatableGenericError class for generic translatable errors across any component
- CoreI18nComponentId constant export
- 130+ new tests for comprehensive coverage
- Complete usage documentation Changed:
- Standardized all fallback formats to use square brackets
[componentId.stringKey] - Refactored to use CoreI18nComponentId constant All tests pass and backward compatibility is maintained.
- Corrected safeCoreTranslation fallback format to use
- Tue Oct 14 2025 17:04:00 GMT-0700 (Pacific Daylight Time)
- Added missing T function to i18n plugin engine
- Tue Oct 14 2025 14:48:00 GMT-0700 (Pacific Daylight Time)
- [Current] HotFix for GlobalActiveContext
- Fixed getInstance method to throw RegistryError when instance not found instead of auto-creating instances
- Improved test reliability and proper error handling for non-existent instances
- Updated API documentation to reflect error-throwing behavior
- [Current] HotFix for GlobalActiveContext
- Tue Oct 14 2025 14:21:00 GMT-0700 (Pacific Daylight Time)
- Removed duplicate parallel RegistryContext and focused on GlobalActiveContext
- Tue Oct 14 2025 14:00:00 GMT-0700 (Pacific Daylight Time)
- Added GlobalActiveContext class instead of plain object
- Sat Oct 11 2025 19:25:00 GMT-0700 (Pacific Daylight Time)
- Added cleanup mechanisms for other modules to deregister, etc.
- Sat Oct 11 2025 17:47:00 GMT-0700 (Pacific Daylight Time)
- Improved type checking for completeness of component translations during registration
- Updated README/Migration guide for clarity.
- Sat Oct 11 2025 16:49:00 GMT-0700 (Pacific Daylight Time)
- Introduced plugin-based architecture with component registration and compile-time type safety
- Added
PluginI18nEnginewith comprehensive validation and fallback system - Maintained full support for legacy
I18nEngine - Added pre-built core and user system components
- Enhanced template processing and context management features
- Improved documentation and examples for both architectures
-
Wed Sep 24 2025 15:20:07 GMT-0700 (Pacific Daylight Time)
- Initial release of the TypeScript internationalization library with enum translation, template processing, context management, and currency formatting. PluginI18nEngine.resetAll(); });
afterEach(() => { PluginI18nEngine.resetAll(); });
it('should translate', () => { const engine = PluginI18nEngine.createInstance('test', languages); engine.registerComponent(registration); expect(engine.translate('app', 'hello')).toBe('Hello'); }); });
## TypeScript Support
Full TypeScript support with generic types:
```typescript
// Type-safe language codes
type MyLanguages = 'en-US' | 'fr' | 'es';
const engine = PluginI18nEngine.createInstance<MyLanguages>('app', languages);
// Type-safe string keys
enum MyStringKeys {
Welcome = 'welcome',
Goodbye = 'goodbye'
}
// Type-safe component registration
const registration: ComponentRegistration<MyStringKeys, MyLanguages> = {
component: {
id: 'app',
name: 'App',
stringKeys: Object.values(MyStringKeys)
},
strings: {
'en-US': {
[MyStringKeys.Welcome]: 'Welcome',
[MyStringKeys.Goodbye]: 'Goodbye'
},
'fr': {
[MyStringKeys.Welcome]: 'Bienvenue',
[MyStringKeys.Goodbye]: 'Au revoir'
}
}
};
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Node.js: 18+
The i18n-lib package uses a comprehensive testing strategy combining unit tests, integration tests, and property-based testing to ensure correctness across all features.
Test Framework: Jest with TypeScript support
Property-Based Testing: fast-check for testing universal properties
Coverage Target: 90%+ statement coverage, 85%+ branch coverage
tests/
├── unit/ # Unit tests for individual components
├── integration/ # Integration tests for component interactions
├── property/ # Property-based tests using fast-check
└── fixtures/ # Test data and mock translations
# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- plugin-i18n-engine.spec.ts
# Run in watch mode
npm test -- --watchimport { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
describe('Component Registration', () => {
let engine: PluginI18nEngine;
beforeEach(() => {
PluginI18nEngine.resetAll();
engine = PluginI18nEngine.createInstance('test', [
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true }
]);
});
afterEach(() => {
PluginI18nEngine.resetAll();
});
it('should register component with translations', () => {
engine.registerComponent({
component: {
id: 'test',
name: 'Test',
stringKeys: ['hello']
},
strings: {
[LanguageCodes.EN_US]: {
hello: 'Hello, {name}!'
}
}
});
expect(engine.translate('test', 'hello', { name: 'World' }))
.toBe('Hello, World!');
});
});import { formatICUMessage } from '@digitaldefiance/i18n-lib';
describe('ICU MessageFormat', () => {
it('should format plural messages', () => {
const message = '{count, plural, one {# item} other {# items}}';
expect(formatICUMessage(message, { count: 1 })).toBe('1 item');
expect(formatICUMessage(message, { count: 5 })).toBe('5 items');
});
it('should handle nested select and plural', () => {
const message = '{gender, select, male {He has} female {She has}} {count, plural, one {# item} other {# items}}';
expect(formatICUMessage(message, { gender: 'female', count: 2 }))
.toBe('She has 2 items');
});
});import { RegistryError, RegistryErrorType } from '@digitaldefiance/i18n-lib';
describe('Error Handling', () => {
it('should throw RegistryError for missing component', () => {
expect(() => {
engine.translate('missing', 'key');
}).toThrow(RegistryError);
});
it('should provide error metadata', () => {
try {
engine.translate('missing', 'key');
} catch (error) {
expect(error).toBeInstanceOf(RegistryError);
expect(error.type).toBe(RegistryErrorType.COMPONENT_NOT_FOUND);
expect(error.metadata).toEqual({ componentId: 'missing' });
}
});
});import * as fc from 'fast-check';
import { createPluralString } from '@digitaldefiance/i18n-lib';
describe('Pluralization Properties', () => {
it('should always return a string for any count', () => {
fc.assert(
fc.property(fc.integer(), (count) => {
const plural = createPluralString({
one: '{count} item',
other: '{count} items'
});
const result = engine.translate('test', 'items', { count });
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
})
);
});
});- Always reset engine state between tests using
PluginI18nEngine.resetAll() - Test with multiple languages to ensure translations work correctly
- Test edge cases like empty strings, special characters, and missing variables
- Use property-based tests for testing universal properties across many inputs
- Mock external dependencies when testing error conditions
When testing packages that depend on i18n-lib:
import { PluginI18nEngine } from '@digitaldefiance/i18n-lib';
import { YourService } from 'your-package';
describe('Service with i18n', () => {
beforeEach(() => {
// Set up i18n engine for your service
PluginI18nEngine.resetAll();
const engine = PluginI18nEngine.createInstance('test', languages);
// Register your component translations
});
afterEach(() => {
PluginI18nEngine.resetAll();
});
it('should use translated error messages', () => {
const service = new YourService();
expect(() => service.doSomething()).toThrow(/translated message/);
});
});MIT License - See LICENSE file for details
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass (
npm test) - Submit a pull request
- Issues: https://github.com/Digital-Defiance/i18n-lib/issues
- Documentation: See docs/ directory
- Examples: See tests/ directory
Version: 2.0.0
Status: Production Ready
Bundle Size: ~25KB (minified + gzipped)