@cocoar/localization 0.1.0-beta.126 → 0.1.0-beta.151

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.
@@ -1,9 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, Injectable, InjectionToken, inject, computed, DestroyRef, makeEnvironmentProviders, APP_INITIALIZER, Pipe, ChangeDetectorRef } from '@angular/core';
3
- import { of, tap, switchMap, catchError, BehaviorSubject, lastValueFrom, map, distinctUntilChanged } from 'rxjs';
2
+ import { signal, computed, Injectable, InjectionToken, inject, DestroyRef, makeEnvironmentProviders, APP_INITIALIZER, Pipe, ChangeDetectorRef } from '@angular/core';
3
+ import { of, switchMap, tap, catchError, forkJoin, combineLatest, BehaviorSubject, Subject, lastValueFrom, map as map$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
4
4
  import { ReadonlyState, BehaviorSubjectProxy } from '@cocoar/ts-utils';
5
5
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
6
6
  import { HttpClient } from '@angular/common/http';
7
+ import { map, distinctUntilChanged } from 'rxjs/operators';
7
8
  import { Temporal } from '@js-temporal/polyfill';
8
9
 
9
10
  /**
@@ -12,6 +13,14 @@ import { Temporal } from '@js-temporal/polyfill';
12
13
  */
13
14
  class CoarLocalizationDataStore {
14
15
  store = signal(new Map(), ...(ngDevMode ? [{ debugName: "store" }] : []));
16
+ /**
17
+ * Reactive signal that changes whenever any locale data is updated.
18
+ * Use this in computed() to ensure reactivity to data loading.
19
+ */
20
+ dataVersion = computed(() => {
21
+ // Reading store() creates the signal dependency
22
+ return this.store().size;
23
+ }, ...(ngDevMode ? [{ debugName: "dataVersion" }] : []));
15
24
  /**
16
25
  * Set locale data for a specific locale.
17
26
  * @param locale Locale code (e.g., 'en', 'de', 'en-US')
@@ -370,6 +379,12 @@ function coarInterpolate(template, params) {
370
379
  });
371
380
  }
372
381
 
382
+ /**
383
+ * Injection token for translation loaders (multi-provider).
384
+ * Loaders are executed in order and results are deep-merged.
385
+ * Intl loader is always first (provides common defaults from browser Intl API).
386
+ */
387
+ const COAR_TRANSLATION_LOADERS = new InjectionToken('COAR_TRANSLATION_LOADERS');
373
388
  /**
374
389
  * Abstract loader for translation data.
375
390
  *
@@ -697,11 +712,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
697
712
  class CoarI18nService {
698
713
  locale = inject(CoarLocalizationService);
699
714
  store = inject(CoarTranslationStore);
700
- loader = inject(CoarTranslationLoader, { optional: true });
715
+ loaders = inject(COAR_TRANSLATION_LOADERS, { optional: true }) ?? [];
701
716
  destroyRef = inject(DestroyRef);
702
717
  constructor() {
703
- // Auto-load translations when language changes.
704
- // Observable-first so there is a single canonical dataflow.
718
+ // Coordination: Wait for pending language changes, load translations, then resolve
719
+ // This ensures translations are ready BEFORE the language state changes
720
+ this.locale.pendingLanguageChange$
721
+ .pipe(takeUntilDestroyed(this.destroyRef), switchMap(({ language, resolve }) => {
722
+ // Load translations if not already loaded
723
+ if (this.store.hasLanguage(language)) {
724
+ resolve(); // Already loaded, resolve immediately
725
+ return of(void 0);
726
+ }
727
+ // Load and then resolve
728
+ return this.loadLanguage(language).pipe(tap(() => resolve()) // Signal that translations are ready
729
+ );
730
+ }))
731
+ .subscribe();
732
+ // Fallback: Auto-load translations when language changes without coordination
733
+ // (for backwards compatibility if someone directly mutates the subject)
705
734
  this.locale.languageState.value$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((lang) => {
706
735
  if (this.store.hasLanguage(lang)) {
707
736
  return;
@@ -729,29 +758,56 @@ class CoarI18nService {
729
758
  return coarInterpolate(value, params);
730
759
  }
731
760
  /**
732
- * Loads translations for a specific language.
761
+ * Loads translations from all sources and deep-merges them.
733
762
  *
734
- * Called automatically when language changes via effect.
735
- * Can also be called manually to preload languages.
763
+ * Sources are executed in order:
764
+ * 1. Intl (always first, provides common translations from browser APIs)
765
+ * 2. HTTP (if configured, merges application-specific overrides)
766
+ * 3. Custom sources (if provided, can add dynamic updates)
767
+ *
768
+ * Later sources override earlier sources at the key level.
736
769
  *
737
770
  * @param language - Language code to load
738
771
  * @returns Observable that completes when loading finishes
739
772
  */
740
773
  loadLanguage(language) {
741
- // No loader configured - mark as loaded with empty translations
742
- if (!this.loader) {
774
+ // No loaders configured - mark as loaded with empty translations
775
+ if (this.loaders.length === 0) {
743
776
  this.store.setTranslations(language, {});
744
777
  return of(void 0);
745
778
  }
746
- return this.loader.loadTranslations(language).pipe(tap((translations) => {
747
- this.store.setTranslations(language, translations);
779
+ // Load from all sources in parallel
780
+ const loadObservables = this.loaders.map((loader) => loader.loadTranslations(language).pipe(catchError((err) => {
781
+ console.warn(`[CoarI18n] Translation loader failed for '${language}':`, err);
782
+ return of(null); // Return null for failed sources
783
+ })));
784
+ return forkJoin(loadObservables).pipe(tap((results) => {
785
+ // Deep merge all sources (nulls are ignored)
786
+ const merged = this.mergeTranslations(results.filter((r) => r !== null));
787
+ this.store.setTranslations(language, merged);
748
788
  }), switchMap(() => of(void 0)), catchError((error) => {
749
- console.error(`Failed to load translations for language: ${language}`, error);
789
+ console.error(`[CoarI18n] Failed to load translations for '${language}':`, error);
750
790
  // Store empty translations to prevent repeated load attempts
751
791
  this.store.setTranslations(language, {});
752
792
  return of(void 0);
753
793
  }));
754
794
  }
795
+ /**
796
+ * Merges multiple translation sources.
797
+ * Later sources override earlier sources at the key level.
798
+ */
799
+ mergeTranslations(sources) {
800
+ const result = {};
801
+ for (const source of sources) {
802
+ if (!source)
803
+ continue;
804
+ // Merge all keys from this source
805
+ for (const [key, value] of Object.entries(source)) {
806
+ result[key] = value;
807
+ }
808
+ }
809
+ return result;
810
+ }
755
811
  /**
756
812
  * Preloads translations for a specific language.
757
813
  *
@@ -776,6 +832,212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
776
832
  type: Injectable
777
833
  }], ctorParameters: () => [] });
778
834
 
835
+ /**
836
+ * Translation loader that generates common translations from browser's Intl API.
837
+ *
838
+ * This loader provides default translations for common UI elements that can be
839
+ * automatically localized using browser APIs, without requiring JSON files.
840
+ *
841
+ * Translations generated:
842
+ * - Relative time labels (today, yesterday, tomorrow)
843
+ * - Month names (full and abbreviated)
844
+ * - Weekday names (full and abbreviated)
845
+ * - Common date/time units
846
+ *
847
+ * These translations can be overridden by registering additional loaders
848
+ * (e.g., HTTP loader) that provide custom values.
849
+ *
850
+ * @example
851
+ * ```ts
852
+ * // Automatic registration via provideCoarLocalization()
853
+ * provideCoarLocalization({ defaultLanguage: 'en' })
854
+ *
855
+ * // Intl loader provides: { 'common.today': 'today' }
856
+ * // HTTP loader can override: { 'common.today': 'Now' }
857
+ * ```
858
+ */
859
+ class CoarIntlTranslationLoader extends CoarTranslationLoader {
860
+ loadTranslations(locale) {
861
+ return of(this.generateFromIntl(locale));
862
+ }
863
+ /**
864
+ * Generate translations from browser Intl API.
865
+ */
866
+ generateFromIntl(locale) {
867
+ const translations = {};
868
+ // Relative time labels (for date pickers, calendars, etc.)
869
+ try {
870
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
871
+ translations['common.today'] = rtf.format(0, 'day');
872
+ translations['common.yesterday'] = rtf.format(-1, 'day');
873
+ translations['common.tomorrow'] = rtf.format(1, 'day');
874
+ }
875
+ catch {
876
+ // Fallback for older browsers without RelativeTimeFormat
877
+ translations['common.today'] = 'today';
878
+ translations['common.yesterday'] = 'yesterday';
879
+ translations['common.tomorrow'] = 'tomorrow';
880
+ }
881
+ // Month names (full)
882
+ const monthFormatter = new Intl.DateTimeFormat(locale, { month: 'long' });
883
+ for (let m = 0; m < 12; m++) {
884
+ const date = new Date(2024, m, 1);
885
+ const monthName = monthFormatter.format(date);
886
+ translations[`common.month.${m + 1}`] = monthName;
887
+ }
888
+ // Month names (abbreviated)
889
+ const monthFormatterShort = new Intl.DateTimeFormat(locale, { month: 'short' });
890
+ for (let m = 0; m < 12; m++) {
891
+ const date = new Date(2024, m, 1);
892
+ const monthName = monthFormatterShort.format(date);
893
+ translations[`common.month.short.${m + 1}`] = monthName;
894
+ }
895
+ // Weekday names (full) - Monday=1, Sunday=7
896
+ const dayFormatter = new Intl.DateTimeFormat(locale, { weekday: 'long' });
897
+ for (let d = 0; d < 7; d++) {
898
+ // Jan 1, 2024 is Monday
899
+ const date = new Date(2024, 0, 1 + d);
900
+ const dayName = dayFormatter.format(date);
901
+ translations[`common.weekday.${d + 1}`] = dayName;
902
+ }
903
+ // Weekday names (abbreviated)
904
+ const dayFormatterShort = new Intl.DateTimeFormat(locale, { weekday: 'short' });
905
+ for (let d = 0; d < 7; d++) {
906
+ const date = new Date(2024, 0, 1 + d);
907
+ const dayName = dayFormatterShort.format(date);
908
+ translations[`common.weekday.short.${d + 1}`] = dayName;
909
+ }
910
+ return translations;
911
+ }
912
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarIntlTranslationLoader, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
913
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarIntlTranslationLoader });
914
+ }
915
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarIntlTranslationLoader, decorators: [{
916
+ type: Injectable
917
+ }] });
918
+
919
+ /**
920
+ * Built-in timezone provider using browser's Intl API.
921
+ *
922
+ * Always present as guaranteed baseline fallback.
923
+ * Returns the user's operating system timezone.
924
+ *
925
+ * Note: This is a one-time snapshot. If user changes OS timezone
926
+ * while app is running, this will not update (browser limitation).
927
+ *
928
+ * @internal
929
+ */
930
+ class BrowserTimeZoneProvider {
931
+ timeZone$;
932
+ constructor() {
933
+ // Detect browser timezone using Intl API
934
+ // This is a one-time snapshot (browser doesn't provide reactive updates)
935
+ const browserTimeZone = this.detectBrowserTimeZone();
936
+ this.timeZone$ = of(browserTimeZone);
937
+ }
938
+ detectBrowserTimeZone() {
939
+ try {
940
+ // Use Intl API to get user's timezone
941
+ // Example return values: "America/New_York", "Europe/Paris", "Asia/Tokyo"
942
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
943
+ // Fallback to UTC if browser returns undefined (shouldn't happen in modern browsers)
944
+ return timeZone || 'UTC';
945
+ }
946
+ catch {
947
+ // Fallback to UTC if Intl API fails
948
+ return 'UTC';
949
+ }
950
+ }
951
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BrowserTimeZoneProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
952
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BrowserTimeZoneProvider });
953
+ }
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BrowserTimeZoneProvider, decorators: [{
955
+ type: Injectable
956
+ }], ctorParameters: () => [] });
957
+
958
+ /**
959
+ * Injection token for custom timezone providers (multi-provider).
960
+ *
961
+ * Providers are resolved in array order (first to last).
962
+ * First non-null value wins.
963
+ *
964
+ * Browser provider is always present as guaranteed baseline (automatically added).
965
+ *
966
+ * @internal
967
+ */
968
+ const COAR_TIMEZONE_PROVIDERS = new InjectionToken('COAR_TIMEZONE_PROVIDERS');
969
+
970
+ /**
971
+ * Service for timezone resolution with pluggable provider hierarchy.
972
+ *
973
+ * Resolution order:
974
+ * 1. Custom providers (from config, in array order)
975
+ * 2. Browser provider (Intl API, always present as guaranteed baseline)
976
+ * 3. UTC safety net (if all providers return null)
977
+ *
978
+ * First non-null value wins. Changes propagate reactively.
979
+ *
980
+ * @example
981
+ * ```ts
982
+ * // In component
983
+ * export class MyComponent {
984
+ * private timeZoneService = inject(CoarTimeZoneService);
985
+ *
986
+ * // Reactive signal (updates when timezone changes)
987
+ * currentTimeZone = this.timeZoneService.currentTimeZone;
988
+ *
989
+ * // Observable stream
990
+ * timeZone$ = this.timeZoneService.timeZone$;
991
+ * }
992
+ * ```
993
+ */
994
+ class CoarTimeZoneService {
995
+ browserProvider = inject(BrowserTimeZoneProvider);
996
+ customProviders = inject(COAR_TIMEZONE_PROVIDERS, { optional: true }) ?? [];
997
+ /**
998
+ * Observable stream of the current timezone (IANA identifier).
999
+ *
1000
+ * Emits when any provider in the hierarchy changes.
1001
+ * Uses `distinctUntilChanged()` to prevent unnecessary emissions.
1002
+ *
1003
+ * Resolution order: Custom providers → Browser → UTC
1004
+ */
1005
+ timeZone$;
1006
+ /**
1007
+ * Signal of the current timezone (IANA identifier).
1008
+ *
1009
+ * Automatically updates when timezone changes.
1010
+ * Use this in templates or reactive contexts.
1011
+ */
1012
+ currentTimeZone;
1013
+ constructor() {
1014
+ // Build provider chain: Custom providers → Browser (always last)
1015
+ const allProviders = [
1016
+ ...this.customProviders,
1017
+ this.browserProvider, // Browser is guaranteed baseline, always present
1018
+ ];
1019
+ // Combine all provider streams
1020
+ const providerStreams = allProviders.map((provider) => provider.timeZone$);
1021
+ this.timeZone$ = combineLatest(providerStreams).pipe(
1022
+ // Find first non-null value in priority order
1023
+ map((timeZones) => {
1024
+ const resolved = timeZones.find((tz) => tz !== null);
1025
+ // UTC as safety net if all providers return null (shouldn't happen)
1026
+ return resolved ?? 'UTC';
1027
+ }),
1028
+ // Prevent unnecessary emissions when value doesn't actually change
1029
+ distinctUntilChanged());
1030
+ // Initialize signal after timeZone$ is set up
1031
+ this.currentTimeZone = toSignal(this.timeZone$, { requireSync: true });
1032
+ }
1033
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarTimeZoneService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1034
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarTimeZoneService, providedIn: 'root' });
1035
+ }
1036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CoarTimeZoneService, decorators: [{
1037
+ type: Injectable,
1038
+ args: [{ providedIn: 'root' }]
1039
+ }], ctorParameters: () => [] });
1040
+
779
1041
  /**
780
1042
  * Injection token for locale configuration.
781
1043
  */
@@ -824,6 +1086,16 @@ function provideCoarLocalization(config) {
824
1086
  },
825
1087
  CoarLocalizationService,
826
1088
  CoarLocalizationDataStore,
1089
+ // TimeZone: Provide the timezone service
1090
+ CoarTimeZoneService,
1091
+ // TimeZone: Browser provider (always present as guaranteed baseline)
1092
+ BrowserTimeZoneProvider,
1093
+ // TimeZone: Register custom providers from config (if any)
1094
+ ...(config.timeZoneProviders?.map((provider) => ({
1095
+ provide: COAR_TIMEZONE_PROVIDERS,
1096
+ multi: true,
1097
+ useValue: provider,
1098
+ })) ?? []),
827
1099
  // L10n: Auto-include Intl source as first loader (provides complete defaults)
828
1100
  {
829
1101
  provide: COAR_LOCALIZATION_DATA_LOADERS,
@@ -835,6 +1107,12 @@ function provideCoarLocalization(config) {
835
1107
  provide: COAR_I18N_PROVIDER,
836
1108
  useClass: CoarI18nService,
837
1109
  },
1110
+ // i18n: Auto-include Intl source as first translation loader (provides common defaults)
1111
+ {
1112
+ provide: COAR_TRANSLATION_LOADERS,
1113
+ multi: true,
1114
+ useClass: CoarIntlTranslationLoader,
1115
+ },
838
1116
  // APP_INITIALIZER: Preload default language (L10n + i18n if loader registered)
839
1117
  {
840
1118
  provide: APP_INITIALIZER,
@@ -955,6 +1233,12 @@ class CoarLocalizationService {
955
1233
  localeDataLoaders = inject(COAR_LOCALIZATION_DATA_LOADERS, { optional: true }) ?? [];
956
1234
  defaultLanguage = this.config?.defaultLanguage ?? 'en';
957
1235
  languageSubject = new BehaviorSubject(this.defaultLanguage);
1236
+ /**
1237
+ * Subject for coordinating language changes with i18n service.
1238
+ * The i18n service subscribes to this to load translations BEFORE the language change is emitted.
1239
+ * @internal
1240
+ */
1241
+ pendingLanguageChange$ = new Subject();
958
1242
  /**
959
1243
  * Canonical language state.
960
1244
  *
@@ -965,9 +1249,13 @@ class CoarLocalizationService {
965
1249
  constructor() {
966
1250
  // Expose to window for debugging
967
1251
  if (typeof window !== 'undefined') {
968
- window.__coarLocalizationStore =
969
- this.localeDataStore;
1252
+ window.__coarLocalizationStore = this.localeDataStore;
970
1253
  }
1254
+ // Load locale data for the default language on initialization
1255
+ // This ensures firstDayOfWeek and other formatting data is available immediately
1256
+ this.loadAndmergeLocalizationData(this.defaultLanguage).catch((error) => {
1257
+ console.warn(`[CoarLocalizationService] Failed to load initial locale data for '${this.defaultLanguage}':`, error);
1258
+ });
971
1259
  }
972
1260
  /**
973
1261
  * Set the current language.
@@ -990,6 +1278,10 @@ class CoarLocalizationService {
990
1278
  */
991
1279
  async setLanguage(language) {
992
1280
  const current = this.languageState.value;
1281
+ // Skip if language hasn't changed
1282
+ if (current === language) {
1283
+ return;
1284
+ }
993
1285
  // Load locale data if not already loaded (cached)
994
1286
  if (!this.localeDataStore.hasLocaleData(language)) {
995
1287
  try {
@@ -1001,10 +1293,16 @@ class CoarLocalizationService {
1001
1293
  // Formatting pipes will use fallback values
1002
1294
  }
1003
1295
  }
1004
- // Only notify if language actually changed
1005
- if (current !== language) {
1006
- this.languageSubject.next(language);
1007
- }
1296
+ // Wait for i18n service to load translations before emitting language change
1297
+ // Use a timeout in case i18n service is not injected (e.g., in tests)
1298
+ await Promise.race([
1299
+ new Promise((resolve) => {
1300
+ this.pendingLanguageChange$.next({ language, resolve });
1301
+ }),
1302
+ new Promise((resolve) => setTimeout(resolve, 100)), // Fallback timeout
1303
+ ]);
1304
+ // Now emit the language change (UI will react with translations ready)
1305
+ this.languageSubject.next(language);
1008
1306
  }
1009
1307
  /**
1010
1308
  * Load locale data from all sources and deep-merge them.
@@ -1425,7 +1723,7 @@ class CoarI18n {
1425
1723
  */
1426
1724
  t$(key, params, fallback) {
1427
1725
  // Re-evaluate on every language change from CoarLocalizationService
1428
- return this.locale.languageState.value$.pipe(map(() => this.callT(key, params, fallback)), distinctUntilChanged());
1726
+ return this.locale.languageState.value$.pipe(map$1(() => this.callT(key, params, fallback)), distinctUntilChanged$1());
1429
1727
  }
1430
1728
  /**
1431
1729
  * Signal variant that updates when the language changes.
@@ -1641,7 +1939,8 @@ function provideCoarI18nHttpSource(config) {
1641
1939
  const headers = config?.headers;
1642
1940
  return makeEnvironmentProviders([
1643
1941
  {
1644
- provide: CoarTranslationLoader,
1942
+ provide: COAR_TRANSLATION_LOADERS,
1943
+ multi: true,
1645
1944
  useFactory: () => {
1646
1945
  const loader = new CoarHttpTranslationLoader();
1647
1946
  loader.urlFn = urlFn;
@@ -1658,5 +1957,5 @@ function provideCoarI18nHttpSource(config) {
1658
1957
  * Generated bundle index. Do not edit.
1659
1958
  */
1660
1959
 
1661
- export { COAR_I18N_PROVIDER, COAR_LOCALIZATION_CONFIG, COAR_LOCALIZATION_DATA_LOADERS, CoarCurrencyPipe, CoarDatePipe, CoarHttpLocaleDataLoader, CoarHttpTranslationLoader, CoarI18n, CoarI18nContext, CoarI18nPipe, CoarI18nService, CoarIntlLocaleDataLoader, CoarLocalizationDataLoader, CoarLocalizationDataStore, CoarLocalizationService, CoarNumberPipe, CoarPercentPipe, CoarTranslationLoader, CoarTranslationStore, coarInterpolate, coarIsMissingTranslation, mergeLocalizationData, provideCoarI18nHttpSource, provideCoarL10nHttpSource, provideCoarLocalization };
1960
+ export { COAR_I18N_PROVIDER, COAR_LOCALIZATION_CONFIG, COAR_LOCALIZATION_DATA_LOADERS, COAR_TRANSLATION_LOADERS, CoarCurrencyPipe, CoarDatePipe, CoarHttpLocaleDataLoader, CoarHttpTranslationLoader, CoarI18n, CoarI18nContext, CoarI18nPipe, CoarI18nService, CoarIntlLocaleDataLoader, CoarIntlTranslationLoader, CoarLocalizationDataLoader, CoarLocalizationDataStore, CoarLocalizationService, CoarNumberPipe, CoarPercentPipe, CoarTimeZoneService, CoarTranslationLoader, CoarTranslationStore, coarInterpolate, coarIsMissingTranslation, mergeLocalizationData, provideCoarI18nHttpSource, provideCoarL10nHttpSource, provideCoarLocalization };
1662
1961
  //# sourceMappingURL=cocoar-localization.mjs.map