@o3r/transloco 0.0.0 → 14.5.0-prerelease.1

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.
Files changed (122) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +64 -0
  3. package/builders/helpers/localization-generator.d.ts +126 -0
  4. package/builders/helpers/localization-generator.d.ts.map +1 -0
  5. package/builders/helpers/localization-generator.js +303 -0
  6. package/builders/helpers/localization-generator.js.map +1 -0
  7. package/builders/i18n/index.d.ts +7 -0
  8. package/builders/i18n/index.d.ts.map +1 -0
  9. package/builders/i18n/index.js +39 -0
  10. package/builders/i18n/index.js.map +1 -0
  11. package/builders/i18n/schema.d.ts +30 -0
  12. package/builders/i18n/schema.d.ts.map +1 -0
  13. package/builders/i18n/schema.js +3 -0
  14. package/builders/i18n/schema.js.map +1 -0
  15. package/builders/i18n/schema.json +35 -0
  16. package/builders/localization/index.d.ts +23 -0
  17. package/builders/localization/index.d.ts.map +1 -0
  18. package/builders/localization/index.js +416 -0
  19. package/builders/localization/index.js.map +1 -0
  20. package/builders/localization/schema.d.ts +29 -0
  21. package/builders/localization/schema.d.ts.map +1 -0
  22. package/builders/localization/schema.js +3 -0
  23. package/builders/localization/schema.js.map +1 -0
  24. package/builders/localization/schema.json +78 -0
  25. package/builders/localization-extractor/index.d.ts +8 -0
  26. package/builders/localization-extractor/index.d.ts.map +1 -0
  27. package/builders/localization-extractor/index.js +181 -0
  28. package/builders/localization-extractor/index.js.map +1 -0
  29. package/builders/localization-extractor/schema.d.ts +25 -0
  30. package/builders/localization-extractor/schema.d.ts.map +1 -0
  31. package/builders/localization-extractor/schema.js +3 -0
  32. package/builders/localization-extractor/schema.js.map +1 -0
  33. package/builders/localization-extractor/schema.json +62 -0
  34. package/builders/localization-extractor/validations.d.ts +17 -0
  35. package/builders/localization-extractor/validations.d.ts.map +1 -0
  36. package/builders/localization-extractor/validations.js +54 -0
  37. package/builders/localization-extractor/validations.js.map +1 -0
  38. package/builders/metadata-check/helpers/index.d.ts +2 -0
  39. package/builders/metadata-check/helpers/index.d.ts.map +1 -0
  40. package/builders/metadata-check/helpers/index.js +5 -0
  41. package/builders/metadata-check/helpers/index.js.map +1 -0
  42. package/builders/metadata-check/helpers/localization-metadata-comparison-helper.d.ts +14 -0
  43. package/builders/metadata-check/helpers/localization-metadata-comparison-helper.d.ts.map +1 -0
  44. package/builders/metadata-check/helpers/localization-metadata-comparison-helper.js +34 -0
  45. package/builders/metadata-check/helpers/localization-metadata-comparison-helper.js.map +1 -0
  46. package/builders/metadata-check/index.d.ts +7 -0
  47. package/builders/metadata-check/index.d.ts.map +1 -0
  48. package/builders/metadata-check/index.js +12 -0
  49. package/builders/metadata-check/index.js.map +1 -0
  50. package/builders/metadata-check/schema.d.ts +5 -0
  51. package/builders/metadata-check/schema.d.ts.map +1 -0
  52. package/builders/metadata-check/schema.js +3 -0
  53. package/builders/metadata-check/schema.js.map +1 -0
  54. package/builders/metadata-check/schema.json +46 -0
  55. package/builders/package.json +3 -0
  56. package/builders.json +25 -0
  57. package/collection.json +29 -0
  58. package/fesm2022/o3r-transloco-rules-engine.mjs +57 -0
  59. package/fesm2022/o3r-transloco-rules-engine.mjs.map +1 -0
  60. package/fesm2022/o3r-transloco.mjs +1177 -0
  61. package/fesm2022/o3r-transloco.mjs.map +1 -0
  62. package/package.json +190 -2
  63. package/rules-engine/package.json +4 -0
  64. package/schemas/localization.metadata.schema.json +84 -0
  65. package/schemas/localization.schema.json +116 -0
  66. package/schemas/rules-engine.localization-action.schema.json +26 -0
  67. package/schematics/add-localization-key/index.d.ts +13 -0
  68. package/schematics/add-localization-key/index.d.ts.map +1 -0
  69. package/schematics/add-localization-key/index.js +227 -0
  70. package/schematics/add-localization-key/index.js.map +1 -0
  71. package/schematics/add-localization-key/schema.d.ts +20 -0
  72. package/schematics/add-localization-key/schema.d.ts.map +1 -0
  73. package/schematics/add-localization-key/schema.js +3 -0
  74. package/schematics/add-localization-key/schema.js.map +1 -0
  75. package/schematics/add-localization-key/schema.json +49 -0
  76. package/schematics/cms-adapter/index.d.ts +8 -0
  77. package/schematics/cms-adapter/index.d.ts.map +1 -0
  78. package/schematics/cms-adapter/index.js +81 -0
  79. package/schematics/cms-adapter/index.js.map +1 -0
  80. package/schematics/localization-base/index.d.ts +14 -0
  81. package/schematics/localization-base/index.d.ts.map +1 -0
  82. package/schematics/localization-base/index.js +359 -0
  83. package/schematics/localization-base/index.js.map +1 -0
  84. package/schematics/localization-base/templates/src/assets/locales/__empty__.gitkeep +0 -0
  85. package/schematics/localization-to-component/index.d.ts +13 -0
  86. package/schematics/localization-to-component/index.d.ts.map +1 -0
  87. package/schematics/localization-to-component/index.js +264 -0
  88. package/schematics/localization-to-component/index.js.map +1 -0
  89. package/schematics/localization-to-component/schema.d.ts +12 -0
  90. package/schematics/localization-to-component/schema.d.ts.map +1 -0
  91. package/schematics/localization-to-component/schema.js +3 -0
  92. package/schematics/localization-to-component/schema.js.map +1 -0
  93. package/schematics/localization-to-component/schema.json +31 -0
  94. package/schematics/localization-to-component/templates/__name__-localization.json +3 -0
  95. package/schematics/localization-to-component/templates/__name__-translation.ts.template +5 -0
  96. package/schematics/migration-localization-to-transloco/index.d.ts +8 -0
  97. package/schematics/migration-localization-to-transloco/index.d.ts.map +1 -0
  98. package/schematics/migration-localization-to-transloco/index.js +194 -0
  99. package/schematics/migration-localization-to-transloco/index.js.map +1 -0
  100. package/schematics/migration-localization-to-transloco/schema.d.ts +15 -0
  101. package/schematics/migration-localization-to-transloco/schema.d.ts.map +1 -0
  102. package/schematics/migration-localization-to-transloco/schema.js +3 -0
  103. package/schematics/migration-localization-to-transloco/schema.js.map +1 -0
  104. package/schematics/migration-localization-to-transloco/schema.json +30 -0
  105. package/schematics/ng-add/helpers/devtools-registration.d.ts +8 -0
  106. package/schematics/ng-add/helpers/devtools-registration.d.ts.map +1 -0
  107. package/schematics/ng-add/helpers/devtools-registration.js +37 -0
  108. package/schematics/ng-add/helpers/devtools-registration.js.map +1 -0
  109. package/schematics/ng-add/index.d.ts +8 -0
  110. package/schematics/ng-add/index.d.ts.map +1 -0
  111. package/schematics/ng-add/index.js +62 -0
  112. package/schematics/ng-add/index.js.map +1 -0
  113. package/schematics/ng-add/schema.d.ts +11 -0
  114. package/schematics/ng-add/schema.d.ts.map +1 -0
  115. package/schematics/ng-add/schema.js +3 -0
  116. package/schematics/ng-add/schema.js.map +1 -0
  117. package/schematics/ng-add/schema.json +38 -0
  118. package/schematics/package.json +3 -0
  119. package/types/o3r-transloco-rules-engine.d.ts +40 -0
  120. package/types/o3r-transloco-rules-engine.d.ts.map +1 -0
  121. package/types/o3r-transloco.d.ts +804 -0
  122. package/types/o3r-transloco.d.ts.map +1 -0
@@ -0,0 +1,1177 @@
1
+ import { immutablePrimitive, deepFill, otterComponentInfoPropertyName, sendOtterMessage, filterMessageContent } from '@o3r/core';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, inject, Injectable, Injector, Optional, NgModule, signal, ChangeDetectorRef, Pipe, RendererFactory2, LOCALE_ID, isDevMode, makeEnvironmentProviders, ElementRef, Renderer2, TemplateRef, DestroyRef, SimpleChange, Input, Directive, ApplicationRef } from '@angular/core';
4
+ import { TRANSLOCO_LOADER, TranslocoService, provideTransloco, TranslocoDirective, TranslocoPipe, TRANSLOCO_SCOPE, TRANSLOCO_LANG, TRANSLOCO_TRANSPILER } from '@jsverse/transloco';
5
+ import { from, of, combineLatest, startWith, switchMap as switchMap$1, map as map$1, distinctUntilChanged, firstValueFrom, shareReplay, Subject, lastValueFrom, fromEvent } from 'rxjs';
6
+ import { DynamicContentService } from '@o3r/dynamic-content';
7
+ import { LoggerService } from '@o3r/logger';
8
+ import { switchMap, catchError, map } from 'rxjs/operators';
9
+ import { CurrencyPipe, DatePipe, DecimalPipe } from '@angular/common';
10
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
+ import * as i1 from '@ngrx/store';
12
+ import { createAction, props, on, createReducer, StoreModule, createFeatureSelector, createSelector, Store, select } from '@ngrx/store';
13
+ import { Directionality } from '@angular/cdk/bidi';
14
+
15
+ /**
16
+ * Decorator to pass localization url
17
+ * @param url
18
+ */
19
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- decorator should start with a capital letter
20
+ function Localization(url) {
21
+ return (target, key) => {
22
+ const privateField = url || `_${key}`;
23
+ const privateValue = target[key];
24
+ if (delete target[key]) {
25
+ Object.defineProperty(target, key, {
26
+ get: function () {
27
+ return this[privateField];
28
+ },
29
+ set: function (value) {
30
+ const currentField = this[privateField] || privateValue;
31
+ this[privateField] = typeof currentField === 'undefined' ? immutablePrimitive(value) : deepFill(currentField, value);
32
+ if (this[otterComponentInfoPropertyName]) {
33
+ this[otterComponentInfoPropertyName].translations = this[privateField];
34
+ }
35
+ },
36
+ enumerable: true,
37
+ configurable: true
38
+ });
39
+ }
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Default configuration for LocalizationModule
45
+ */
46
+ const DEFAULT_LOCALIZATION_CONFIGURATION = {
47
+ supportedLocales: [],
48
+ endPointUrl: '',
49
+ useDynamicContent: false,
50
+ rtlLanguages: ['ar', 'he'],
51
+ fallbackLanguage: 'en',
52
+ bundlesOutputPath: '',
53
+ debugMode: false,
54
+ enableTranslationDeactivation: false,
55
+ mergeWithLocalTranslations: false
56
+ };
57
+
58
+ /**
59
+ * Formats a debug key string combining the translation key and its value.
60
+ * Used across the localization service, pipe, and directive when debugMode is enabled.
61
+ * @param key The translation key
62
+ * @param value The translated value
63
+ */
64
+ function getDebugKey(key, value) {
65
+ return `${key} - ${value}`;
66
+ }
67
+
68
+ /** Localization Configuration Token */
69
+ const LOCALIZATION_CONFIGURATION_TOKEN = new InjectionToken('Localization Configuration injection token');
70
+
71
+ const JSON_EXT = '.json';
72
+ /**
73
+ * This class is responsible for loading translation bundles from remote or local endpoints depending on the LocalizationConfiguration.
74
+ * Fallback mechanism ensures that if a bundle in some language cannot be fetched remotely
75
+ * we try to fetch the same language bundle locally (bundles stored inside the application)
76
+ * and finally load the fallback language bundle (if all previous fetches failed)
77
+ */
78
+ class TranslationsLoader {
79
+ constructor() {
80
+ this.localizationConfiguration = inject(LOCALIZATION_CONFIGURATION_TOKEN);
81
+ this.logger = inject(LoggerService, { optional: true });
82
+ this.dynamicContentService = inject(DynamicContentService, { optional: true });
83
+ }
84
+ /**
85
+ * Download a language bundle file
86
+ * @param url Url to the bundle file
87
+ */
88
+ downloadLanguageBundle$(url) {
89
+ const queryParams = this.localizationConfiguration.queryParams;
90
+ const parsedUrl = new URL(url, window.location.origin);
91
+ if (queryParams) {
92
+ Object.entries(queryParams).forEach(([key, value]) => parsedUrl.searchParams.append(key, value));
93
+ }
94
+ return from(fetch(parsedUrl.href, this.localizationConfiguration.fetchOptions)).pipe(switchMap((response) => response.json()));
95
+ }
96
+ /**
97
+ * @inheritdoc
98
+ */
99
+ getTranslation(lang) {
100
+ const fallback = this.localizationConfiguration.fallbackLanguage;
101
+ let localizationPath$ = of(this.localizationConfiguration.endPointUrl);
102
+ if (this.localizationConfiguration.useDynamicContent) {
103
+ if (!this.dynamicContentService) {
104
+ throw new Error('Dynamic Content is not available. Please verify you have provided the DynamicContent in your application');
105
+ }
106
+ localizationPath$ = this.dynamicContentService.getContentPathStream(this.localizationConfiguration.endPointUrl);
107
+ }
108
+ return localizationPath$.pipe(switchMap((localizationPath) => {
109
+ if (localizationPath) {
110
+ const localizationBundle$ = this.downloadLanguageBundle$(localizationPath + lang + JSON_EXT);
111
+ if (this.localizationConfiguration.mergeWithLocalTranslations) {
112
+ return combineLatest([
113
+ localizationBundle$.pipe(catchError(() => {
114
+ this.logger?.warn(`Failed to load the localization resource from ${localizationPath + lang + JSON_EXT} during merge, falling back to local translations only`);
115
+ return of({});
116
+ })),
117
+ this.getTranslationFromLocal(lang, fallback).pipe(map((translations) => {
118
+ Object.entries(translations).forEach(([key, value]) => translations[key] = `[local] ${value}`);
119
+ return translations;
120
+ }))
121
+ ]).pipe(map(([dynamicTranslations, localTranslations]) => ({ ...localTranslations, ...dynamicTranslations })));
122
+ }
123
+ /*
124
+ * if endPointUrl is specified by the configuration then:
125
+ * 1. try to load lang from endPointUrl
126
+ * 2. if 1 fails then try to load from the app (local file)
127
+ */
128
+ return localizationBundle$.pipe(catchError(() => {
129
+ this.logger?.warn(`Failed to load the localization resource from ${localizationPath + lang + JSON_EXT}, trying from the application resources`);
130
+ return this.getTranslationFromLocal(lang, fallback);
131
+ }));
132
+ }
133
+ /*
134
+ * else if endPointUrl NOT specified by then configuration then:
135
+ * 1. try to load from the app (local file)
136
+ */
137
+ this.logger?.warn('No localization endpoint specified, localization fetch from application resources');
138
+ return this.getTranslationFromLocal(lang, fallback);
139
+ }));
140
+ }
141
+ /**
142
+ *
143
+ *Fetches localization bundles from published folder (internal to application)
144
+ *
145
+ *1. try to load lang from local
146
+ *2. if 1 fails try to load fallback lang but only if it's different from lang in 1
147
+ * @param lang - language of the bundle
148
+ * @param fallbackLanguage - fallback language in case bundle in language not found
149
+ */
150
+ getTranslationFromLocal(lang, fallbackLanguage) {
151
+ const pathPrefix = this.localizationConfiguration.bundlesOutputPath;
152
+ return this.downloadLanguageBundle$(pathPrefix + lang + JSON_EXT).pipe(catchError(() => {
153
+ if (lang === fallbackLanguage) {
154
+ this.logger?.error(`Failed to load ${lang} from ${pathPrefix + lang + JSON_EXT}.`);
155
+ return of({});
156
+ }
157
+ else {
158
+ this.logger?.warn(`Failed to load ${lang} from ${pathPrefix + lang + JSON_EXT}. Application will fallback to ${fallbackLanguage}`);
159
+ return this.getTranslationFromLocal(fallbackLanguage, fallbackLanguage);
160
+ }
161
+ }));
162
+ }
163
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TranslationsLoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
164
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TranslationsLoader }); }
165
+ }
166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TranslationsLoader, decorators: [{
167
+ type: Injectable
168
+ }] });
169
+
170
+ /**
171
+ * Creates a loader of translations bundles based on the configuration
172
+ * (endPointUrl and language determine which bundle we load and where do we fetch it from)
173
+ * @param localizationConfiguration
174
+ * @param logger service to handle the log of warning and errors
175
+ * @param dynamicContentService (optional)
176
+ */
177
+ function createTranslateLoader(localizationConfiguration, logger, dynamicContentService) {
178
+ const injector = Injector.create({
179
+ providers: [
180
+ { provide: LOCALIZATION_CONFIGURATION_TOKEN, useValue: localizationConfiguration },
181
+ { provide: LoggerService, useValue: logger },
182
+ { provide: DynamicContentService, useValue: dynamicContentService },
183
+ {
184
+ provide: TranslationsLoader,
185
+ deps: [[LoggerService, new Optional()], [DynamicContentService, new Optional()], LOCALIZATION_CONFIGURATION_TOKEN]
186
+ }
187
+ ]
188
+ });
189
+ return injector.get(TranslationsLoader);
190
+ }
191
+ /**
192
+ * TranslocoLoader provider, using framework's TranslationsLoader class
193
+ */
194
+ const translateLoaderProvider = {
195
+ provide: TRANSLOCO_LOADER,
196
+ useFactory: createTranslateLoader,
197
+ deps: [LOCALIZATION_CONFIGURATION_TOKEN, [new Optional(), LoggerService], [new Optional(), DynamicContentService]]
198
+ };
199
+
200
+ /** Actions */
201
+ const ACTION_SET = '[LocalizationOverride] set';
202
+ /**
203
+ * Clear all overrides and fill the store with the payload
204
+ */
205
+ const setLocalizationOverride = createAction(ACTION_SET, props());
206
+
207
+ /**
208
+ * LocalizationOverride Store initial value
209
+ */
210
+ const localizationOverrideInitialState = { localizationOverrides: {} };
211
+ /**
212
+ * List of basic actions for LocalizationOverride Store
213
+ */
214
+ const localizationOverrideReducerFeatures = [
215
+ on(setLocalizationOverride, (_state, payload) => ({ ...payload.state }))
216
+ ];
217
+ /**
218
+ * LocalizationOverride Store reducer
219
+ */
220
+ const localizationOverrideReducer = createReducer(localizationOverrideInitialState, ...localizationOverrideReducerFeatures);
221
+
222
+ /**
223
+ * Name of the LocalizationOverride Store
224
+ */
225
+ const LOCALIZATION_OVERRIDE_STORE_NAME = 'localizationOverride';
226
+
227
+ /** Token of the LocalizationOverride reducer */
228
+ const LOCALIZATION_OVERRIDE_REDUCER_TOKEN = new InjectionToken('Feature LocalizationOverride Reducer');
229
+ /** Provide default reducer for LocalizationOverride store */
230
+ function getDefaultLocalizationOverrideReducer() {
231
+ return localizationOverrideReducer;
232
+ }
233
+ /**
234
+ * NgModule for localization override store.
235
+ */
236
+ class LocalizationOverrideStoreModule {
237
+ static forRoot(reducerFactory) {
238
+ return {
239
+ ngModule: LocalizationOverrideStoreModule,
240
+ providers: [
241
+ { provide: LOCALIZATION_OVERRIDE_REDUCER_TOKEN, useFactory: reducerFactory }
242
+ ]
243
+ };
244
+ }
245
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationOverrideStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
246
+ /** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.15", ngImport: i0, type: LocalizationOverrideStoreModule, imports: [i1.StoreFeatureModule] }); }
247
+ /** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationOverrideStoreModule, providers: [
248
+ { provide: LOCALIZATION_OVERRIDE_REDUCER_TOKEN, useFactory: getDefaultLocalizationOverrideReducer }
249
+ ], imports: [StoreModule.forFeature(LOCALIZATION_OVERRIDE_STORE_NAME, LOCALIZATION_OVERRIDE_REDUCER_TOKEN)] }); }
250
+ }
251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationOverrideStoreModule, decorators: [{
252
+ type: NgModule,
253
+ args: [{
254
+ imports: [
255
+ StoreModule.forFeature(LOCALIZATION_OVERRIDE_STORE_NAME, LOCALIZATION_OVERRIDE_REDUCER_TOKEN)
256
+ ],
257
+ providers: [
258
+ { provide: LOCALIZATION_OVERRIDE_REDUCER_TOKEN, useFactory: getDefaultLocalizationOverrideReducer }
259
+ ]
260
+ }]
261
+ }] });
262
+
263
+ /** Select LocalizationOverride State */
264
+ const selectLocalizationOverrideState = createFeatureSelector(LOCALIZATION_OVERRIDE_STORE_NAME);
265
+ /** Select all localization override map */
266
+ const selectLocalizationOverride = createSelector(selectLocalizationOverrideState, (state) => state?.localizationOverrides || {});
267
+
268
+ /**
269
+ * Deserializer for the LocalizationOverride store storage
270
+ * @param rawObject
271
+ */
272
+ const localizationOverrideStorageDeserializer = (rawObject) => {
273
+ if (!rawObject) {
274
+ return localizationOverrideInitialState;
275
+ }
276
+ return rawObject;
277
+ };
278
+ /** Serializer/Deserializer configuration for the LocalizationOverride store sync */
279
+ const localizationOverrideStorageSync = {
280
+ deserialize: localizationOverrideStorageDeserializer
281
+ };
282
+
283
+ /**
284
+ * Service which is wrapping the configuration logic of TranslocoService from JSVerse Transloco.
285
+ * Any application willing to use localization just needs to inject LocalizationService
286
+ * in the root component and call its configure() method.
287
+ */
288
+ class LocalizationService {
289
+ constructor() {
290
+ this.translateService = inject(TranslocoService);
291
+ this.logger = inject(LoggerService, { optional: true });
292
+ this.configuration = inject(LOCALIZATION_CONFIGURATION_TOKEN);
293
+ this.store = inject(Store, { optional: true });
294
+ this.localeSplitIdentifier = '-';
295
+ /**
296
+ * Internal signal that we use to track changes between keys only and translation mode
297
+ */
298
+ this._showKeys = signal(false, ...(ngDevMode ? [{ debugName: "_showKeys" }] : /* istanbul ignore next */ []));
299
+ /**
300
+ * _showKeys exposed as an Observable
301
+ */
302
+ this.showKeys$ = toObservable(this._showKeys);
303
+ /**
304
+ * Return the current value of debug show/hide translation keys.
305
+ */
306
+ this.showKeys = this._showKeys.asReadonly();
307
+ this.configure().catch((err) => {
308
+ this.logger?.error(`Failed to configure LocalizationService: ${err}`);
309
+ });
310
+ if (this.store) {
311
+ this.keyMapping$ = this.store.pipe(select(selectLocalizationOverride));
312
+ }
313
+ else {
314
+ this.logger?.debug('Store not available: localization key overrides via rules engine will not be active.');
315
+ }
316
+ }
317
+ /**
318
+ * This will handle the fallback language hierarchy to find out fallback language.
319
+ * supportedLocales language has highest priority, next priority goes to fallbackLocalesMap and default would be
320
+ * fallbackLanguage.
321
+ * @param language Selected language.
322
+ * @returns selected language if supported, fallback language otherwise.
323
+ */
324
+ checkFallbackLocalesMap(language) {
325
+ if (language && !this.configuration.supportedLocales.includes(language)) {
326
+ const closestSupportedLanguageCode = this.getFirstClosestSupportedLanguageCode(language);
327
+ const fallbackForLanguage = this.getFallbackMapLangCode(language);
328
+ const fallbackStrategyDebug = (fallbackForLanguage && ' associated fallback language ')
329
+ || (closestSupportedLanguageCode && ' closest supported language ')
330
+ || (this.configuration.fallbackLanguage && ' configured default language ');
331
+ const fallbackLang = fallbackForLanguage || closestSupportedLanguageCode || this.configuration.fallbackLanguage || language;
332
+ if (language !== fallbackLang) {
333
+ this.logger?.debug(`Non supported languages ${language} will fallback to ${fallbackStrategyDebug} ${fallbackLang}`);
334
+ }
335
+ return fallbackLang;
336
+ }
337
+ else if (!language) {
338
+ this.logger?.debug('Language is not defined');
339
+ }
340
+ return language;
341
+ }
342
+ /**
343
+ * This function checks if fallback language can be provided from fallbackLocalesMap.
344
+ * supportedLocales: ['en-GB', 'en-US', 'fr-FR'], fallbackLocalesMap: {'en-CA': 'en-US', 'de': 'fr-FR'}
345
+ * translate to en-CA -> fallback to en-US, translate to de-DE -> fallback to fr-FR
346
+ * translate to en-NZ -> fallback to en-GB
347
+ * @param language Selected language.
348
+ * @returns Fallback language if available, undefined otherwise.
349
+ */
350
+ getFallbackMapLangCode(language) {
351
+ const fallbackLocalesMap = this.configuration.fallbackLocalesMap;
352
+ const [locale] = language.split(this.localeSplitIdentifier);
353
+ return fallbackLocalesMap && (fallbackLocalesMap[language] || fallbackLocalesMap[locale]);
354
+ }
355
+ /**
356
+ * This function checks if closest supported language available incase of selected language is not
357
+ * supported language.
358
+ * supportedLocales: ['en-GB', 'en-US', 'fr-FR']
359
+ * translate to en-CA -> fallback to en-GB
360
+ * @param language Selected language.
361
+ * @returns Closest supported language if available, undefined otherwise.
362
+ */
363
+ getFirstClosestSupportedLanguageCode(language) {
364
+ const [locale] = language.split(this.localeSplitIdentifier);
365
+ const firstClosestRegx = new RegExp(`^${locale}${this.localeSplitIdentifier}?`, 'i');
366
+ return this.configuration.supportedLocales.find((supportedLang) => firstClosestRegx.test(supportedLang));
367
+ }
368
+ /**
369
+ * Returns a stream of translated values of a key which updates whenever the language changes.
370
+ * @param translationKey Key to translate
371
+ * @param interpolateParams Object to use in translation binding
372
+ * @returns A stream of the translated key
373
+ */
374
+ getTranslationStream(translationKey, interpolateParams) {
375
+ const translation$ = this.translateService.events$.pipe(startWith(undefined), switchMap$1(() => this.translateService.selectTranslate(translationKey, interpolateParams)), map$1((value) => this.configuration.debugMode ? getDebugKey(translationKey, value) : value), distinctUntilChanged());
376
+ if (!this.configuration.enableTranslationDeactivation) {
377
+ return translation$;
378
+ }
379
+ return combineLatest([
380
+ translation$,
381
+ this.showKeys$
382
+ ]).pipe(map$1(([value, showKeys]) => showKeys ? translationKey : value));
383
+ }
384
+ /**
385
+ * Configures TranslocoService and registers locales. This method is called from the application level.
386
+ */
387
+ async configure() {
388
+ const language = this.checkFallbackLocalesMap(this.configuration.language || this.configuration.fallbackLanguage);
389
+ this.translateService.setAvailableLangs(this.configuration.supportedLocales);
390
+ this.translateService.setDefaultLang(language);
391
+ await firstValueFrom(this.useLanguage(language));
392
+ }
393
+ /**
394
+ * Is the translation deactivation enabled
395
+ */
396
+ isTranslationDeactivationEnabled() {
397
+ return this.configuration.enableTranslationDeactivation;
398
+ }
399
+ /**
400
+ * Wrapper to call the TranslocoService method getAvailableLangs().
401
+ */
402
+ getLanguages() {
403
+ return this.translateService.getAvailableLangs().map((l) => (typeof l === 'string' ? l : l.id));
404
+ }
405
+ /**
406
+ * Wrapper to call the TranslocoService method setActiveLang(language).
407
+ * @param language
408
+ */
409
+ useLanguage(language) {
410
+ language = this.checkFallbackLocalesMap(language);
411
+ this.translateService.setActiveLang(language);
412
+ return this.translateService.load(language);
413
+ }
414
+ /**
415
+ * Wrapper to get the TranslocoService getActiveLang.
416
+ */
417
+ getCurrentLanguage() {
418
+ return this.translateService.getActiveLang();
419
+ }
420
+ /**
421
+ * Get the instance of the TranslocoService used by LocalizationService.
422
+ */
423
+ getTranslateService() {
424
+ return this.translateService;
425
+ }
426
+ /**
427
+ * Toggle the ShowKeys mode between active and inactive.
428
+ * @param value if specified, set the ShowKeys mode to value. If not specified, toggle the ShowKeys mode.
429
+ */
430
+ toggleShowKeys(value) {
431
+ if (!this.configuration.enableTranslationDeactivation) {
432
+ throw new Error('Translation deactivation is not enabled. Please set the LocalizationConfiguration property "enableTranslationDeactivation" accordingly.');
433
+ }
434
+ const newValue = value === undefined ? !this.showKeys() : value;
435
+ this._showKeys.set(newValue);
436
+ }
437
+ /**
438
+ * Get an observable of translation key after global mapping
439
+ * @param requestedKey Original translation key
440
+ */
441
+ getKey(requestedKey) {
442
+ return this.keyMapping$
443
+ ? this.keyMapping$.pipe(map$1((keyMapping) => keyMapping[requestedKey] || requestedKey), distinctUntilChanged())
444
+ : of(requestedKey);
445
+ }
446
+ /**
447
+ * Returns a stream of translated values of a key which updates whenever the language changes.
448
+ * @param key Key to translate
449
+ * @param interpolateParams Object to use in translation binding
450
+ * @returns A stream of the translated key
451
+ */
452
+ translate(key, interpolateParams) {
453
+ return this.getKey(key).pipe(switchMap$1((translationKey) => this.getTranslationStream(translationKey, interpolateParams)), shareReplay({ refCount: true, bufferSize: 1 }));
454
+ }
455
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
456
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationService }); }
457
+ }
458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationService, decorators: [{
459
+ type: Injectable
460
+ }], ctorParameters: () => [] });
461
+
462
+ /**
463
+ * Native angular CurrencyPipe taking the current lang into consideration
464
+ */
465
+ class LocalizedCurrencyPipe extends CurrencyPipe {
466
+ constructor() {
467
+ super(inject(LocalizationService).getCurrentLanguage());
468
+ this.localizationService = inject(LocalizationService);
469
+ this.changeDetectorRef = inject(ChangeDetectorRef);
470
+ this.localizationService.getTranslateService().langChanges$
471
+ .pipe(takeUntilDestroyed())
472
+ .subscribe(() => this.changeDetectorRef.markForCheck());
473
+ }
474
+ transform(value, currencyCode, display, digitsInfo, locale) {
475
+ return this.localizationService.showKeys() ? '' : super.transform(value, currencyCode, display, digitsInfo, locale || this.localizationService.getCurrentLanguage());
476
+ }
477
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedCurrencyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
478
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.15", ngImport: i0, type: LocalizedCurrencyPipe, isStandalone: true, name: "currency", pure: false }); }
479
+ }
480
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedCurrencyPipe, decorators: [{
481
+ type: Pipe,
482
+ args: [{
483
+ name: 'currency',
484
+ pure: false,
485
+ standalone: true
486
+ }]
487
+ }], ctorParameters: () => [] });
488
+
489
+ /**
490
+ * Native angular DatePipe taking the current lang into consideration
491
+ */
492
+ class LocalizedDatePipe extends DatePipe {
493
+ constructor() {
494
+ super(inject(LocalizationService).getCurrentLanguage());
495
+ this.localizationService = inject(LocalizationService);
496
+ this.changeDetectorRef = inject(ChangeDetectorRef);
497
+ this.localizationService.getTranslateService().langChanges$
498
+ .pipe(takeUntilDestroyed())
499
+ .subscribe(() => this.changeDetectorRef.markForCheck());
500
+ }
501
+ transform(value, format = 'mediumDate', timezone, locale) {
502
+ return this.localizationService.showKeys() ? format : super.transform(value, format, timezone, locale || this.localizationService.getCurrentLanguage());
503
+ }
504
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
505
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDatePipe, isStandalone: true, name: "date", pure: false }); }
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDatePipe, decorators: [{
508
+ type: Pipe,
509
+ args: [{
510
+ name: 'date',
511
+ pure: false,
512
+ standalone: true
513
+ }]
514
+ }], ctorParameters: () => [] });
515
+
516
+ /**
517
+ * Native angular DecimalPipe taking the current lang into consideration
518
+ */
519
+ class LocalizedDecimalPipe extends DecimalPipe {
520
+ constructor() {
521
+ super(inject(LocalizationService).getCurrentLanguage());
522
+ this.localizationService = inject(LocalizationService);
523
+ this.changeDetectorRef = inject(ChangeDetectorRef);
524
+ this.localizationService.getTranslateService().langChanges$
525
+ .pipe(takeUntilDestroyed())
526
+ .subscribe(() => this.changeDetectorRef.markForCheck());
527
+ }
528
+ transform(value, digitsInfo, locale) {
529
+ return this.localizationService.showKeys() ? '' : super.transform(value, digitsInfo, locale || this.localizationService.getCurrentLanguage());
530
+ }
531
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDecimalPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
532
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDecimalPipe, isStandalone: true, name: "number", pure: false }); }
533
+ }
534
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizedDecimalPipe, decorators: [{
535
+ type: Pipe,
536
+ args: [{
537
+ name: 'number',
538
+ pure: false,
539
+ standalone: true
540
+ }]
541
+ }], ctorParameters: () => [] });
542
+
543
+ /**
544
+ * Service for handling the text direction based on the LocalizationConfiguration
545
+ */
546
+ class TextDirectionService {
547
+ constructor() {
548
+ this.translateService = inject(TranslocoService);
549
+ this.configuration = inject(LOCALIZATION_CONFIGURATION_TOKEN);
550
+ this.rendererFactory = inject(RendererFactory2);
551
+ this.directionality = inject(Directionality);
552
+ this.renderer = this.rendererFactory.createRenderer(null, null);
553
+ }
554
+ /**
555
+ * Updates the dir attribute on body HTML tag.
556
+ * @returns a subscription that updates the dir attribute
557
+ */
558
+ onLangChangeSubscription() {
559
+ if (this.subscription && !this.subscription.closed) {
560
+ return this.subscription;
561
+ }
562
+ this.subscription = this.translateService.langChanges$.subscribe((lang) => {
563
+ const direction = this.configuration.rtlLanguages.includes(lang.split('-')[0]) ? 'rtl' : 'ltr';
564
+ this.renderer.setAttribute(document.body, 'dir', direction);
565
+ this.directionality.change.emit(direction);
566
+ });
567
+ return this.subscription;
568
+ }
569
+ ngOnDestroy() {
570
+ this.subscription?.unsubscribe();
571
+ }
572
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TextDirectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
573
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TextDirectionService }); }
574
+ }
575
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: TextDirectionService, decorators: [{
576
+ type: Injectable
577
+ }], ctorParameters: () => [] });
578
+
579
+ /**
580
+ * creates LocalizationConfiguration, which is used if the application
581
+ * @param configuration Localization configuration
582
+ */
583
+ function createLocalizationConfiguration(configuration) {
584
+ return {
585
+ ...DEFAULT_LOCALIZATION_CONFIGURATION,
586
+ ...configuration
587
+ };
588
+ }
589
+ /**
590
+ * Factory to inject the LOCALE_ID token with the current language into Angular context
591
+ * @param localizationService Localization service
592
+ */
593
+ function localeIdNgBridge(localizationService) {
594
+ return localizationService.getCurrentLanguage();
595
+ }
596
+ /** Custom Localization Configuration Token to override default localization configuration */
597
+ const CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN = new InjectionToken('Partial Localization configuration');
598
+ /**
599
+ * Provide localization services and configuration for the application.
600
+ * This is the recommended way to set up localization in standalone applications.
601
+ * @param configuration Optional partial localization configuration to override defaults. Can be a configuration object or a factory function.
602
+ * @example Override of default and supported languages override at application bootstrap
603
+ * ```typescript
604
+ * bootstrapApplication(App, {
605
+ * providers: [
606
+ * provideLocalization({ language: 'en-US', supportedLocales: ['en-US', 'fr-FR'] })
607
+ * ]
608
+ * });
609
+ * ```
610
+ */
611
+ function provideLocalization(configuration) {
612
+ const config = typeof configuration === 'function' ? configuration() : (configuration || {});
613
+ const mergedConfig = { ...DEFAULT_LOCALIZATION_CONFIGURATION, ...config };
614
+ const providers = [
615
+ provideTransloco({
616
+ config: {
617
+ availableLangs: mergedConfig.supportedLocales || [],
618
+ defaultLang: mergedConfig.language || mergedConfig.fallbackLanguage,
619
+ reRenderOnLangChange: true,
620
+ prodMode: !isDevMode(),
621
+ fallbackLang: mergedConfig.fallbackLanguage,
622
+ missingHandler: {
623
+ useFallbackTranslation: true
624
+ }
625
+ }
626
+ }),
627
+ translateLoaderProvider,
628
+ LocalizationService,
629
+ TextDirectionService,
630
+ { provide: LOCALIZATION_CONFIGURATION_TOKEN, useFactory: createLocalizationConfiguration, deps: [[new Optional(), CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN]] },
631
+ { provide: LOCALE_ID, useFactory: localeIdNgBridge, deps: [LocalizationService] },
632
+ { provide: DatePipe, useClass: LocalizedDatePipe },
633
+ { provide: DecimalPipe, useClass: LocalizedDecimalPipe },
634
+ { provide: CurrencyPipe, useClass: LocalizedCurrencyPipe }
635
+ ];
636
+ if (configuration) {
637
+ providers.push({
638
+ provide: CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN,
639
+ ...(typeof configuration === 'function' ? { useFactory: configuration } : { useValue: configuration })
640
+ });
641
+ }
642
+ return makeEnvironmentProviders(providers);
643
+ }
644
+
645
+ /**
646
+ * TranslocoDirective class adding debug functionality.
647
+ *
648
+ * Extends the TranslocoDirective from Transloco to add:
649
+ * - Key mapping via LocalizationService (override store)
650
+ * - Show keys mode (display raw keys instead of translations)
651
+ * - Debug mode (display key alongside translation)
652
+ *
653
+ * For the structural strategy (`*transloco`), the debug/showKeys behavior is handled
654
+ * by overriding `getTranslateFn`.
655
+ *
656
+ * For the attribute strategy (`[transloco]="key"`), the parent's private `attributeStrategy()`
657
+ * sets `innerText` directly. Since we cannot override this private method, we apply
658
+ * debug/showKeys transformations by overwriting `innerText` immediately after calling
659
+ * `super.ngOnInit()` and `super.ngOnChanges()`, in the same call stack.
660
+ */
661
+ class LocalizationTranslateDirective extends TranslocoDirective {
662
+ /** @inheritdoc */
663
+ set transloco(key) {
664
+ if (key) {
665
+ this.translocoSubject.next(key);
666
+ }
667
+ }
668
+ constructor() {
669
+ super();
670
+ this.localizationService = inject(LocalizationService);
671
+ this.localizationConfig = inject(LOCALIZATION_CONFIGURATION_TOKEN);
672
+ this.changeDetectorRef = inject(ChangeDetectorRef);
673
+ this.hostElement = inject(ElementRef);
674
+ this.hostRenderer = inject(Renderer2);
675
+ this.isAttributeStrategy = inject(TemplateRef, { optional: true }) === null;
676
+ this.o3rDestroyRef = inject(DestroyRef);
677
+ /**
678
+ * Should we display keys instead of translations
679
+ */
680
+ this.showKeys = false;
681
+ /**
682
+ * Subject to emit translation key changes
683
+ */
684
+ this.translocoSubject = new Subject();
685
+ if (this.localizationConfig.enableTranslationDeactivation) {
686
+ this.localizationService.showKeys$.pipe(takeUntilDestroyed(this.o3rDestroyRef)).subscribe((showKeys) => {
687
+ this.showKeys = showKeys;
688
+ this.changeDetectorRef.markForCheck();
689
+ });
690
+ }
691
+ // Set up reactive key mapping subscription
692
+ this.translocoSubject.pipe(switchMap$1((key) => this.localizationService.getKey(key)), takeUntilDestroyed(this.o3rDestroyRef)).subscribe((newKey) => {
693
+ const previousKey = this.key;
694
+ this.key = newKey;
695
+ this.changeDetectorRef.markForCheck();
696
+ super.ngOnChanges({ transloco: new SimpleChange(previousKey, newKey, !previousKey) });
697
+ });
698
+ }
699
+ /**
700
+ * For the attribute strategy, overwrites `innerText` with the debug/showKeys value
701
+ * immediately after the parent has set it.
702
+ */
703
+ applyAttributeDebug() {
704
+ if (!this.isAttributeStrategy || !this.key) {
705
+ return;
706
+ }
707
+ if (this.showKeys) {
708
+ this.hostRenderer.setProperty(this.hostElement.nativeElement, 'innerText', this.key);
709
+ }
710
+ else if (this.localizationConfig.debugMode) {
711
+ this.hostRenderer.setProperty(this.hostElement.nativeElement, 'innerText', getDebugKey(this.key, this.hostElement.nativeElement.innerText));
712
+ }
713
+ }
714
+ /**
715
+ * Override getTranslateFn to plug debug/showKeys for the structural strategy.
716
+ * @param lang Current language
717
+ * @param prefix Scope prefix
718
+ */
719
+ getTranslateFn(lang, prefix) {
720
+ const parentTranslateFn = super.getTranslateFn(lang, prefix);
721
+ return (key, params) => {
722
+ if (this.showKeys) {
723
+ return key;
724
+ }
725
+ const value = parentTranslateFn(key, params);
726
+ if (this.localizationConfig.debugMode) {
727
+ return getDebugKey(key, value);
728
+ }
729
+ return value;
730
+ };
731
+ }
732
+ /** @inheritdoc */
733
+ ngOnInit() {
734
+ super.ngOnInit();
735
+ this.applyAttributeDebug();
736
+ }
737
+ /** @inheritdoc */
738
+ ngOnChanges(changes) {
739
+ super.ngOnChanges(changes);
740
+ this.applyAttributeDebug();
741
+ }
742
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationTranslateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
743
+ /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.15", type: LocalizationTranslateDirective, isStandalone: true, selector: "[transloco]", inputs: { transloco: "transloco" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
744
+ }
745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationTranslateDirective, decorators: [{
746
+ type: Directive,
747
+ args: [{
748
+ selector: '[transloco]',
749
+ standalone: true
750
+ }]
751
+ }], ctorParameters: () => [], propDecorators: { transloco: [{
752
+ type: Input
753
+ }] } });
754
+
755
+ /**
756
+ * TranslocoPipe class adding debug functionality
757
+ */
758
+ class O3rLocalizationTranslatePipe extends TranslocoPipe {
759
+ constructor() {
760
+ super(inject(TranslocoService), inject(TRANSLOCO_SCOPE, { optional: true }) ?? undefined, inject(TRANSLOCO_LANG, { optional: true }) ?? undefined, inject(ChangeDetectorRef));
761
+ /** Localization service instance */
762
+ this.localizationService = inject(LocalizationService);
763
+ /** Change detector service instance */
764
+ this.changeDetector = inject(ChangeDetectorRef);
765
+ /** Localization config token */
766
+ this.localizationConfig = inject(LOCALIZATION_CONFIGURATION_TOKEN);
767
+ /** Destroy reference */
768
+ this.destroyRef = inject(DestroyRef);
769
+ /**
770
+ * Should we display keys instead of translations
771
+ */
772
+ this.showKeys = false;
773
+ /**
774
+ * Subject to emit translation key changes
775
+ */
776
+ this.querySubject = new Subject();
777
+ if (this.localizationConfig.enableTranslationDeactivation) {
778
+ this.localizationService.showKeys$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((showKeys) => {
779
+ this.showKeys = showKeys;
780
+ this.changeDetector.markForCheck();
781
+ });
782
+ }
783
+ // Set up reactive key mapping subscription
784
+ this.querySubject.pipe(switchMap$1((query) => this.localizationService.getKey(query)), takeUntilDestroyed(this.destroyRef)).subscribe((key) => {
785
+ this.lastResolvedKey = key;
786
+ this.changeDetector.markForCheck();
787
+ });
788
+ }
789
+ /**
790
+ * Calls original transform method and eventually outputs the key if debugMode (in LocalizationConfiguration) is enabled
791
+ * @inheritdoc
792
+ */
793
+ transform(query, ...args) {
794
+ if (!query || this.localizationService.showKeys()) {
795
+ return query;
796
+ }
797
+ // Emit query to subject for reactive key mapping
798
+ this.querySubject.next(query);
799
+ const value = super.transform(this.lastResolvedKey, ...args);
800
+ if (this.localizationConfig.debugMode) {
801
+ return getDebugKey(this.lastResolvedKey, value);
802
+ }
803
+ return value;
804
+ }
805
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: O3rLocalizationTranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
806
+ /** @nocollapse */ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.15", ngImport: i0, type: O3rLocalizationTranslatePipe, isStandalone: true, name: "o3rTranslate", pure: false }); }
807
+ }
808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: O3rLocalizationTranslatePipe, decorators: [{
809
+ type: Pipe,
810
+ args: [{
811
+ name: 'o3rTranslate',
812
+ pure: false,
813
+ standalone: true
814
+ }]
815
+ }], ctorParameters: () => [] });
816
+
817
+ /**
818
+ * Service that provides core localization devtools functionality
819
+ */
820
+ class OtterLocalizationDevtools {
821
+ constructor() {
822
+ this.localizationService = inject(LocalizationService);
823
+ this.translateTranspiler = inject(TRANSLOCO_TRANSPILER);
824
+ this.appRef = inject(ApplicationRef);
825
+ this.languageChangeSubscriptions = new Set();
826
+ }
827
+ /**
828
+ * Is the translation deactivation enabled
829
+ */
830
+ isTranslationDeactivationEnabled() {
831
+ return this.localizationService.isTranslationDeactivationEnabled();
832
+ }
833
+ /**
834
+ * Show localization keys
835
+ * @param value value enforced by the DevTools extension
836
+ */
837
+ showLocalizationKeys(value) {
838
+ this.localizationService.toggleShowKeys(value);
839
+ this.appRef.tick();
840
+ }
841
+ /**
842
+ * Returns the current language
843
+ */
844
+ getCurrentLanguage() {
845
+ return this.localizationService.getCurrentLanguage();
846
+ }
847
+ /**
848
+ * Set up a listener on language change
849
+ * @param fn called when the language is changed in the app
850
+ * @returns Object with unsubscribe() method to prevent memory leaks
851
+ */
852
+ onLanguageChange(fn) {
853
+ const subscription = this.localizationService
854
+ .getTranslateService()
855
+ .langChanges$
856
+ .subscribe((lang) => {
857
+ fn(lang);
858
+ });
859
+ this.languageChangeSubscriptions.add(subscription);
860
+ // Return custom object with unsubscribe method
861
+ return {
862
+ unsubscribe: () => {
863
+ subscription.unsubscribe();
864
+ this.languageChangeSubscriptions.delete(subscription);
865
+ }
866
+ };
867
+ }
868
+ /**
869
+ * Clear all language change listeners
870
+ */
871
+ clearLanguageChangeListeners() {
872
+ this.languageChangeSubscriptions.forEach((sub) => sub.unsubscribe());
873
+ this.languageChangeSubscriptions.clear();
874
+ }
875
+ /**
876
+ * Cleanup subscriptions when service is destroyed
877
+ */
878
+ ngOnDestroy() {
879
+ this.clearLanguageChangeListeners();
880
+ }
881
+ /**
882
+ * Switch the current language to the specified value
883
+ * @param language new language to switch to
884
+ */
885
+ async switchLanguage(language) {
886
+ if (!language) {
887
+ return;
888
+ }
889
+ await lastValueFrom(this.localizationService.useLanguage(language));
890
+ this.appRef.tick();
891
+ }
892
+ /**
893
+ * Updates the specified localization key/values for the current language.
894
+ *
895
+ * Recommendation: To be used with a small number of keys to update to avoid performance issues.
896
+ * @param keyValues key/values to update
897
+ * @param language if not provided, the current language value
898
+ */
899
+ updateLocalizationKeys(keyValues, language) {
900
+ const lang = language || this.getCurrentLanguage();
901
+ const translateService = this.localizationService.getTranslateService();
902
+ Object.entries(keyValues).forEach(([key, value]) => {
903
+ translateService.setTranslationKey(key, value, { lang });
904
+ });
905
+ this.appRef.tick();
906
+ }
907
+ /**
908
+ * Reload a language from the language file
909
+ * @param language language to reload
910
+ */
911
+ async reloadLocalizationKeys(language) {
912
+ const lang = language || this.getCurrentLanguage();
913
+ if (this.translateTranspiler.setLocale) {
914
+ this.translateTranspiler.setLocale(null);
915
+ }
916
+ const translateService = this.localizationService.getTranslateService();
917
+ const translations = await lastValueFrom(translateService.load(lang));
918
+ translateService.setTranslation(translations, lang, { merge: false });
919
+ this.appRef.tick();
920
+ }
921
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: OtterLocalizationDevtools, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
922
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: OtterLocalizationDevtools }); }
923
+ }
924
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: OtterLocalizationDevtools, decorators: [{
925
+ type: Injectable
926
+ }] });
927
+
928
+ /**
929
+ * Default configuration options for the Otter Localization Devtools
930
+ */
931
+ const OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS = {
932
+ isActivatedOnBootstrap: false,
933
+ isActivatedOnBootstrapWhenCMSContext: true,
934
+ metadataFilePath: './metadata/localisation.metadata.json'
935
+ };
936
+ /**
937
+ * Injection token for Otter Localization Devtools configuration options
938
+ */
939
+ const OTTER_LOCALIZATION_DEVTOOLS_OPTIONS = new InjectionToken('Otter Localization Devtools options');
940
+
941
+ /* eslint-disable no-console -- This is the purpose of this service */
942
+ /**
943
+ * Service that exposes localization devtools functionality via the browser console
944
+ */
945
+ class LocalizationDevtoolsConsoleService {
946
+ /** Name of the Window property to access to the devtools */
947
+ static { this.windowModuleName = 'localization'; }
948
+ constructor() {
949
+ this.localizationDevtools = inject(OtterLocalizationDevtools);
950
+ this.options = inject(OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, { optional: true }) ?? OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS;
951
+ if (this.options.isActivatedOnBootstrap
952
+ || (this.options.isActivatedOnBootstrapWhenCMSContext
953
+ && document.body.dataset.cmscontext === 'true')) {
954
+ this.activate();
955
+ }
956
+ }
957
+ /** @inheritDoc */
958
+ activate() {
959
+ const windowWithDevtools = window;
960
+ windowWithDevtools._OTTER_DEVTOOLS_ ||= {};
961
+ windowWithDevtools._OTTER_DEVTOOLS_[LocalizationDevtoolsConsoleService.windowModuleName] = this;
962
+ console.info(`Otter localization Devtools is now accessible via the _OTTER_DEVTOOLS_.${LocalizationDevtoolsConsoleService.windowModuleName} variable`);
963
+ }
964
+ /**
965
+ * @inheritdoc
966
+ */
967
+ isTranslationDeactivationEnabled() {
968
+ return this.localizationDevtools.isTranslationDeactivationEnabled();
969
+ }
970
+ /**
971
+ * @inheritdoc
972
+ */
973
+ showLocalizationKeys(value) {
974
+ this.localizationDevtools.showLocalizationKeys(value);
975
+ }
976
+ /**
977
+ * @inheritdoc
978
+ */
979
+ getCurrentLanguage() {
980
+ const currentLanguage = this.localizationDevtools.getCurrentLanguage();
981
+ return currentLanguage;
982
+ }
983
+ /**
984
+ * @inheritdoc
985
+ */
986
+ async switchLanguage(language) {
987
+ const previous = this.localizationDevtools.getCurrentLanguage();
988
+ await this.localizationDevtools.switchLanguage(language);
989
+ const current = this.localizationDevtools.getCurrentLanguage();
990
+ return {
991
+ requested: language,
992
+ previous,
993
+ current
994
+ };
995
+ }
996
+ /**
997
+ * @inheritdoc
998
+ */
999
+ onLanguageChange(fn) {
1000
+ return this.localizationDevtools.onLanguageChange(fn);
1001
+ }
1002
+ /**
1003
+ * @inheritdoc
1004
+ */
1005
+ clearLanguageChangeListeners() {
1006
+ this.localizationDevtools.clearLanguageChangeListeners();
1007
+ }
1008
+ /**
1009
+ * @inheritdoc
1010
+ */
1011
+ updateLocalizationKeys(keyValues, language) {
1012
+ return this.localizationDevtools.updateLocalizationKeys(keyValues, language);
1013
+ }
1014
+ /**
1015
+ * @inheritdoc
1016
+ */
1017
+ reloadLocalizationKeys(language) {
1018
+ return this.localizationDevtools.reloadLocalizationKeys(language);
1019
+ }
1020
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsConsoleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1021
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsConsoleService }); }
1022
+ }
1023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsConsoleService, decorators: [{
1024
+ type: Injectable
1025
+ }], ctorParameters: () => [] });
1026
+
1027
+ /**
1028
+ * Type guard to check if a message is a localization message
1029
+ * @param message Message to check
1030
+ * @returns True if the message is a localization message
1031
+ */
1032
+ const isLocalizationMessage = (message) => {
1033
+ return message && (message.dataType === 'displayLocalizationKeys'
1034
+ || message.dataType === 'languages'
1035
+ || message.dataType === 'switchLanguage'
1036
+ || message.dataType === 'localizations'
1037
+ || message.dataType === 'updateLocalization'
1038
+ || message.dataType === 'requestMessages'
1039
+ || message.dataType === 'connect'
1040
+ || message.dataType === 'reloadLocalizationKeys'
1041
+ || message.dataType === 'isTranslationDeactivationEnabled'
1042
+ || message.dataType === 'getTranslationValuesContentMessage');
1043
+ };
1044
+ /**
1045
+ * Service that handles localization devtools messages communication
1046
+ */
1047
+ class LocalizationDevtoolsMessageService {
1048
+ constructor() {
1049
+ this.logger = inject(LoggerService);
1050
+ this.localizationDevTools = inject(OtterLocalizationDevtools);
1051
+ this.localizationService = inject(LocalizationService);
1052
+ this.options = inject(OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, { optional: true }) ?? OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS;
1053
+ this.sendMessage = (sendOtterMessage);
1054
+ this.destroyRef = inject(DestroyRef);
1055
+ this.options = {
1056
+ ...OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS,
1057
+ ...this.options
1058
+ };
1059
+ if (this.options.isActivatedOnBootstrap) {
1060
+ this.activate();
1061
+ }
1062
+ }
1063
+ async sendLocalizationsMetadata() {
1064
+ const metadata = await (await fetch(this.options.metadataFilePath)).json();
1065
+ this.sendMessage('localizations', {
1066
+ localizations: metadata
1067
+ });
1068
+ }
1069
+ /**
1070
+ * Function to trigger a re-send a requested messages to the Otter Chrome DevTools extension
1071
+ * @param only restricted list of messages to re-send
1072
+ */
1073
+ handleReEmitRequest(only) {
1074
+ if (!only || only.includes('localizations')) {
1075
+ void this.sendLocalizationsMetadata();
1076
+ }
1077
+ if (!only || only.includes('switchLanguage')) {
1078
+ this.sendMessage('switchLanguage', { language: this.localizationDevTools.getCurrentLanguage() });
1079
+ }
1080
+ if (!only || only.includes('languages')) {
1081
+ this.sendMessage('languages', { languages: this.localizationService.getLanguages() });
1082
+ }
1083
+ if (!only || only.includes('getTranslationValuesContentMessage')) {
1084
+ this.sendMessage('getTranslationValuesContentMessage', {
1085
+ translations: this.localizationService.getTranslateService().getTranslation(this.localizationService.getCurrentLanguage())
1086
+ });
1087
+ }
1088
+ if (!only || only.includes('isTranslationDeactivationEnabled')) {
1089
+ this.sendMessage('isTranslationDeactivationEnabled', { enabled: this.localizationService.isTranslationDeactivationEnabled() });
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Function to handle the incoming messages from Otter Chrome DevTools extension
1094
+ * @param message Message coming from the Otter Chrome DevTools extension
1095
+ */
1096
+ handleEvents(message) {
1097
+ this.logger.debug('Message handling by the localization service', message);
1098
+ switch (message.dataType) {
1099
+ case 'connect': {
1100
+ this.connectPlugin();
1101
+ break;
1102
+ }
1103
+ case 'displayLocalizationKeys': {
1104
+ this.localizationDevTools.showLocalizationKeys(message.toggle);
1105
+ break;
1106
+ }
1107
+ case 'requestMessages': {
1108
+ void this.handleReEmitRequest(message.only);
1109
+ break;
1110
+ }
1111
+ case 'switchLanguage': {
1112
+ void this.localizationDevTools.switchLanguage(message.language);
1113
+ break;
1114
+ }
1115
+ case 'updateLocalization': {
1116
+ void this.localizationDevTools.updateLocalizationKeys({
1117
+ [message.key]: message.value
1118
+ }, message.lang);
1119
+ break;
1120
+ }
1121
+ case 'reloadLocalizationKeys': {
1122
+ void this.localizationDevTools.reloadLocalizationKeys(message.lang);
1123
+ break;
1124
+ }
1125
+ default: {
1126
+ this.logger.warn('Message ignored by the localization service', message);
1127
+ }
1128
+ }
1129
+ }
1130
+ /**
1131
+ * Function to connect the plugin to the Otter Chrome DevTools extension
1132
+ */
1133
+ connectPlugin() {
1134
+ this.logger.debug('Otter DevTools is plugged to localization service of the application');
1135
+ }
1136
+ /** @inheritDoc */
1137
+ activate() {
1138
+ fromEvent(window, 'message').pipe(takeUntilDestroyed(this.destroyRef), filterMessageContent(isLocalizationMessage)).subscribe((e) => this.handleEvents(e));
1139
+ }
1140
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsMessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1141
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsMessageService }); }
1142
+ }
1143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: LocalizationDevtoolsMessageService, decorators: [{
1144
+ type: Injectable
1145
+ }], ctorParameters: () => [] });
1146
+
1147
+ /**
1148
+ * Provide localization devtools functionality for the application.
1149
+ * This is the recommended way to set up localization devtools in standalone applications.
1150
+ * @param options Optional partial localization devtools configuration to override defaults. Can be a configuration object or a factory function.
1151
+ * @example Load localization with automatic activation at bootstrap
1152
+ * ```typescript
1153
+ * bootstrapApplication(App, {
1154
+ * providers: [
1155
+ * provideLocalizationDevtools({ isActivatedOnBootstrap: true })
1156
+ * ]
1157
+ * });
1158
+ * ```
1159
+ */
1160
+ function provideLocalizationDevtools(options) {
1161
+ return makeEnvironmentProviders([
1162
+ {
1163
+ provide: OTTER_LOCALIZATION_DEVTOOLS_OPTIONS,
1164
+ useValue: { ...OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS, ...options }
1165
+ },
1166
+ LocalizationDevtoolsMessageService,
1167
+ LocalizationDevtoolsConsoleService,
1168
+ OtterLocalizationDevtools
1169
+ ]);
1170
+ }
1171
+
1172
+ /**
1173
+ * Generated bundle index. Do not edit.
1174
+ */
1175
+
1176
+ export { CUSTOM_LOCALIZATION_CONFIGURATION_TOKEN, DEFAULT_LOCALIZATION_CONFIGURATION, LOCALIZATION_CONFIGURATION_TOKEN, LOCALIZATION_OVERRIDE_REDUCER_TOKEN, LOCALIZATION_OVERRIDE_STORE_NAME, Localization, LocalizationDevtoolsConsoleService, LocalizationDevtoolsMessageService, LocalizationOverrideStoreModule, LocalizationService, LocalizationTranslateDirective, LocalizedCurrencyPipe, LocalizedDatePipe, LocalizedDecimalPipe, O3rLocalizationTranslatePipe, OTTER_LOCALIZATION_DEVTOOLS_DEFAULT_OPTIONS, OTTER_LOCALIZATION_DEVTOOLS_OPTIONS, OtterLocalizationDevtools, TextDirectionService, TranslationsLoader, createLocalizationConfiguration, createTranslateLoader, getDebugKey, getDefaultLocalizationOverrideReducer, localeIdNgBridge, localizationOverrideInitialState, localizationOverrideReducer, localizationOverrideReducerFeatures, localizationOverrideStorageDeserializer, localizationOverrideStorageSync, provideLocalization, provideLocalizationDevtools, selectLocalizationOverride, selectLocalizationOverrideState, setLocalizationOverride, translateLoaderProvider };
1177
+ //# sourceMappingURL=o3r-transloco.mjs.map