@breadstone/ziegel-platform-localization β
Comprehensive localization and resource management for the ziegel platform. Provides localization managers, multiple providers, key resolvers, reactive localization, and advanced internationalization capabilities for enterprise applications.
Localization: Enterprise localization with multiple providers, caching, key resolution, and reactive updates for comprehensive internationalization.
π Overview β
@breadstone/ziegel-platform-localization provides:
- Localization Manager: Centralized localization management and resource loading
- Multiple Providers: Map, HTTP loader, composite, and empty providers
- Key Resolvers: Different naming conventions (camelCase, PascalCase, snake_case, kebab-case)
- Caching System: Efficient localization caching and performance optimization
- Value Parsers: Format and reference parsing for complex localization strings
- Builder Pattern: Fluent API for localization manager configuration
- Reactive Integration: RxJS integration for reactive localization updates
π¦ Installation β
npm install @breadstone/ziegel-platform-localization
# or
yarn add @breadstone/ziegel-platform-localizationπ§© Features & Usage Examples β
Localization Manager β
import {
LocalizationManager,
ILocalizationManager,
MapLocalizationProvider
} from '@breadstone/ziegel-platform-localization';
// Create localization resources
const englishResources = new Map([
['welcome.title', 'Welcome to our application'],
['welcome.message', 'Hello {name}, enjoy your stay!'],
['navigation.home', 'Home'],
['navigation.about', 'About'],
['navigation.contact', 'Contact'],
['forms.validation.required', 'This field is required'],
['forms.validation.email', 'Please enter a valid email address']
]);
const germanResources = new Map([
['welcome.title', 'Willkommen in unserer Anwendung'],
['welcome.message', 'Hallo {name}, viel SpaΓ beim Verweilen!'],
['navigation.home', 'Startseite'],
['navigation.about', 'Γber uns'],
['navigation.contact', 'Kontakt'],
['forms.validation.required', 'Dieses Feld ist erforderlich'],
['forms.validation.email', 'Bitte geben Sie eine gΓΌltige E-Mail-Adresse ein']
]);
// Setup providers
const englishProvider = new MapLocalizationProvider(englishResources);
const germanProvider = new MapLocalizationProvider(germanResources);
// Create localization manager
const localizationManager: ILocalizationManager = new LocalizationManager();
localizationManager.addProvider('en', englishProvider);
localizationManager.addProvider('de', germanProvider);
// Use localization
localizationManager.setCurrentLocale('de');
const welcomeTitle = localizationManager.getString('welcome.title');
const welcomeMessage = localizationManager.getString('welcome.message', { name: 'Hans' });
console.log(welcomeTitle); // "Willkommen in unserer Anwendung"
console.log(welcomeMessage); // "Hallo Hans, viel SpaΓ beim Verweilen!"Builder Pattern β
import {
LocalizationManagerBuilder,
ILocalizationManagerBuilder,
HttpLocalizationLoader
} from '@breadstone/ziegel-platform-localization';
// Build localization manager with fluent API
const builder: ILocalizationManagerBuilder = new LocalizationManagerBuilder();
const localizationManager = await builder
.withDefaultLocale('en')
.withFallbackLocale('en')
.withHttpProvider('en', '/api/locales/en.json')
.withHttpProvider('de', '/api/locales/de.json')
.withHttpProvider('fr', '/api/locales/fr.json')
.withCaching(true)
.withMissingHandler((key, locale) => {
console.warn(`Missing localization: ${key} for locale: ${locale}`);
return `[${key}]`;
})
.build();
// Use the built manager
const localizedString = localizationManager.getString('app.title');HTTP Localization Loader β
import {
LoaderLocalizationProvider,
HttpLocalizationLoader,
ILocalizationLoader
} from '@breadstone/ziegel-platform-localization';
// Create HTTP loader for dynamic resource loading
const httpLoader: ILocalizationLoader = new HttpLocalizationLoader({
baseUrl: '/api/locales',
format: 'json',
cacheTtl: 3600000, // 1 hour cache
retryAttempts: 3
});
// Create provider with loader
const loaderProvider = new LoaderLocalizationProvider(httpLoader);
// Load resources dynamically
const resources = await loaderProvider.getResources('de');
console.log('Loaded German resources:', resources);Key Resolvers β
import {
CamelCaseKeyResolver,
PascalCaseKeyResolver,
SnakeCaseKeyResolver,
KebabCaseKeyResolver,
IKeyResolver
} from '@breadstone/ziegel-platform-localization';
// Different key naming conventions
const camelResolver: IKeyResolver = new CamelCaseKeyResolver();
const pascalResolver: IKeyResolver = new PascalCaseKeyResolver();
const snakeResolver: IKeyResolver = new SnakeCaseKeyResolver();
const kebabResolver: IKeyResolver = new KebabCaseKeyResolver();
// Test key transformations
const originalKey = 'user_profile_settings';
console.log('Camel:', camelResolver.resolveKey(originalKey)); // "userProfileSettings"
console.log('Pascal:', pascalResolver.resolveKey(originalKey)); // "UserProfileSettings"
console.log('Snake:', snakeResolver.resolveKey(originalKey)); // "user_profile_settings"
console.log('Kebab:', kebabResolver.resolveKey(originalKey)); // "user-profile-settings"
// Use with localization manager
localizationManager.setKeyResolver(camelResolver);Composite Localization Provider β
import {
CompositeLocalizationProvider,
MapLocalizationProvider,
LoaderLocalizationProvider
} from '@breadstone/ziegel-platform-localization';
// Create multiple providers
const memoryProvider = new MapLocalizationProvider(new Map([
['app.title', 'My Application'],
['app.version', '1.0.0']
]));
const httpProvider = new LoaderLocalizationProvider(
new HttpLocalizationLoader({ baseUrl: '/api/locales' })
);
// Combine providers (memory provider has priority)
const compositeProvider = new CompositeLocalizationProvider([
memoryProvider,
httpProvider
]);
// Provider will check memory first, then HTTP
const localizationManager = new LocalizationManager();
localizationManager.addProvider('en', compositeProvider);Value Parsers β
import {
FormatLocalizationValueParser,
RefLocalizationValueParser,
ILocalizationValueParser
} from '@breadstone/ziegel-platform-localization';
// Format parser for interpolation
const formatParser: ILocalizationValueParser = new FormatLocalizationValueParser();
const formatResult = formatParser.parse(
'Hello {name}, you have {count} messages',
{ name: 'John', count: 5 }
);
console.log(formatResult); // "Hello John, you have 5 messages"
// Reference parser for cross-references
const refParser: ILocalizationValueParser = new RefLocalizationValueParser();
// Setup reference resolver
refParser.setResolver((key: string) => {
const refs = new Map([
['common.company', 'Acme Corp'],
['common.year', '2025']
]);
return refs.get(key) || `[${key}]`;
});
const refResult = refParser.parse(
'Copyright Β© {ref:common.year} {ref:common.company}',
{}
);
console.log(refResult); // "Copyright Β© 2025 Acme Corp"Reactive Localization β
import { fromLocalizable, loc, localize } from '@breadstone/ziegel-platform-localization';
import { map, distinctUntilChanged } from 'rxjs/operators';
// Create localizable object
class LocalizableComponent implements ILocalizable {
constructor(private localizationManager: ILocalizationManager) {}
getLocalizationManager(): ILocalizationManager {
return this.localizationManager;
}
}
const component = new LocalizableComponent(localizationManager);
// Create reactive localization streams
const title$ = fromLocalizable(component, 'welcome.title');
const message$ = loc(component, 'welcome.message', { name: 'User' });
// Subscribe to localization changes
title$.pipe(
distinctUntilChanged()
).subscribe(title => {
console.log('Title updated:', title);
document.title = title;
});
message$.pipe(
map(msg => msg.toUpperCase())
).subscribe(message => {
console.log('Message updated:', message);
});
// Trigger updates by changing locale
localizationManager.setCurrentLocale('de'); // Triggers reactive updatesCaching System β
import {
LocalizationCache,
ILocalizationCache,
LocalizationManager
} from '@breadstone/ziegel-platform-localization';
// Create custom cache implementation
class CustomLocalizationCache implements ILocalizationCache {
private cache = new Map<string, Map<string, string>>();
private ttl = 3600000; // 1 hour
private timestamps = new Map<string, number>();
get(locale: string): Map<string, string> | null {
const timestamp = this.timestamps.get(locale);
if (timestamp && Date.now() - timestamp > this.ttl) {
this.cache.delete(locale);
this.timestamps.delete(locale);
return null;
}
return this.cache.get(locale) || null;
}
set(locale: string, resources: Map<string, string>): void {
this.cache.set(locale, new Map(resources));
this.timestamps.set(locale, Date.now());
}
remove(locale: string): void {
this.cache.delete(locale);
this.timestamps.delete(locale);
}
clear(): void {
this.cache.clear();
this.timestamps.clear();
}
has(locale: string): boolean {
return this.cache.has(locale) &&
this.timestamps.has(locale) &&
Date.now() - this.timestamps.get(locale)! <= this.ttl;
}
}
// Use with localization manager
const customCache = new CustomLocalizationCache();
const cachedManager = new LocalizationManager();
cachedManager.setCache(customCache);Missing Localization Handlers β
import {
defaultMissingLocalizationHandler,
emptyLocalizationHandler,
IMissingLocalizationHandlerFunc
} from '@breadstone/ziegel-platform-localization';
// Use built-in handlers
localizationManager.setMissingHandler(defaultMissingLocalizationHandler);
// Or use empty handler
localizationManager.setMissingHandler(emptyLocalizationHandler);
// Create custom missing handler
const customMissingHandler: IMissingLocalizationHandlerFunc = (key: string, locale: string) => {
// Log to analytics
console.warn(`Missing localization key: ${key} for locale: ${locale}`);
// Send to monitoring service
fetch('/api/missing-translations', {
method: 'POST',
body: JSON.stringify({ key, locale, timestamp: new Date() })
});
// Return fallback value
return `β οΈ ${key}`;
};
localizationManager.setMissingHandler(customMissingHandler);π Package Exports β
import {
// Core Interfaces
ILocalizable,
ILocalizationCache,
ILocalizationManager,
IMissingLocalizationHandlerFunc,
// Manager & Builder
LocalizationManager,
LocalizationManagerLocator,
ILocalizationManagerBuilder,
LocalizationManagerBuilder,
// Caching
LocalizationCache,
// Providers
ILocalizationProvider,
LocalizationProviderBase,
MapLocalizationProvider,
LoaderLocalizationProvider,
CompositeLocalizationProvider,
EmptyLocalizationProvider,
// Loaders
ILocalizationLoader,
HttpLocalizationLoader,
// Key Resolvers
IKeyResolver,
KeyResolverBase,
CamelCaseKeyResolver,
PascalCaseKeyResolver,
SnakeCaseKeyResolver,
KebabCaseKeyResolver,
EmptyKeyResolver,
// Value Parsers
ILocalizationValueParser,
FormatLocalizationValueParser,
RefLocalizationValueParser,
// Missing Handlers
defaultMissingLocalizationHandler,
emptyLocalizationHandler,
// Reactive Extensions
fromLocalizable,
loc,
localize
} from '@breadstone/ziegel-platform-localization';π§ Advanced Usage β
Multi-Tenant Localization β
import { LocalizationManager, MapLocalizationProvider } from '@breadstone/ziegel-platform-localization';
class MultiTenantLocalizationService {
private managers = new Map<string, ILocalizationManager>();
getManager(tenantId: string): ILocalizationManager {
if (!this.managers.has(tenantId)) {
const manager = new LocalizationManager();
this.setupTenantLocalization(manager, tenantId);
this.managers.set(tenantId, manager);
}
return this.managers.get(tenantId)!;
}
private async setupTenantLocalization(manager: ILocalizationManager, tenantId: string): Promise<void> {
// Load tenant-specific resources
const resources = await this.loadTenantResources(tenantId);
for (const [locale, localeResources] of resources) {
const provider = new MapLocalizationProvider(localeResources);
manager.addProvider(locale, provider);
}
}
private async loadTenantResources(tenantId: string): Promise<Map<string, Map<string, string>>> {
// Load from tenant-specific API endpoint
const response = await fetch(`/api/tenants/${tenantId}/locales`);
const data = await response.json();
const resources = new Map<string, Map<string, string>>();
for (const [locale, translations] of Object.entries(data)) {
resources.set(locale, new Map(Object.entries(translations as Record<string, string>)));
}
return resources;
}
}Lazy Loading with Namespaces β
class NamespacedLocalizationManager {
private loadedNamespaces = new Set<string>();
constructor(private manager: ILocalizationManager) {}
async getString(key: string, params?: Record<string, any>): Promise<string> {
const namespace = this.extractNamespace(key);
if (!this.loadedNamespaces.has(namespace)) {
await this.loadNamespace(namespace);
this.loadedNamespaces.add(namespace);
}
return this.manager.getString(key, params);
}
private extractNamespace(key: string): string {
return key.split('.')[0];
}
private async loadNamespace(namespace: string): Promise<void> {
const response = await fetch(`/api/locales/${this.manager.getCurrentLocale()}/${namespace}.json`);
const resources = await response.json();
const resourceMap = new Map<string, string>();
for (const [key, value] of Object.entries(resources)) {
resourceMap.set(`${namespace}.${key}`, value as string);
}
const provider = new MapLocalizationProvider(resourceMap);
this.manager.addProvider(this.manager.getCurrentLocale(), provider);
}
}π― Integration Examples β
React Hook Integration β
import React, { createContext, useContext, useEffect, useState } from 'react';
import { ILocalizationManager } from '@breadstone/ziegel-platform-localization';
interface LocalizationContextValue {
manager: ILocalizationManager;
locale: string;
t: (key: string, params?: Record<string, any>) => string;
setLocale: (locale: string) => void;
}
const LocalizationContext = createContext<LocalizationContextValue | null>(null);
export function LocalizationProvider({
children,
manager
}: {
children: React.ReactNode;
manager: ILocalizationManager;
}) {
const [locale, setLocale] = useState(manager.getCurrentLocale());
const t = (key: string, params?: Record<string, any>) => {
return manager.getString(key, params);
};
const handleSetLocale = (newLocale: string) => {
manager.setCurrentLocale(newLocale);
setLocale(newLocale);
};
return (
<LocalizationContext.Provider value={{ manager, locale, t, setLocale: handleSetLocale }}>
{children}
</LocalizationContext.Provider>
);
}
export function useLocalization() {
const context = useContext(LocalizationContext);
if (!context) {
throw new Error('useLocalization must be used within LocalizationProvider');
}
return context;
}
// Usage in component
function MyComponent() {
const { t, locale, setLocale } = useLocalization();
return (
<div>
<h1>{t('welcome.title')}</h1>
<p>{t('welcome.message', { name: 'World' })}</p>
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">FranΓ§ais</option>
</select>
</div>
);
}π API Documentation β
For detailed API documentation, visit: API Docs
Related Packages β
- @breadstone/ziegel-intl: Core internationalization utilities
- @breadstone/ziegel-platform: Core platform services
- @breadstone/ziegel-rx: Reactive extensions
- @breadstone/ziegel-core: Foundation utilities
License β
MIT
Issues β
Please report bugs and feature requests in the Issue Tracker
Part of the ziegel Enterprise TypeScript Framework } from '@ziegel/platform-localization';
// Configure dynamic loading const loader = new DynamicResourceLoader({ strategy: ResourceLoadingStrategy.OnDemand, preloadLocales: ['en', 'de'], chunkSize: 50, // Load 50 keys at a time retryPolicy: { maxRetries: 3, backoffFactor: 2 } });
// Load specific namespaces await loader.loadNamespace('common', 'de'); await loader.loadNamespace('forms', 'de');
// Preload critical resources await loader.preloadCritical(['navigation', 'errors']);
### Locale Detection
```typescript
import {
LocaleDetector,
DetectionStrategy
} from '@ziegel/platform-localization';
// Configure locale detection
const detector = new LocaleDetector({
strategies: [
DetectionStrategy.QueryParameter,
DetectionStrategy.LocalStorage,
DetectionStrategy.Navigator,
DetectionStrategy.AcceptLanguage
],
fallback: 'en',
cache: true
});
// Detect user locale
const detectedLocale = await detector.detect();
console.log('Detected locale:', detectedLocale);
// Manual locale setting with persistence
await detector.setLocale('de-DE', { persist: true });Advanced Features β
Interpolation and Formatting β
import {
Translator,
InterpolationEngine,
FormatterRegistry
} from '@ziegel/platform-localization';
// Configure custom formatters
const formatters = new FormatterRegistry();
formatters.register('currency', (value, locale, options) => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: options.currency || 'EUR'
}).format(value);
});
formatters.register('relative', (value, locale) => {
return new Intl.RelativeTimeFormat(locale).format(value.amount, value.unit);
});
// Create translator with advanced interpolation
const translator = new Translator({
interpolation: new InterpolationEngine({
formatters,
escapeHtml: true,
maxNestingDepth: 3
})
});
// Use advanced formatting
const message = translator.translate('product.price', {
price: { value: 29.99, currency: 'EUR' },
discount: { amount: -2, unit: 'day' }
});
// Result: "Price: β¬29.99 (offer ends 2 days ago)"Pluralization β
import {
PluralResolver,
PluralRule
} from '@ziegel/platform-localization';
// Configure pluralization rules
const pluralResolver = new PluralResolver({
rules: {
en: PluralRule.Simple, // one, other
de: PluralRule.Simple,
ru: PluralRule.Complex, // zero, one, few, many, other
ar: PluralRule.Arabic // zero, one, two, few, many, other
}
});
// Define pluralized resources
const resources = {
en: {
items: {
zero: 'No items',
one: '1 item',
other: '{{count}} items'
}
},
ru: {
items: {
zero: 'ΠΠ΅Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²',
one: '{{count}} ΡΠ»Π΅ΠΌΠ΅Π½Ρ',
few: '{{count}} ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°',
many: '{{count}} ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²',
other: '{{count}} ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°'
}
}
};
// Use pluralization
const translator = new Translator({ pluralResolver });
console.log(translator.translate('items', { count: 0 })); // "No items"
console.log(translator.translate('items', { count: 1 })); // "1 item"
console.log(translator.translate('items', { count: 5 })); // "5 items"Context-Aware Translation β
import {
ContextualTranslator,
TranslationContext
} from '@ziegel/platform-localization';
// Define context-aware translations
const contextualResources = {
en: {
save: {
button: 'Save',
'button_urgent': 'Save Now!',
'menu_item': 'Save File',
'shortcut': 'Ctrl+S'
}
}
};
// Create contextual translator
const translator = new ContextualTranslator({
resources: contextualResources,
contextResolver: (key, context) => {
if (context.component === 'button' && context.priority === 'urgent') {
return `${key}_urgent`;
}
if (context.component) {
return `${key}_${context.component}`;
}
return key;
}
});
// Use with context
const buttonText = translator.translate('save', {
component: 'button',
priority: 'urgent'
}); // "Save Now!"Hooks and React Integration β
Core Hooks β
import {
useLocalization,
useTranslation,
useLocale,
useResourceLoader
} from '@ziegel/platform-localization';
function MyComponent() {
// Main localization hook
const { t, locale, setLocale, isReady } = useLocalization();
// Translation-only hook
const translate = useTranslation();
// Locale management hook
const {
currentLocale,
availableLocales,
switchLocale,
isSupported
} = useLocale();
// Resource loading hook
const {
loadResources,
isLoading,
loadedNamespaces
} = useResourceLoader();
useEffect(() => {
loadResources(['forms', 'validation']);
}, [loadResources]);
return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => switchLocale('de')}>
Deutsch
</button>
</div>
);
}Advanced Hooks β
import {
useTranslationWithFallback,
useInterpolatedTranslation,
usePluralTranslation
} from '@ziegel/platform-localization';
function AdvancedComponent({ items, user }) {
// Translation with fallback
const tWithFallback = useTranslationWithFallback({
fallbackLocale: 'en',
fallbackNamespace: 'common'
});
// Interpolated translation
const tInterpolated = useInterpolatedTranslation({
formatters: ['currency', 'date', 'relative']
});
// Plural translation
const tPlural = usePluralTranslation();
return (
<div>
<h1>{tWithFallback('greeting', { name: user.name })}</h1>
<p>{tInterpolated('lastSeen', { date: user.lastSeen })}</p>
<span>{tPlural('itemCount', { count: items.length })}</span>
</div>
);
}Configuration Options β
interface LocalizationConfig {
// Core settings
defaultLocale: string;
supportedLocales: string[];
fallbackLocale?: string;
// Resource loading
resourceLoader: {
type: 'static' | 'dynamic' | 'hybrid';
basePath?: string;
format?: 'json' | 'yaml' | 'po';
loader?: ResourceLoader;
};
// Detection
detection: {
strategies: DetectionStrategy[];
caching: boolean;
cookieName?: string;
storageKey?: string;
};
// Interpolation
interpolation: {
prefix: string;
suffix: string;
escapeValue: boolean;
formatters: FormatterRegistry;
};
// Pluralization
pluralization: {
resolver: PluralResolver;
keyGenerator: (key: string, count: number) => string;
};
// Performance
performance: {
lazy: boolean;
preload: string[];
caching: CacheConfig;
debounceMs: number;
};
// Development
development: {
debug: boolean;
missingKeyHandler: (locale: string, key: string) => void;
saveMissing: boolean;
};
}Best Practices β
Resource Organization β
// Organize by feature/domain
const resourceStructure = {
common: {
navigation: {},
buttons: {},
forms: {}
},
features: {
auth: {},
dashboard: {},
settings: {}
},
errors: {
validation: {},
network: {},
business: {}
}
};
// Use consistent naming conventions
const namingConventions = {
keys: 'camelCase',
namespaces: 'kebab-case',
files: 'kebab-case.json'
};Performance Optimization β
// Lazy load non-critical resources
const optimizedConfig = createLocalizationConfig({
performance: {
lazy: true,
preload: ['common', 'navigation'],
caching: {
strategy: 'lru',
maxSize: 100,
ttl: 3600000
}
},
resourceLoader: {
type: 'dynamic',
chunkSize: 25,
compression: true
}
});
// Implement resource splitting
const splitResources = {
critical: ['navigation', 'errors'],
secondary: ['forms', 'tooltips'],
tertiary: ['help', 'documentation']
};Testing β
import {
createMockLocalizationProvider,
MockResourceLoader
} from '@ziegel/platform-localization/testing';
describe('Localization', () => {
let provider: LocalizationProvider;
beforeEach(() => {
const mockLoader = new MockResourceLoader({
en: { welcome: 'Welcome' },
de: { welcome: 'Willkommen' }
});
provider = createMockLocalizationProvider({
defaultLocale: 'en',
resourceLoader: mockLoader
});
});
it('should translate correctly', async () => {
await provider.setLocale('de');
const translation = provider.translate('welcome');
expect(translation).toBe('Willkommen');
});
it('should handle missing keys gracefully', () => {
const translation = provider.translate('missing.key');
expect(translation).toBe('missing.key');
});
});Migration Guide β
From react-i18next β
// Old: react-i18next
import { useTranslation } from 'react-i18next';
function Component() {
const { t, i18n } = useTranslation();
return <h1>{t('welcome')}</h1>;
}
// New: ziegel-platform-localization
import { useLocalization } from '@ziegel/platform-localization';
function Component() {
const { t, setLocale } = useLocalization();
return <h1>{t('welcome')}</h1>;
}Configuration Migration β
// Old: i18next config
i18next.init({
lng: 'en',
fallbackLng: 'en',
resources: {
en: { translation: require('./en.json') }
}
});
// New: ziegel config
const config = createLocalizationConfig({
defaultLocale: 'en',
fallbackLocale: 'en',
resourceLoader: {
type: 'static',
resources: {
en: require('./en.json')
}
}
});Related Packages β
@ziegel/platform- Core platform functionality@ziegel/platform-http- HTTP client for remote resources@ziegel/platform-logging- Logging integration@ziegel/intl- Basic internationalization utilities
API Reference β
For detailed API documentation, see the auto-generated API reference.