@cocoar/localization 0.1.0-beta.114

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.
@@ -0,0 +1,1194 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Signal, InjectionToken, EnvironmentProviders, PipeTransform, OnDestroy, Type, Provider } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+ import { HttpClient } from '@angular/common/http';
5
+ import { Temporal } from '@js-temporal/polyfill';
6
+
7
+ /**
8
+ * Core locale service responsible for language management.
9
+ *
10
+ * This service manages the current language state and notifies consumers
11
+ * about language changes. It serves as the single source of truth for
12
+ * the application's current language.
13
+ *
14
+ * Other systems (i18n, localization/formatting) can subscribe to language
15
+ * changes and react accordingly.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { inject } from '@angular/core';
20
+ * import { CoarLocalizationService } from '@cocoar/localization';
21
+ *
22
+ * export class MyComponent {
23
+ * private readonly locale = inject(CoarLocalizationService);
24
+ *
25
+ * constructor() {
26
+ * // Get current language
27
+ * console.log(this.locale.getCurrentLanguage());
28
+ *
29
+ * // React to changes via Signal
30
+ * effect(() => {
31
+ * console.log('Language changed:', this.locale.language());
32
+ * });
33
+ *
34
+ * // React to changes via Observable
35
+ * this.locale.languageChanged$.subscribe(lang => {
36
+ * console.log('Language changed:', lang);
37
+ * });
38
+ * }
39
+ *
40
+ * changeLanguage() {
41
+ * this.locale.setLanguage('de');
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ declare class CoarLocalizationService {
47
+ private readonly config;
48
+ private readonly localeDataStore;
49
+ private readonly localeDataLoaders;
50
+ private readonly languageSignal;
51
+ private readonly languageChangedSubject;
52
+ /**
53
+ * Signal containing the current language.
54
+ * Updates automatically when the language changes.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const locale = inject(CoarLocalizationService);
59
+ *
60
+ * // Use in computed
61
+ * const greeting = computed(() =>
62
+ * locale.language() === 'de' ? 'Hallo' : 'Hello'
63
+ * );
64
+ *
65
+ * // Use in effect
66
+ * effect(() => {
67
+ * console.log('Current language:', locale.language());
68
+ * });
69
+ * ```
70
+ */
71
+ readonly language: Signal<string>;
72
+ /**
73
+ * Observable that emits when the language changes.
74
+ * Emits the new language code immediately after the change.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * locale.languageChanged$.subscribe(newLang => {
79
+ * console.log('Language changed to:', newLang);
80
+ * // Reload translations, update formatting rules, etc.
81
+ * });
82
+ * ```
83
+ */
84
+ readonly languageChanged$: Observable<string>;
85
+ /**
86
+ * Get the current language code.
87
+ *
88
+ * @returns The current language code (e.g., 'en', 'de', 'en-US')
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const currentLang = locale.getCurrentLanguage();
93
+ * console.log(currentLang); // 'en'
94
+ * ```
95
+ */
96
+ getCurrentLanguage(): string;
97
+ /**
98
+ * Set the current language.
99
+ *
100
+ * Automatically loads locale data from all configured sources (Intl, HTTP, etc.)
101
+ * and deep-merges them in order. Later sources override earlier ones.
102
+ *
103
+ * Cached data is not reloaded, so switching back to a previous language is instant.
104
+ *
105
+ * @param language - The new language code (e.g., 'en', 'de', 'en-US')
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * // Change to German (loads from Intl + HTTP, merges overrides)
110
+ * await locale.setLanguage('de');
111
+ *
112
+ * // Change back to English (uses cached data, instant)
113
+ * await locale.setLanguage('en');
114
+ * ```
115
+ */
116
+ setLanguage(language: string): Promise<void>;
117
+ /**
118
+ * Load locale data from all sources and deep-merge them.
119
+ *
120
+ * Sources are executed in order:
121
+ * 1. Intl (always first, provides complete defaults)
122
+ * 2. HTTP (if configured, merges business overrides)
123
+ * 3. Custom sources (if provided, can add dynamic updates)
124
+ *
125
+ * @param language - Language code to load
126
+ */
127
+ private loadAndmergeLocalizationData;
128
+ /**
129
+ * Preload locale data for a language without switching to it.
130
+ * Useful for avoiding UI flicker when switching languages.
131
+ *
132
+ * @param language - The language code to preload
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * // Preload German locale data before switching
137
+ * await locale.preloadLocaleData('de');
138
+ * await locale.setLanguage('de'); // Instant switch, no loading delay
139
+ * ```
140
+ */
141
+ preloadLocaleData(language: string): Promise<void>;
142
+ /**
143
+ * Get the list of available languages configured for the application.
144
+ *
145
+ * @returns Array of available language codes, or empty array if not configured
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const langs = locale.getAvailableLanguages(); // ['en', 'de', 'fr']
150
+ * ```
151
+ */
152
+ getAvailableLanguages(): string[];
153
+ /**
154
+ * Get the default language configured for the application.
155
+ *
156
+ * @returns The default language code
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const defaultLang = locale.getDefaultLanguage(); // 'en'
161
+ * ```
162
+ */
163
+ getDefaultLanguage(): string;
164
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarLocalizationService, never>;
165
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarLocalizationService>;
166
+ }
167
+
168
+ /**
169
+ * Locale data structure for date and number formatting.
170
+ *
171
+ * This data can be loaded from various sources:
172
+ * - Browser Intl API (CoarIntlLocaleDataLoader) - default
173
+ * - JSON files (CoarHttpLocaleDataLoader) - for overrides
174
+ * - SignalR (future) - for dynamic backend-controlled formatting
175
+ */
176
+ /**
177
+ * Complete locale data for a specific language/region.
178
+ */
179
+ interface CoarLocalizationData {
180
+ /** Locale code (e.g., 'en', 'de', 'en-US', 'de-DE') */
181
+ code: string;
182
+ /** Date formatting configuration */
183
+ date: CoarDateFormatData;
184
+ /** Number formatting configuration */
185
+ number: CoarNumberFormatData;
186
+ /** Currency formatting configuration */
187
+ currency: CoarCurrencyFormatData;
188
+ /** Percent formatting configuration */
189
+ percent: CoarPercentFormatData;
190
+ }
191
+ /**
192
+ * Date formatting configuration.
193
+ */
194
+ interface CoarDateFormatData {
195
+ /**
196
+ * Date format pattern: 'dd.mm.yyyy', 'dd/mm/yyyy', 'mm/dd/yyyy', 'yyyy-mm-dd'
197
+ */
198
+ pattern: 'dd.mm.yyyy' | 'dd/mm/yyyy' | 'mm/dd/yyyy' | 'yyyy-mm-dd';
199
+ /**
200
+ * First day of week (0 = Sunday, 1 = Monday, ..., 6 = Saturday).
201
+ */
202
+ firstDayOfWeek: number;
203
+ /** Localized month names (full) */
204
+ monthNames: string[];
205
+ /** Localized month names (short) */
206
+ monthNamesShort: string[];
207
+ /** Localized day names (full) */
208
+ dayNames: string[];
209
+ /** Localized day names (short) */
210
+ dayNamesShort: string[];
211
+ /** AM/PM labels */
212
+ amPm: [string, string];
213
+ }
214
+ /**
215
+ * Number formatting configuration.
216
+ */
217
+ interface CoarNumberFormatData {
218
+ /** Decimal separator (e.g., "." for English, "," for German) */
219
+ decimal: string;
220
+ /** Thousands/group separator (e.g., "," for English, "." for German) */
221
+ group: string;
222
+ /** Grouping pattern ([3] means group every 3 digits) */
223
+ grouping: number[];
224
+ }
225
+ /**
226
+ * Currency formatting configuration.
227
+ */
228
+ interface CoarCurrencyFormatData {
229
+ /** Default currency code (e.g., "USD", "EUR") */
230
+ default: string;
231
+ /** Currency symbols map (e.g., { "USD": "$", "EUR": "€" }) */
232
+ symbols: Record<string, string>;
233
+ /** Symbol position: 'before' ($1,234) or 'after' (1,234 €) */
234
+ position: 'before' | 'after';
235
+ /** Space between symbol and number */
236
+ spacing: boolean;
237
+ /** Decimal places for currency display */
238
+ decimals: number;
239
+ }
240
+ /**
241
+ * Percent formatting configuration.
242
+ */
243
+ interface CoarPercentFormatData {
244
+ /** Percent symbol (usually "%") */
245
+ symbol: string;
246
+ /** Space between number and symbol (e.g., "25%" vs "25 %") */
247
+ spacing: boolean;
248
+ /** Decimal places for percent display */
249
+ decimals: number;
250
+ }
251
+
252
+ /**
253
+ * Abstract loader for locale data.
254
+ * Implement this interface to load locale data from any source (HTTP, memory, database, etc.).
255
+ */
256
+ declare abstract class CoarLocalizationDataLoader {
257
+ /**
258
+ * Load locale data for a specific locale.
259
+ * @param locale Locale code (e.g., 'en', 'de', 'en-US')
260
+ * @returns Observable that emits the locale data
261
+ */
262
+ abstract loadLocaleData(locale: string): Observable<CoarLocalizationData>;
263
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarLocalizationDataLoader, never>;
264
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarLocalizationDataLoader>;
265
+ }
266
+ /**
267
+ * HTTP-based locale data loader.
268
+ * Loads locale data from JSON files via HTTP.
269
+ *
270
+ * Supports custom URL patterns and headers for authentication/configuration.
271
+ * Typically used as a second source (after Intl) to provide business-specific overrides.
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * // Simple base path
276
+ * new CoarHttpLocaleDataLoader(httpClient, (lang) => `/locales/${lang}.json`)
277
+ *
278
+ * // Custom URL pattern
279
+ * new CoarHttpLocaleDataLoader(httpClient, (lang) => `/api/config/intl-${lang}.json`)
280
+ *
281
+ * // With headers
282
+ * new CoarHttpLocaleDataLoader(
283
+ * httpClient,
284
+ * (lang) => `/api/locales/${lang}.json`,
285
+ * { 'Authorization': 'Bearer token' }
286
+ * )
287
+ * ```
288
+ */
289
+ declare class CoarHttpLocaleDataLoader extends CoarLocalizationDataLoader {
290
+ private readonly httpClient;
291
+ private readonly urlFn;
292
+ private readonly headers?;
293
+ constructor(httpClient: HttpClient, urlFn: (language: string) => string, headers?: Record<string, string> | undefined);
294
+ loadLocaleData(locale: string): Observable<CoarLocalizationData>;
295
+ }
296
+
297
+ /**
298
+ * Configuration for the locale system.
299
+ */
300
+ interface CoarLocalizationConfig {
301
+ /**
302
+ * List of available languages in the application.
303
+ */
304
+ availableLanguages: string[];
305
+ /**
306
+ * Default language to use on initialization.
307
+ */
308
+ defaultLanguage: string;
309
+ }
310
+ /**
311
+ * Injection token for locale configuration.
312
+ */
313
+ declare const COAR_LOCALIZATION_CONFIG: InjectionToken<CoarLocalizationConfig>;
314
+ /**
315
+ * Injection token for locale data loaders (multi-provider).
316
+ * Loaders are executed in order and results are deep-merged.
317
+ * Intl loader is always first (provides complete defaults).
318
+ */
319
+ declare const COAR_LOCALIZATION_DATA_LOADERS: InjectionToken<CoarLocalizationDataLoader[]>;
320
+ /**
321
+ * Provides the core locale system (language management).
322
+ *
323
+ * This only provides the language service - no locale data sources.
324
+ * Add locale data sources separately with:
325
+ * - `provideCoarIntlLocalizationSource()` - Browser Intl API (recommended as first source)
326
+ * - `provideCoarHttpLocalizationSource()` - Load from JSON files
327
+ * - Custom sources via `COAR_LOCALIZATION_DATA_LOADERS` multi-provider
328
+ *
329
+ * Sources are executed in registration order and deep-merged.
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * // Minimal setup (no formatting data)
334
+ * provideCoarLocalization({
335
+ * availableLanguages: ['en', 'de'],
336
+ * defaultLanguage: 'en',
337
+ * })
338
+ *
339
+ * // With Intl defaults
340
+ * provideCoarLocalization({...}),
341
+ * provideCoarIntlLocalizationSource(),
342
+ *
343
+ * // With Intl + HTTP overrides
344
+ * provideCoarLocalization({...}),
345
+ * provideCoarIntlLocalizationSource(),
346
+ * provideCoarHttpLocalizationSource({
347
+ * url: (lang) => `/locales/${lang}.json`
348
+ * }),
349
+ * ```
350
+ */
351
+ declare function provideCoarLocalization(config: CoarLocalizationConfig): EnvironmentProviders;
352
+
353
+ /**
354
+ * Signal-based storage for locale data.
355
+ * Provides reactive access to loaded locale formatting rules.
356
+ */
357
+ declare class CoarLocalizationDataStore {
358
+ private readonly store;
359
+ /**
360
+ * Set locale data for a specific locale.
361
+ * @param locale Locale code (e.g., 'en', 'de', 'en-US')
362
+ * @param data Locale data
363
+ */
364
+ setLocaleData(locale: string, data: CoarLocalizationData): void;
365
+ /**
366
+ * Get locale data for a specific locale.
367
+ * @param locale Locale code (e.g., 'en', 'de', 'en-US')
368
+ * @returns Locale data or undefined if not loaded
369
+ */
370
+ getLocaleData(locale: string): CoarLocalizationData | undefined;
371
+ /**
372
+ * Check if locale data is loaded for a specific locale.
373
+ * @param locale Locale code (e.g., 'en', 'de', 'en-US')
374
+ * @returns True if locale data is loaded
375
+ */
376
+ hasLocaleData(locale: string): boolean;
377
+ /**
378
+ * Remove locale data for a specific locale.
379
+ * @param locale Locale code (e.g., 'en', 'de', 'en-US')
380
+ */
381
+ removeLocaleData(locale: string): void;
382
+ /**
383
+ * Clear all loaded locale data.
384
+ */
385
+ clear(): void;
386
+ }
387
+
388
+ /**
389
+ * Locale data loader that detects formatting from browser's Intl API.
390
+ *
391
+ * This is the default loader and works for all locales without requiring JSON files.
392
+ * Use this when you want formatting to match the user's browser locale automatically.
393
+ *
394
+ * For business-specific overrides (e.g., force Monday as first day of week),
395
+ * use CoarHttpLocaleDataLoader to load custom JSON files.
396
+ */
397
+ declare class CoarIntlLocaleDataLoader extends CoarLocalizationDataLoader {
398
+ loadLocaleData(locale: string): Observable<CoarLocalizationData>;
399
+ /**
400
+ * Detect all locale formatting from browser Intl API.
401
+ */
402
+ private detectFromIntl;
403
+ /**
404
+ * Detect date formatting from Intl.DateTimeFormat.
405
+ */
406
+ private detectDateFormat;
407
+ /**
408
+ * Detect number formatting from Intl.NumberFormat.
409
+ */
410
+ private detectNumberFormat;
411
+ /**
412
+ * Detect currency formatting from Intl.NumberFormat.
413
+ */
414
+ private detectCurrencyFormat;
415
+ /**
416
+ * Detect percent formatting from Intl.NumberFormat.
417
+ */
418
+ private detectPercentFormat;
419
+ }
420
+
421
+ /**
422
+ * Provides browser Intl API as a locale data source.
423
+ *
424
+ * This loader detects date/number formatting from the browser's Intl API
425
+ * and provides complete locale data for any language without requiring JSON files.
426
+ *
427
+ * **Recommended as the first source** to provide complete defaults that other
428
+ * sources can override.
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * // Intl only (pure browser defaults)
433
+ * provideCoarLocalization({ defaultLanguage: 'en', availableLanguages: ['en', 'de'] }),
434
+ * provideCoarIntlLocalizationSource(),
435
+ *
436
+ * // Intl + HTTP overrides (recommended)
437
+ * provideCoarLocalization({...}),
438
+ * provideCoarIntlLocalizationSource(), // First source (complete defaults)
439
+ * provideCoarHttpLocalizationSource({ // Second source (business overrides)
440
+ * url: (lang) => `/locales/${lang}.json`
441
+ * }),
442
+ * ```
443
+ */
444
+ declare function provideCoarIntlLocalizationSource(): EnvironmentProviders;
445
+
446
+ /**
447
+ * Configuration for HTTP locale data source.
448
+ */
449
+ interface CoarHttpLocaleSourceConfig {
450
+ /**
451
+ * URL generator function that returns the URL for a given language.
452
+ *
453
+ * @param language - Language code (e.g., 'en', 'de', 'en-US')
454
+ * @returns URL to load locale data from
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * url: (lang) => `/locales/${lang}.json`
459
+ * url: (lang) => `/api/config/intl-${lang}.json`
460
+ * url: (lang) => `https://cdn.example.com/locales/${lang}.json`
461
+ * ```
462
+ */
463
+ url: (language: string) => string;
464
+ /**
465
+ * Optional HTTP headers to include in requests.
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * headers: {
470
+ * 'Authorization': 'Bearer token123',
471
+ * 'X-Custom-Header': 'value'
472
+ * }
473
+ * ```
474
+ */
475
+ headers?: Record<string, string>;
476
+ }
477
+ /**
478
+ * Provides HTTP as a locale data source.
479
+ *
480
+ * This loader fetches locale data from JSON files via HTTP.
481
+ * Typically used as a second source (after Intl) to provide business-specific
482
+ * overrides like forced firstDayOfWeek, custom separators, etc.
483
+ *
484
+ * @example
485
+ * ```ts
486
+ * // Basic usage (default URL pattern)
487
+ * provideCoarHttpLocalizationSource({
488
+ * url: (lang) => `/locales/${lang}.json`
489
+ * })
490
+ *
491
+ * // Custom URL pattern
492
+ * provideCoarHttpLocalizationSource({
493
+ * url: (lang) => `/api/config/intl-${lang}.json`
494
+ * })
495
+ *
496
+ * // With authentication
497
+ * provideCoarHttpLocalizationSource({
498
+ * url: (lang) => `/api/locales/${lang}.json`,
499
+ * headers: {
500
+ * 'Authorization': 'Bearer ' + getToken()
501
+ * }
502
+ * })
503
+ * ```
504
+ *
505
+ * Expected file structure:
506
+ * ```
507
+ * public/locales/
508
+ * en.json - English overrides
509
+ * de.json - German overrides
510
+ * ```
511
+ *
512
+ * JSON files should contain partial CoarLocalizationData:
513
+ * ```json
514
+ * {
515
+ * "code": "de",
516
+ * "date": {
517
+ * "firstDayOfWeek": 1
518
+ * }
519
+ * }
520
+ * ```
521
+ */
522
+ declare function provideCoarHttpLocalizationSource(config: CoarHttpLocaleSourceConfig): EnvironmentProviders;
523
+
524
+ /**
525
+ * Deep merge utility for locale data.
526
+ * Later sources override earlier sources at the field level.
527
+ *
528
+ * @example
529
+ * ```ts
530
+ * const intl = { date: { pattern: 'dd.mm.yyyy', firstDayOfWeek: 1 }, number: {...} };
531
+ * const http = { date: { firstDayOfWeek: 0 } }; // Only override firstDayOfWeek
532
+ *
533
+ * const result = mergeLocalizationData([intl, http]);
534
+ * // Result: { date: { pattern: 'dd.mm.yyyy', firstDayOfWeek: 0 }, number: {...} }
535
+ * ```
536
+ */
537
+ declare function mergeLocalizationData(sources: Partial<CoarLocalizationData>[]): CoarLocalizationData | null;
538
+
539
+ /**
540
+ * Pipe for formatting dates using locale-specific configuration.
541
+ *
542
+ * @example
543
+ * ```html
544
+ * <span>{{ myDate | coarDate }}</span>
545
+ * <span>{{ myDate | coarDate:'de' }}</span>
546
+ * ```
547
+ */
548
+ declare class CoarDatePipe implements PipeTransform {
549
+ private readonly localeDataStore;
550
+ private readonly localeService;
551
+ transform(value: Temporal.PlainDate | Date | string | null | undefined, locale?: string): string;
552
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarDatePipe, never>;
553
+ static ɵpipe: i0.ɵɵPipeDeclaration<CoarDatePipe, "coarDate", true>;
554
+ }
555
+
556
+ /**
557
+ * Pipe for formatting numbers using locale-specific configuration.
558
+ *
559
+ * @example
560
+ * ```html
561
+ * <span>{{ 1234.56 | coarNumber }}</span>
562
+ * <span>{{ 1234.56 | coarNumber:'de':2 }}</span>
563
+ * ```
564
+ */
565
+ declare class CoarNumberPipe implements PipeTransform {
566
+ private readonly localeDataStore;
567
+ private readonly localeService;
568
+ transform(value: number | null | undefined, locale?: string, decimals?: number): string;
569
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarNumberPipe, never>;
570
+ static ɵpipe: i0.ɵɵPipeDeclaration<CoarNumberPipe, "coarNumber", true>;
571
+ }
572
+
573
+ /**
574
+ * Pipe for formatting currency using locale-specific configuration.
575
+ *
576
+ * @example
577
+ * ```html
578
+ * <span>{{ 1234.56 | coarCurrency }}</span>
579
+ * <span>{{ 1234.56 | coarCurrency:'de':'EUR' }}</span>
580
+ * ```
581
+ */
582
+ declare class CoarCurrencyPipe implements PipeTransform {
583
+ private readonly localeDataStore;
584
+ private readonly localeService;
585
+ transform(value: number | null | undefined, locale?: string, currency?: string): string;
586
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarCurrencyPipe, never>;
587
+ static ɵpipe: i0.ɵɵPipeDeclaration<CoarCurrencyPipe, "coarCurrency", true>;
588
+ }
589
+
590
+ /**
591
+ * Pipe for formatting percentages using locale-specific configuration.
592
+ *
593
+ * Expects a decimal value (0.25 = 25%).
594
+ *
595
+ * @example
596
+ * ```html
597
+ * <span>{{ 0.25 | coarPercent }}</span>
598
+ * <span>{{ 0.25 | coarPercent:'de':1 }}</span>
599
+ * ```
600
+ */
601
+ declare class CoarPercentPipe implements PipeTransform {
602
+ private readonly localeDataStore;
603
+ private readonly localeService;
604
+ transform(value: number | null | undefined, locale?: string, decimals?: number): string;
605
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarPercentPipe, never>;
606
+ static ɵpipe: i0.ɵɵPipeDeclaration<CoarPercentPipe, "coarPercent", true>;
607
+ }
608
+
609
+ declare class CoarI18n {
610
+ private readonly provider;
611
+ private readonly locale;
612
+ /**
613
+ * Translate a key into a localized string.
614
+ *
615
+ * Overloads:
616
+ * - t(key)
617
+ * - t(key, fallback)
618
+ * - t(key, params)
619
+ * - t(key, fallback, params)
620
+ */
621
+ t(key: string): string;
622
+ t(key: string, fallback: string): string;
623
+ t(key: string, params: Record<string, unknown>): string;
624
+ t(key: string, fallback: string, params?: Record<string, unknown>): string;
625
+ /**
626
+ * Reactive variant that updates when the language changes.
627
+ * Uses CoarLocalizationService to detect language changes.
628
+ */
629
+ t$(key: string, params?: Record<string, unknown>, fallback?: string): Observable<string>;
630
+ /**
631
+ * Signal variant that updates when the language changes.
632
+ * Uses CoarLocalizationService to detect language changes.
633
+ */
634
+ tSignal(key: string, params?: Record<string, unknown>, fallback?: string): Signal<string>;
635
+ private callT;
636
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarI18n, never>;
637
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarI18n>;
638
+ }
639
+
640
+ declare class CoarI18nPipe implements PipeTransform, OnDestroy {
641
+ private readonly i18n;
642
+ private readonly cdr;
643
+ private readonly subject;
644
+ private lastKey?;
645
+ private lastParamsJson?;
646
+ private lastFallback?;
647
+ transform(key: string | null | undefined, paramsOrFallback?: Record<string, unknown> | string, maybeFallbackOrParams?: string | Record<string, unknown>): string;
648
+ ngOnDestroy(): void;
649
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarI18nPipe, never>;
650
+ static ɵpipe: i0.ɵɵPipeDeclaration<CoarI18nPipe, "coarI18n", true>;
651
+ }
652
+
653
+ /**
654
+ * Core i18n provider interface for translation engines.
655
+ *
656
+ * Providers (Transloco, custom, etc.) only need to implement this minimal interface.
657
+ * The CoarI18n service wraps this provider and adds convenience methods.
658
+ */
659
+ interface CoarI18nProvider {
660
+ /**
661
+ * Translate a key into a localized string.
662
+ *
663
+ * @param key i18n key, e.g. 'coar.datePicker.today'
664
+ * @param params optional interpolation parameters for placeholders like {name}
665
+ * @returns the translated and interpolated string
666
+ */
667
+ t(key: string, params?: Record<string, unknown>): string;
668
+ }
669
+ /**
670
+ * Injection token for the i18n provider implementation.
671
+ *
672
+ * Apps should provide this token with their chosen i18n backend (Transloco, custom, etc.).
673
+ */
674
+ declare const COAR_I18N_PROVIDER: InjectionToken<CoarI18nProvider>;
675
+
676
+ /**
677
+ * Context service providing i18n-related information to translation providers.
678
+ *
679
+ * This service acts as a coordination layer between the core locale system
680
+ * and translation provider implementations (e.g., Transloco, custom providers).
681
+ *
682
+ * Providers should inject this service to:
683
+ * - Get the current language
684
+ * - Subscribe to language changes
685
+ * - Access future i18n-related features (caching, preloading, etc.)
686
+ *
687
+ * This abstraction decouples providers from direct CoarLocalizationService dependency,
688
+ * making the system more maintainable and testable.
689
+ *
690
+ * @example
691
+ * ```typescript
692
+ * // In a translation provider implementation
693
+ * export function provideMyI18nBackend(): Provider {
694
+ * return {
695
+ * provide: COAR_I18N_PROVIDER,
696
+ * useFactory: (context: CoarI18nContext, backend: MyBackend) => {
697
+ * // Subscribe to language changes
698
+ * context.language$.subscribe(lang => {
699
+ * backend.switchLanguage(lang);
700
+ * });
701
+ *
702
+ * return {
703
+ * t(key: string): string {
704
+ * return backend.translate(key);
705
+ * }
706
+ * };
707
+ * },
708
+ * deps: [CoarI18nContext, MyBackend],
709
+ * };
710
+ * }
711
+ * ```
712
+ */
713
+ declare class CoarI18nContext {
714
+ private readonly localeService;
715
+ /**
716
+ * Observable that emits whenever the language changes.
717
+ *
718
+ * Translation providers should subscribe to this observable to stay
719
+ * synchronized with the current language state.
720
+ *
721
+ * @example
722
+ * ```typescript
723
+ * context.language$.subscribe(newLang => {
724
+ * console.log('Language changed to:', newLang);
725
+ * translationBackend.loadLanguage(newLang);
726
+ * });
727
+ * ```
728
+ */
729
+ readonly language$: Observable<string>;
730
+ /**
731
+ * Get the current language code.
732
+ *
733
+ * @returns The current language code (e.g., 'en', 'de', 'en-US')
734
+ *
735
+ * @example
736
+ * ```typescript
737
+ * const currentLang = context.getCurrentLanguage();
738
+ * console.log(currentLang); // 'en'
739
+ * ```
740
+ */
741
+ getCurrentLanguage(): string;
742
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarI18nContext, never>;
743
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarI18nContext>;
744
+ }
745
+
746
+ /**
747
+ * Core i18n service implementation.
748
+ *
749
+ * Integrates:
750
+ * - `CoarLocalizationService` - Language state management
751
+ * - `CoarTranslationStore` - Translation storage
752
+ * - `CoarTranslationLoader` - Translation loading
753
+ * - `CoarI18nContext` - Coordination layer
754
+ *
755
+ * ## Features
756
+ * - Automatic translation loading when language changes
757
+ * - Missing translation detection
758
+ * - Parameter interpolation
759
+ * - Reactive Signal-based API
760
+ *
761
+ * ## Usage
762
+ * Use `provideCoarI18n()` to configure - don't instantiate directly.
763
+ *
764
+ * @example
765
+ * ```ts
766
+ * export const appConfig: ApplicationConfig = {
767
+ * providers: [
768
+ * provideCoarLocalization({
769
+ * availableLanguages: ['en', 'de'],
770
+ * defaultLanguage: 'en',
771
+ * }),
772
+ * provideCoarI18n({
773
+ * loader: CoarHttpTranslationLoader,
774
+ * }),
775
+ * ],
776
+ * };
777
+ * ```
778
+ */
779
+ declare class CoarI18nService implements CoarI18nProvider {
780
+ private readonly locale;
781
+ private readonly store;
782
+ private readonly loader;
783
+ /**
784
+ * Signal containing all translations for the current language.
785
+ *
786
+ * Returns undefined if language not yet loaded.
787
+ */
788
+ private readonly currentTranslations;
789
+ constructor();
790
+ t(key: string, params?: Record<string, unknown>): string;
791
+ /**
792
+ * Loads translations for a specific language.
793
+ *
794
+ * Called automatically when language changes via effect.
795
+ * Can also be called manually to preload languages.
796
+ *
797
+ * @param language - Language code to load
798
+ * @returns Observable that completes when loading finishes
799
+ */
800
+ private loadLanguage;
801
+ /**
802
+ * Preloads translations for a specific language.
803
+ *
804
+ * Used by APP_INITIALIZER to prevent flash of untranslated content.
805
+ *
806
+ * @param language - Language code to preload
807
+ * @returns Promise that resolves when loading completes
808
+ */
809
+ preloadLanguage(language: string): Promise<void>;
810
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarI18nService, never>;
811
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarI18nService>;
812
+ }
813
+
814
+ /**
815
+ * Default/fallback implementation of CoarI18nProvider.
816
+ *
817
+ * This is a minimal passthrough implementation that returns the key unchanged,
818
+ * with placeholder interpolation applied if params are provided.
819
+ *
820
+ * Used when the app does not provide a custom i18n provider.
821
+ * The CoarI18n service wraps this and adds the tWithDefault method.
822
+ *
823
+ * @example
824
+ * ```ts
825
+ * import { provideCoarDefaultI18n } from '@cocoar/localization';
826
+ *
827
+ * export const appConfig: ApplicationConfig = {
828
+ * providers: [provideCoarDefaultI18n()],
829
+ * };
830
+ * ```
831
+ */
832
+ declare class CoarDefaultI18n implements CoarI18nProvider {
833
+ t(key: string, params?: Record<string, unknown>): string;
834
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarDefaultI18n, never>;
835
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarDefaultI18n>;
836
+ }
837
+
838
+ /**
839
+ * Interpolates {placeholders} in a template string using the given params.
840
+ *
841
+ * This is the official Cocoar placeholder syntax for i18n strings.
842
+ * Placeholders use curly braces: {name}, {count}, etc.
843
+ *
844
+ * @param template - The template string containing {placeholder} patterns
845
+ * @param params - Optional key-value pairs for interpolation
846
+ * @returns The interpolated string
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * coarInterpolate("Hello {name}, you have {count} items.", { name: 'Alice', count: 3 })
851
+ * // => "Hello Alice, you have 3 items."
852
+ * ```
853
+ */
854
+ declare function coarInterpolate(template: string, params?: Record<string, unknown>): string;
855
+
856
+ /**
857
+ * Determines if a translation result should be considered "missing".
858
+ *
859
+ * A translation is considered missing if it is:
860
+ * - null or undefined
861
+ * - an empty string (after trim)
862
+ * - exactly equal to the requested key
863
+ *
864
+ * This provides unified missing-key semantics across different i18n engines.
865
+ *
866
+ * @param key - The translation key that was requested
867
+ * @param result - The result returned by the i18n service
868
+ * @returns true if the translation is missing, false otherwise
869
+ *
870
+ * @example
871
+ * ```ts
872
+ * coarIsMissingTranslation('coar.button.save', null) // true
873
+ * coarIsMissingTranslation('coar.button.save', '') // true
874
+ * coarIsMissingTranslation('coar.button.save', 'coar.button.save') // true
875
+ * coarIsMissingTranslation('coar.button.save', 'Save') // false
876
+ * ```
877
+ */
878
+ declare function coarIsMissingTranslation(key: string, result: string | null | undefined): boolean;
879
+
880
+ /**
881
+ * Translation storage for a single language.
882
+ * Maps translation keys to their values.
883
+ */
884
+ type CoarTranslations = Record<string, string>;
885
+ /**
886
+ * Reactive store for translation data.
887
+ *
888
+ * Stores translations for multiple languages in memory and provides
889
+ * Signal-based API for reactive updates.
890
+ *
891
+ * ## Features
892
+ * - Signal-based reactive API
893
+ * - Language-scoped storage
894
+ * - Simple Map-based implementation
895
+ * - Zero dependencies beyond Angular core
896
+ *
897
+ * @example
898
+ * ```ts
899
+ * const store = inject(CoarTranslationStore);
900
+ *
901
+ * // Load translations
902
+ * store.setTranslations('en', { 'hello': 'Hello' });
903
+ * store.setTranslations('de', { 'hello': 'Hallo' });
904
+ *
905
+ * // Check if language is loaded
906
+ * if (store.hasLanguage('en')) {
907
+ * const translations = store.getTranslations('en');
908
+ * }
909
+ *
910
+ * // Get specific translation
911
+ * const value = store.getTranslation('en', 'hello'); // 'Hello'
912
+ *
913
+ * // Check for missing keys
914
+ * const missing = store.getTranslation('en', 'unknown'); // undefined
915
+ * ```
916
+ */
917
+ declare class CoarTranslationStore {
918
+ /**
919
+ * Internal storage: Map<language, Map<key, translation>>
920
+ *
921
+ * Why nested Maps instead of Record:
922
+ * - Faster lookups for large translation sets
923
+ * - No prototype chain pollution
924
+ * - Clear separation between languages
925
+ */
926
+ private readonly storage;
927
+ /**
928
+ * Set of all loaded languages.
929
+ * Used for quick existence checks.
930
+ */
931
+ readonly loadedLanguages: Signal<Set<string>>;
932
+ /**
933
+ * Stores all translations for a specific language.
934
+ *
935
+ * Replaces any existing translations for that language.
936
+ *
937
+ * @param language - Language code (e.g., 'en', 'de')
938
+ * @param translations - Translation key-value pairs
939
+ *
940
+ * @example
941
+ * ```ts
942
+ * // HTTP: Load entire language at once
943
+ * store.setTranslations('en', { 'hello': 'Hello', 'goodbye': 'Goodbye' });
944
+ * ```
945
+ */
946
+ setTranslations(language: string, translations: CoarTranslations): void;
947
+ /**
948
+ * Updates a single translation key for a specific language.
949
+ *
950
+ * Creates the language if it doesn't exist.
951
+ * Useful for real-time updates via SignalR.
952
+ *
953
+ * @param language - Language code
954
+ * @param key - Translation key
955
+ * @param value - Translation value
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * // SignalR: Update single key in real-time
960
+ * signalR.on('TranslationUpdated', ({ lang, key, value }) => {
961
+ * store.setTranslation(lang, key, value);
962
+ * });
963
+ * ```
964
+ */
965
+ setTranslation(language: string, key: string, value: string): void;
966
+ /**
967
+ * Merges partial translations into an existing language.
968
+ *
969
+ * Only updates/adds the provided keys, keeps existing keys intact.
970
+ * Creates the language if it doesn't exist.
971
+ *
972
+ * @param language - Language code
973
+ * @param partialTranslations - Partial translation key-value pairs to merge
974
+ *
975
+ * @example
976
+ * ```ts
977
+ * // Existing: { 'hello': 'Hello', 'goodbye': 'Goodbye' }
978
+ * store.updateTranslations('en', { 'hello': 'Hi' });
979
+ * // Result: { 'hello': 'Hi', 'goodbye': 'Goodbye' }
980
+ * ```
981
+ *
982
+ * @example
983
+ * ```ts
984
+ * // SignalR: Batch update multiple keys
985
+ * signalR.on('TranslationsBatchUpdated', ({ lang, updates }) => {
986
+ * store.updateTranslations(lang, updates);
987
+ * });
988
+ * ```
989
+ */
990
+ updateTranslations(language: string, partialTranslations: CoarTranslations): void;
991
+ /**
992
+ * Checks if translations for a language are loaded.
993
+ *
994
+ * @param language - Language code to check
995
+ * @returns True if language is loaded
996
+ */
997
+ hasLanguage(language: string): boolean;
998
+ /**
999
+ * Gets all translations for a specific language.
1000
+ *
1001
+ * @param language - Language code
1002
+ * @returns Translation map, or undefined if language not loaded
1003
+ */
1004
+ getTranslations(language: string): Map<string, string> | undefined;
1005
+ /**
1006
+ * Gets a specific translation value.
1007
+ *
1008
+ * @param language - Language code
1009
+ * @param key - Translation key
1010
+ * @returns Translation value, or undefined if not found
1011
+ */
1012
+ getTranslation(language: string, key: string): string | undefined;
1013
+ /**
1014
+ * Clears all translations from the store.
1015
+ *
1016
+ * Used primarily for testing.
1017
+ */
1018
+ clear(): void;
1019
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarTranslationStore, never>;
1020
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarTranslationStore>;
1021
+ }
1022
+
1023
+ /**
1024
+ * Abstract loader for translation data.
1025
+ *
1026
+ * Implement this interface to load translations from any source:
1027
+ * - HTTP endpoints
1028
+ * - SignalR real-time updates
1029
+ * - Static imports
1030
+ * - IndexedDB
1031
+ * - etc.
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * @Injectable()
1036
+ * export class MyCustomLoader implements CoarTranslationLoader {
1037
+ * loadTranslations(language: string): Observable<CoarTranslations> {
1038
+ * // Load from your custom source
1039
+ * return this.myService.getTranslations(language);
1040
+ * }
1041
+ * }
1042
+ * ```
1043
+ */
1044
+ declare abstract class CoarTranslationLoader {
1045
+ /**
1046
+ * Loads translation data for a specific language.
1047
+ *
1048
+ * @param language - Language code to load (e.g., 'en', 'de')
1049
+ * @returns Observable that emits translation key-value pairs
1050
+ */
1051
+ abstract loadTranslations(language: string): Observable<CoarTranslations>;
1052
+ }
1053
+ /**
1054
+ * HTTP-based translation loader.
1055
+ *
1056
+ * Loads translation JSON files from a configurable base path.
1057
+ *
1058
+ * ## Default behavior
1059
+ * - Base path: `/i18n/`
1060
+ * - File pattern: `{language}.json`
1061
+ * - Example: `/i18n/en.json`, `/i18n/de.json`
1062
+ *
1063
+ * ## Custom path
1064
+ * ```ts
1065
+ * const loader = new CoarHttpTranslationLoader();
1066
+ * loader.basePath = '/assets/translations/';
1067
+ * // Loads: /assets/translations/en.json
1068
+ * ```
1069
+ *
1070
+ * ## Expected JSON format
1071
+ * ```json
1072
+ * {
1073
+ * "hello": "Hello",
1074
+ * "goodbye": "Goodbye",
1075
+ * "welcome": "Welcome, {{name}}!"
1076
+ * }
1077
+ * ```
1078
+ */
1079
+ declare class CoarHttpTranslationLoader implements CoarTranslationLoader {
1080
+ private readonly http;
1081
+ /** Base path for translation files */
1082
+ basePath: string;
1083
+ loadTranslations(language: string): Observable<CoarTranslations>;
1084
+ static ɵfac: i0.ɵɵFactoryDeclaration<CoarHttpTranslationLoader, never>;
1085
+ static ɵprov: i0.ɵɵInjectableDeclaration<CoarHttpTranslationLoader>;
1086
+ }
1087
+
1088
+ /**
1089
+ * Configuration for Cocoar i18n system.
1090
+ */
1091
+ interface CoarI18nConfig {
1092
+ /**
1093
+ * Translation loader to use.
1094
+ *
1095
+ * Provide a class that implements `CoarTranslationLoader`.
1096
+ *
1097
+ * ## Built-in loaders
1098
+ * - `CoarHttpTranslationLoader` - Load from HTTP (default)
1099
+ *
1100
+ * ## Custom loader
1101
+ * ```ts
1102
+ * @Injectable()
1103
+ * class MyLoader implements CoarTranslationLoader {
1104
+ * loadTranslations(lang: string): Observable<CoarTranslations> {
1105
+ * // Your implementation
1106
+ * }
1107
+ * }
1108
+ *
1109
+ * provideCoarI18n({ loader: MyLoader })
1110
+ * ```
1111
+ */
1112
+ loader?: Type<CoarTranslationLoader>;
1113
+ /**
1114
+ * Base path for HTTP loader.
1115
+ *
1116
+ * Only used when `loader` is `CoarHttpTranslationLoader`.
1117
+ *
1118
+ * @default '/i18n/'
1119
+ */
1120
+ basePath?: string;
1121
+ }
1122
+ /**
1123
+ * Provides complete Cocoar i18n system.
1124
+ *
1125
+ * Sets up translation loading, storage, and automatic language synchronization.
1126
+ *
1127
+ * ## Features
1128
+ * - Automatic translation loading when language changes
1129
+ * - Preloads initial language (prevents flash of untranslated content)
1130
+ * - Signal-based reactive updates
1131
+ * - Customizable loader (HTTP, SignalR, static, etc.)
1132
+ *
1133
+ * ## Basic usage
1134
+ * ```ts
1135
+ * import { provideCoarLocalization } from '@cocoar/localization';
1136
+ * import { provideCoarI18n } from '@cocoar/localization';
1137
+ *
1138
+ * export const appConfig: ApplicationConfig = {
1139
+ * providers: [
1140
+ * provideHttpClient(),
1141
+ * provideCoarLocalization({
1142
+ * availableLanguages: ['en', 'de', 'fr'],
1143
+ * defaultLanguage: 'en',
1144
+ * }),
1145
+ * provideCoarI18n(),
1146
+ * ],
1147
+ * };
1148
+ * ```
1149
+ *
1150
+ * ## Custom HTTP path
1151
+ * ```ts
1152
+ * provideCoarI18n({
1153
+ * basePath: '/assets/translations/',
1154
+ * })
1155
+ * ```
1156
+ *
1157
+ * ## Custom loader (e.g., SignalR)
1158
+ * ```ts
1159
+ * @Injectable()
1160
+ * class SignalRTranslationLoader implements CoarTranslationLoader {
1161
+ * loadTranslations(lang: string): Observable<CoarTranslations> {
1162
+ * return this.signalR.getTranslations(lang);
1163
+ * }
1164
+ * }
1165
+ *
1166
+ * provideCoarI18n({
1167
+ * loader: SignalRTranslationLoader,
1168
+ * })
1169
+ * ```
1170
+ *
1171
+ * @param config - Optional configuration
1172
+ * @returns Environment providers for the i18n system
1173
+ */
1174
+ declare function provideCoarI18n(config?: CoarI18nConfig): EnvironmentProviders;
1175
+
1176
+ /**
1177
+ * Provides the default i18n provider.
1178
+ *
1179
+ * This minimal provider returns keys unchanged with interpolation applied.
1180
+ * Use this as a fallback when no translation engine is configured.
1181
+ *
1182
+ * @example
1183
+ * ```ts
1184
+ * import { provideCoarDefaultI18n } from '@cocoar/localization';
1185
+ *
1186
+ * export const appConfig: ApplicationConfig = {
1187
+ * providers: [provideCoarDefaultI18n()],
1188
+ * };
1189
+ * ```
1190
+ */
1191
+ declare function provideCoarDefaultI18n(): Provider;
1192
+
1193
+ export { COAR_I18N_PROVIDER, COAR_LOCALIZATION_CONFIG, COAR_LOCALIZATION_DATA_LOADERS, CoarCurrencyPipe, CoarDatePipe, CoarDefaultI18n, CoarHttpLocaleDataLoader, CoarHttpTranslationLoader, CoarI18n, CoarI18nContext, CoarI18nPipe, CoarI18nService, CoarIntlLocaleDataLoader, CoarLocalizationDataLoader, CoarLocalizationDataStore, CoarLocalizationService, CoarNumberPipe, CoarPercentPipe, CoarTranslationLoader, CoarTranslationStore, coarInterpolate, coarIsMissingTranslation, mergeLocalizationData, provideCoarDefaultI18n, provideCoarHttpLocalizationSource, provideCoarI18n, provideCoarIntlLocalizationSource, provideCoarLocalization };
1194
+ export type { CoarCurrencyFormatData, CoarDateFormatData, CoarHttpLocaleSourceConfig, CoarI18nConfig, CoarI18nProvider, CoarLocalizationConfig, CoarLocalizationData, CoarNumberFormatData, CoarPercentFormatData, CoarTranslations };