@acorex/core 19.13.0-next.8 → 19.13.0

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 (38) hide show
  1. package/README.md +11 -14
  2. package/fesm2022/acorex-core-components.mjs +3 -3
  3. package/fesm2022/acorex-core-config.mjs +3 -3
  4. package/fesm2022/acorex-core-date-time.mjs +25 -25
  5. package/fesm2022/acorex-core-events.mjs +3 -3
  6. package/fesm2022/acorex-core-file.mjs +10 -10
  7. package/fesm2022/acorex-core-format.mjs +35 -36
  8. package/fesm2022/acorex-core-format.mjs.map +1 -1
  9. package/fesm2022/acorex-core-image.mjs +3 -3
  10. package/fesm2022/acorex-core-locale.mjs +10 -10
  11. package/fesm2022/acorex-core-network.mjs +3 -3
  12. package/fesm2022/acorex-core-pipes.mjs +3 -3
  13. package/fesm2022/acorex-core-platform.mjs +3 -3
  14. package/fesm2022/acorex-core-storage.mjs +9 -9
  15. package/fesm2022/acorex-core-translation.mjs +213 -248
  16. package/fesm2022/acorex-core-translation.mjs.map +1 -1
  17. package/fesm2022/acorex-core-utils.mjs +18 -143
  18. package/fesm2022/acorex-core-utils.mjs.map +1 -1
  19. package/fesm2022/acorex-core-validation.mjs +40 -40
  20. package/format/lib/format.service.d.ts +7 -8
  21. package/package.json +1 -1
  22. package/translation/index.d.ts +1 -1
  23. package/translation/lib/translation.config.d.ts +6 -19
  24. package/translation/lib/translation.loader.d.ts +2 -2
  25. package/translation/lib/translation.service.d.ts +32 -13
  26. package/translation/lib/translation.types.d.ts +2 -8
  27. package/translation/lib/translator.pipe.d.ts +2 -0
  28. package/utils/index.d.ts +5 -6
  29. package/utils/lib/string-util.d.ts +6 -0
  30. package/translation/lib/translation-loader.service.d.ts +0 -20
  31. package/translation/lib/translation.parser.d.ts +0 -7
  32. package/translation/lib/translation.resolver.d.ts +0 -9
  33. package/utils/lib/execution.utils.d.ts +0 -33
  34. package/utils/lib/string.utils.d.ts +0 -4
  35. /package/utils/lib/{lifecycle-helpers.utils.d.ts → auto-unsubscribe.d.ts} +0 -0
  36. /package/utils/lib/{color.utils.d.ts → color-util.d.ts} +0 -0
  37. /package/utils/lib/{drawing.utils.d.ts → drawing-util.d.ts} +0 -0
  38. /package/utils/lib/{html-utils.d.ts → html-util.d.ts} +0 -0
@@ -282,10 +282,10 @@ class AXPlatform {
282
282
  });
283
283
  }
284
284
  }
285
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXPlatform, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
286
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXPlatform, providedIn: 'root' }); }
285
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXPlatform, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
286
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXPlatform, providedIn: 'root' }); }
287
287
  }
288
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXPlatform, decorators: [{
288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXPlatform, decorators: [{
289
289
  type: Injectable,
290
290
  args: [{
291
291
  providedIn: 'root',
@@ -184,10 +184,10 @@ class AXCookieStorageService {
184
184
  }
185
185
  }
186
186
  }
187
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXCookieStorageService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
188
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXCookieStorageService }); }
187
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXCookieStorageService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
188
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXCookieStorageService }); }
189
189
  }
190
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXCookieStorageService, decorators: [{
190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXCookieStorageService, decorators: [{
191
191
  type: Injectable
192
192
  }], ctorParameters: () => [{ type: Document, decorators: [{
193
193
  type: Inject,
@@ -242,10 +242,10 @@ class AXLocalStorageService {
242
242
  return null;
243
243
  }
244
244
  }
245
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXLocalStorageService, deps: [{ token: AX_LOCALSTORAGE_SECRET_KEY }], target: i0.ɵɵFactoryTarget.Injectable }); }
246
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXLocalStorageService }); }
245
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXLocalStorageService, deps: [{ token: AX_LOCALSTORAGE_SECRET_KEY }], target: i0.ɵɵFactoryTarget.Injectable }); }
246
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXLocalStorageService }); }
247
247
  }
248
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXLocalStorageService, decorators: [{
248
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXLocalStorageService, decorators: [{
249
249
  type: Injectable
250
250
  }], ctorParameters: () => [{ type: undefined, decorators: [{
251
251
  type: Inject,
@@ -277,10 +277,10 @@ class AXSessionStorageService {
277
277
  return sessionStorage.removeItem(key);
278
278
  }
279
279
  }
280
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXSessionStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
281
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXSessionStorageService }); }
280
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXSessionStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
281
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXSessionStorageService }); }
282
282
  }
283
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXSessionStorageService, decorators: [{
283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXSessionStorageService, decorators: [{
284
284
  type: Injectable
285
285
  }] });
286
286
 
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, Directive, Pipe, provideAppInitializer, NgModule } from '@angular/core';
3
- import { of, shareReplay, firstValueFrom, BehaviorSubject, switchMap, from } from 'rxjs';
4
- import { get } from 'lodash-es';
5
- import { AXLocaleService } from '@acorex/core/locale';
6
- import { waitFor } from '@acorex/core/utils';
3
+ import { AXEventService } from '@acorex/core/events';
4
+ import { set, get } from 'lodash-es';
5
+ import { of, BehaviorSubject, forkJoin, tap, catchError, finalize, shareReplay, startWith, map, distinctUntilChanged, switchMap, firstValueFrom } from 'rxjs';
6
+ import { AXLocaleEvents, AXLocaleService } from '@acorex/core/locale';
7
7
 
8
8
  const AX_TRANSLATION_CONFIG = new InjectionToken('AX_TRANSLATION_CONFIG', {
9
9
  providedIn: 'root',
@@ -12,17 +12,9 @@ const AX_TRANSLATION_CONFIG = new InjectionToken('AX_TRANSLATION_CONFIG', {
12
12
  },
13
13
  });
14
14
  const AXTranslationDefaultConfig = {
15
- defaults: {
16
- lang: 'en-US',
17
- scope: 'common',
18
- },
19
- preload: {
20
- langs: ['en-US'],
21
- scopes: ['common'],
22
- },
23
- resolver: {
24
- scopeKey: 'scope',
25
- },
15
+ defaultLang: 'en-US',
16
+ defaultScope: 'common',
17
+ scopeResolverKey: 'scope',
26
18
  };
27
19
  function translationConfig(config = {}) {
28
20
  const result = {
@@ -33,7 +25,7 @@ function translationConfig(config = {}) {
33
25
  }
34
26
 
35
27
  class AXTranslationLoaderDefault {
36
- load(options) {
28
+ getTranslation(options) {
37
29
  return of({});
38
30
  }
39
31
  }
@@ -44,259 +36,232 @@ const AX_TRANSLATION_LOADER = new InjectionToken('AX_TRANSLATION_LOADER', {
44
36
  },
45
37
  });
46
38
 
47
- class AXTranslationLoaderService {
48
- constructor() {
49
- this.loader = inject(AX_TRANSLATION_LOADER);
50
- this.config = inject(AX_TRANSLATION_CONFIG);
51
- this.cache = new Map();
52
- this.inflight = new Map();
39
+ let singletonInstance;
40
+ function translateSync(key, options) {
41
+ return singletonInstance.translateSync(key, options);
42
+ }
43
+ class AXTranslationService {
44
+ getDefaultLang() {
45
+ return this.config.defaultLang;
53
46
  }
54
- load(lang, scope) {
55
- const key = `${lang}:${scope}`;
56
- console.log('load', key);
57
- if (this.cache.has(key)) {
58
- return of(this.cache.get(key));
59
- }
60
- if (!this.inflight.has(key)) {
61
- const obs = this.loader.load({ lang, scope }).pipe(shareReplay(1));
62
- this.inflight.set(key, obs);
63
- obs.subscribe({
64
- next: (data) => {
65
- this.cache.set(key, data);
66
- this.inflight.delete(key);
67
- },
68
- error: () => {
69
- this.inflight.delete(key);
70
- },
47
+ getActiveLang() {
48
+ return this.activeLang.getValue();
49
+ }
50
+ setActiveLang(lang) {
51
+ if (lang != this.getActiveLang()) {
52
+ this.activeLang.next(lang);
53
+ this.eventService.emitEvent({
54
+ type: AXLocaleEvents.AXLanguageChanged,
55
+ payload: lang,
71
56
  });
72
57
  }
73
- return this.inflight.get(key);
74
58
  }
75
- peek(lang, scope, key) {
76
- const cacheKey = `${lang}:${scope}`;
77
- const data = this.cache.get(cacheKey);
78
- return get(data, key, null);
79
- }
80
- async preload(options) {
81
- const langs = [...new Set(options?.langs ?? this.config.preload?.langs ?? [this.config.defaults.lang])];
82
- const scopes = [...new Set(options?.scopes ?? this.config.preload?.scopes ?? [this.config.defaults.scope])];
83
- const promises = [];
84
- langs.forEach(lang => {
85
- scopes.forEach(scope => {
86
- const p = firstValueFrom(this.load(lang, scope));
87
- promises.push(p);
88
- });
59
+ /**
60
+ * @ignore
61
+ */
62
+ constructor() {
63
+ this.loader = inject(AX_TRANSLATION_LOADER);
64
+ this.config = inject(AX_TRANSLATION_CONFIG);
65
+ this.eventService = inject(AXEventService);
66
+ this.localeService = inject(AXLocaleService);
67
+ this.translationCache = {};
68
+ this.ongoingRequests = new Map();
69
+ this.activeLang = new BehaviorSubject(this.getDefaultLang());
70
+ this.langChanges$ = this.activeLang.asObservable();
71
+ this.expressionCache = new Map();
72
+ this.isExpression = (value) => value.includes('t(');
73
+ singletonInstance = this;
74
+ //
75
+ this.localeService.profileChanged$.subscribe((locale) => {
76
+ if (locale?.localeInfo?.code) {
77
+ this.setActiveLang(locale.localeInfo.code);
78
+ }
89
79
  });
90
- await Promise.all(promises);
91
80
  }
92
- clear() {
93
- this.cache.clear();
94
- this.inflight.clear();
81
+ loadLanguagesAndScopes(languages, scopes) {
82
+ const requests = languages.flatMap((lang) => scopes.map((scope) => {
83
+ // Check if translations are already cached
84
+ if (this.translationCache[lang]?.[scope]) {
85
+ return of(this.translationCache[lang][scope]);
86
+ }
87
+ // Use the new method to handle ongoing requests and loading
88
+ return this.fetchTranslationFiles(lang, scope);
89
+ }));
90
+ return forkJoin(requests);
95
91
  }
96
- getFallbackLangs(lang) {
97
- return this.config.fallbacks?.langs?.[lang] ?? [];
92
+ fetchTranslationFiles(lang, scope) {
93
+ const requestKey = `${lang}_${scope}`;
94
+ // Return existing observable if the request is already in progress
95
+ if (this.ongoingRequests.has(requestKey)) {
96
+ return this.ongoingRequests.get(requestKey);
97
+ }
98
+ // Load translations if not in cache or ongoing requests
99
+ const translationObservable = this.loader.getTranslation({ lang, scope }).pipe(tap((translations) => this.setTranslationCache(lang, scope, translations)), catchError((error) => {
100
+ this.handleError(`Error loading translations for lang: ${lang}, scope: ${scope}`, error);
101
+ return of(null);
102
+ }), finalize(() => {
103
+ this.eventService.emitEvent({
104
+ type: AXLocaleEvents.AXLanguageLoaded,
105
+ payload: lang,
106
+ });
107
+ this.ongoingRequests.delete(requestKey);
108
+ }), shareReplay(1));
109
+ this.ongoingRequests.set(requestKey, translationObservable);
110
+ return translationObservable;
98
111
  }
99
- getFallbackScopes(scope) {
100
- return this.config.fallbacks?.scopes?.[scope] ?? [];
112
+ //#region Helpers Methods
113
+ /**
114
+ * Set translation data into cache
115
+ */
116
+ setTranslationCache(lang, scope, translations) {
117
+ set(this.translationCache, `${lang}.${scope}`, translations);
101
118
  }
102
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
103
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationLoaderService, providedIn: 'root' }); }
104
- }
105
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationLoaderService, decorators: [{
106
- type: Injectable,
107
- args: [{ providedIn: 'root' }]
108
- }] });
109
-
110
- class TranslationParserService {
111
- constructor() {
112
- this.legacyRegex = /t\(["']([^"']+)["']\s*(?:,\s*(\{.*?\}))?\)/g;
113
- this.inlineRegex = /@([a-zA-Z\-]+:)?([a-zA-Z0-9_-]+):([a-zA-Z0-9_.:-]+)(?=[\\s.,;!?)]|$)/g;
119
+ /**
120
+ * Get the translation from the cache or fallback to loading
121
+ */
122
+ getTranslationFromCache(lang, scope, key) {
123
+ return get(this.translationCache, `${lang}.${scope}.${key}`, key);
114
124
  }
115
- isExpression(value) {
116
- return typeof value === 'string' && (value.includes('t(') || value.includes('@'));
125
+ isLangAvailable(lang, scope = null) {
126
+ return !this.translationCache[lang] && (!scope || !this.translationCache[lang][scope]);
117
127
  }
118
- parse(expression, currentLang, currentScope) {
119
- const tokens = [];
120
- // Extract inline references like @lang:scope:key or @scope:key
121
- let inlineMatch;
122
- while ((inlineMatch = this.inlineRegex.exec(expression)) !== null) {
123
- const langPart = inlineMatch[1]?.replace(':', '') ?? null;
124
- const scope = inlineMatch[2] ?? currentScope;
125
- const key = inlineMatch[3];
126
- const fullMatch = inlineMatch[0];
127
- tokens.push({ lang: langPart || currentLang, scope, key, fullMatch });
128
- }
129
- // Extract legacy expressions
130
- let legacyMatch;
131
- while ((legacyMatch = this.legacyRegex.exec(expression)) !== null) {
132
- const key = legacyMatch[1];
133
- const rawOptions = legacyMatch[2];
134
- let scope = currentScope;
135
- let lang = currentLang;
136
- if (rawOptions) {
137
- try {
138
- const jsonString = rawOptions
139
- .replace(/(['"])?([a-zA-Z0-9_]+)(['"])?\s*:/g, '"$2":')
140
- .replace(/'/g, '"');
141
- const options = JSON.parse(jsonString);
142
- scope = options.scope || scope;
143
- lang = options.lang || lang;
144
- }
145
- catch (err) {
146
- console.error('Invalid translation expression options:', err);
147
- }
148
- }
149
- tokens.push({ key, scope, lang, fullMatch: legacyMatch[0] });
128
+ translateKey(key, lang, scope, params) {
129
+ // Trigger async preloading without blocking execution
130
+ this.loadLanguagesAndScopes([lang], [scope]).pipe(startWith()).subscribe({
131
+ error: (err) => {
132
+ this.handleError(`Error preloading translations for ${lang}, ${scope}`, err);
133
+ },
134
+ });
135
+ // Retrieve the translation from the cache or fallback to the key
136
+ let translation = this.getTranslationFromCache(lang, scope, key);
137
+ // Replace params like {{ name }}
138
+ if (params && typeof translation === 'string') {
139
+ Object.keys(params).forEach((paramKey) => {
140
+ translation = translation.replace(new RegExp(`{{\\s*${paramKey}\\s*}}`, 'g'), params[paramKey]);
141
+ });
150
142
  }
151
- return tokens;
143
+ return translation || key;
152
144
  }
153
- }
154
-
155
- class TranslationResolverService {
156
- constructor() {
157
- this.loaderService = inject(AXTranslationLoaderService);
158
- this.cache = new Map();
159
- this.inflight = new Map();
160
- this.parser = new TranslationParserService();
161
- }
162
- async resolve(lang, scope, key, params) {
163
- const cacheKey = `${lang}:${scope}`;
164
- // Load and cache if not present
165
- if (!this.cache.has(cacheKey)) {
166
- if (!this.inflight.has(cacheKey)) {
167
- const fetchPromise = firstValueFrom(this.loaderService.load(lang, scope));
168
- this.inflight.set(cacheKey, fetchPromise);
169
- const data = await fetchPromise;
170
- this.cache.set(cacheKey, data);
171
- this.inflight.delete(cacheKey);
145
+ decodeExpression(expression) {
146
+ if (this.expressionCache.has(expression)) {
147
+ return this.expressionCache.get(expression);
148
+ }
149
+ const regex = /t\(["']([^"']+)["']\s*(?:,\s*(\{.*?\}))?\)/g;
150
+ const matches = [];
151
+ let match;
152
+ while ((match = regex.exec(expression)) !== null) {
153
+ const key = match[1];
154
+ const rawOptions = match[2];
155
+ if (!rawOptions) {
156
+ matches.push({ key, scope: null, lang: null, fullMatch: match[0] });
157
+ continue;
172
158
  }
173
- else {
174
- await this.inflight.get(cacheKey); // wait for the inflight fetch
159
+ try {
160
+ const jsonString = rawOptions
161
+ .replace(/(['"])?([a-zA-Z0-9_]+)(['"])?\s*:/g, '"$2":') // Ensure keys are quoted
162
+ .replace(/'/g, '"'); // Replace single quotes with double quotes
163
+ const options = JSON.parse(jsonString);
164
+ matches.push({
165
+ key,
166
+ scope: options.scope || null,
167
+ lang: options.lang || null,
168
+ fullMatch: match[0],
169
+ });
175
170
  }
176
- }
177
- const translations = this.cache.get(cacheKey);
178
- let result = get(translations, key, key);
179
- // Recursively resolve references
180
- if (typeof result === 'string' && this.parser.isExpression(result)) {
181
- const tokens = this.parser.parse(result, lang, scope);
182
- if (tokens.length > 0) {
183
- const first = tokens[0];
184
- result = await this.resolve(first.lang || lang, first.scope || scope, first.key, params);
171
+ catch (error) {
172
+ this.handleError(`Failed to parse options for key "${key}":`, error);
173
+ matches.push({ key, scope: null, lang: null, fullMatch: match[0] });
185
174
  }
186
175
  }
187
- return this.format(result, params);
176
+ this.expressionCache.set(expression, matches);
177
+ return matches;
188
178
  }
189
- format(text, params) {
190
- if (!params || typeof text !== 'string')
191
- return text;
192
- return Object.keys(params).reduce((out, key) => {
193
- const re = new RegExp(`{{\s*${key}\s*}}`, 'g');
194
- return out.replace(re, params[key]);
195
- }, text);
196
- }
197
- }
198
-
199
- let singletonInstance;
200
- function translateSync(key, options) {
201
- return singletonInstance.translateSync(key, options);
202
- }
203
- class AXTranslationService {
204
- constructor() {
205
- this.localeService = inject(AXLocaleService);
206
- this.config = inject(AX_TRANSLATION_CONFIG);
207
- this.loader = inject(AXTranslationLoaderService);
208
- this.parser = new TranslationParserService();
209
- this.resolver = new TranslationResolverService();
210
- this.activeLang = new BehaviorSubject(this.config.defaults.lang);
211
- this.langChanges$ = this.activeLang.asObservable();
212
- singletonInstance = this;
213
- this.localeService.profileChanged$.subscribe((locale) => {
214
- if (locale?.localeInfo?.code) {
215
- this.setActiveLang(locale.localeInfo.code);
216
- }
179
+ handleError(message, error) {
180
+ console.error(message, error);
181
+ this.eventService.emitEvent({
182
+ type: 'error',
183
+ payload: { message, error },
217
184
  });
218
185
  }
219
- setActiveLang(lang) {
220
- if (lang && lang !== this.activeLang.getValue()) {
221
- this.activeLang.next(lang);
186
+ //#endregion
187
+ //#region Async Translation Methods
188
+ translateText(text, contextLang, contextScope, params) {
189
+ if (!this.isExpression(text)) {
190
+ return this.loadLanguagesAndScopes([contextLang], [contextScope]).pipe(map(() => this.translateKey(text, contextLang, contextScope, params)), catchError((error) => {
191
+ this.handleError(`Error during translation:`, error);
192
+ return of(text); // Fallback to the original text
193
+ }));
222
194
  }
223
- }
224
- getActiveLang() {
225
- return this.activeLang.getValue();
226
- }
227
- getDefaultLang() {
228
- return this.config.defaults.lang;
195
+ const matches = this.decodeExpression(text);
196
+ // Extract unique languages and scopes for batch loading
197
+ const langScopeSet = new Set(matches.map(({ lang, scope }) => `${lang || contextLang}_${scope || contextScope}`));
198
+ const langs = Array.from(new Set(Array.from(langScopeSet).map((pair) => pair.split('_')[0])));
199
+ const scopes = Array.from(new Set(Array.from(langScopeSet).map((pair) => pair.split('_')[1])));
200
+ // Load all required languages and scopes
201
+ return this.loadLanguagesAndScopes(langs, scopes).pipe(map(() => {
202
+ // Resolve translations after loading
203
+ const translations = matches.reduce((acc, { key, scope, lang, fullMatch }) => {
204
+ const resolvedScope = scope || contextScope;
205
+ const resolvedLang = lang || contextLang;
206
+ acc[fullMatch] = this.translateKey(key, resolvedLang, resolvedScope, params);
207
+ return acc;
208
+ }, {});
209
+ // Replace all matches in the text with their resolved translations
210
+ return matches.reduce((result, { fullMatch }) => result.replace(new RegExp(fullMatch.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), translations[fullMatch]), text);
211
+ }), catchError((error) => {
212
+ this.handleError(`Error during translation:`, error);
213
+ return of(text); // Fallback to the original text
214
+ }));
229
215
  }
230
216
  translate$(text, options) {
231
- if (!text)
232
- return of('');
233
- const staticOptions = { ...options };
234
- const staticScope = staticOptions.scope ?? this.config.defaults.scope;
235
- return this.langChanges$.pipe(switchMap(lang => {
236
- return from(this.translateAsync(text, {
237
- ...staticOptions,
238
- lang: staticOptions.lang ?? lang,
239
- scope: staticScope
240
- }));
217
+ if (options?.lang) {
218
+ return this.translateText(text, options.lang, options?.scope ?? this.config.defaultScope, options?.params);
219
+ }
220
+ return this.langChanges$.pipe(startWith(this.getActiveLang()), distinctUntilChanged(), switchMap((lang) => {
221
+ return this.translateText(text, lang, options?.scope ?? this.config.defaultScope, options?.params);
241
222
  }));
242
223
  }
243
224
  async translateAsync(text, options) {
244
- const lang = options?.lang ?? this.getActiveLang() ?? this.config.defaults.lang;
245
- const scope = options?.scope ?? this.config.defaults.scope;
246
- const params = options?.params;
247
- if (!this.parser.isExpression(text)) {
248
- const resolved = await this.resolver.resolve(lang, scope, text, params);
249
- return this.resolver.format(resolved, params);
250
- }
251
- const tokens = this.parser.parse(text, lang, scope);
252
- const results = await Promise.all(tokens.map(token => this.resolver.resolve(token.lang ?? lang, token.scope ?? scope, token.key, params)
253
- .then(translated => [token.fullMatch, this.resolver.format(translated, params)])));
254
- return results.reduce((output, [match, translated]) => output.replace(match, translated), text);
225
+ return firstValueFrom(this.translate$(text, options));
255
226
  }
227
+ //#endregion
228
+ //#region Sync Translation Methods
256
229
  translateSync(text, options) {
257
- const lang = options?.lang ?? this.getActiveLang() ?? this.config.defaults.lang;
258
- const scope = options?.scope ?? this.config.defaults.scope;
259
- const params = options?.params;
260
- const waitForLoad = options?.waitForLoad ?? false;
261
- const timeoutMs = options?.timeoutMs ?? 100;
262
- if (!this.parser.isExpression(text)) {
263
- let translated = this.loader.peek(lang, scope, text);
264
- if (!translated && waitForLoad) {
265
- translated = waitFor(() => this.loader.peek(lang, scope, text), () => this.loader.load(lang, scope).subscribe(), timeoutMs);
266
- }
267
- return this.resolver.format(translated ?? text, params);
230
+ if (this.isExpression(text)) {
231
+ return this.translateTextSync(text, options?.lang ?? this.getActiveLang(), options?.scope ?? this.config.defaultScope);
268
232
  }
269
- const tokens = this.parser.parse(text, lang, scope);
270
- let result = text;
271
- for (const token of tokens) {
272
- const targetLang = token.lang ?? lang;
273
- const targetScope = token.scope ?? scope;
274
- const key = token.key;
275
- let translation = this.loader.peek(targetLang, targetScope, key);
276
- if (!translation) {
277
- translation = waitFor(() => this.loader.peek(targetLang, targetScope, key), () => this.loader.load(targetLang, targetScope).subscribe(), waitForLoad ? timeoutMs : 0) ?? key;
278
- }
279
- result = result.replace(token.fullMatch, this.resolver.format(translation, params));
233
+ else {
234
+ return this.translateKey(text, options?.lang ?? this.getActiveLang(), options?.scope ?? this.config.defaultScope);
280
235
  }
281
- return result;
282
236
  }
283
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
284
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, providedIn: 'root' }); }
237
+ translateTextSync(text, contextLang, contextScope, params) {
238
+ const matches = this.decodeExpression(text);
239
+ const translations = matches.reduce((acc, { key, scope, lang, fullMatch }) => {
240
+ const resolvedScope = scope || contextScope;
241
+ const resolvedLang = lang || contextLang;
242
+ // Cache translation to avoid redundant processing
243
+ if (!acc[fullMatch]) {
244
+ acc[fullMatch] = this.translateKey(key, resolvedLang, resolvedScope, params);
245
+ }
246
+ return acc;
247
+ }, {});
248
+ // Replace all matches in one go
249
+ return matches.reduce((result, { fullMatch }) => result.replace(fullMatch, translations[fullMatch]), text);
250
+ }
251
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
252
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationService, providedIn: 'root' }); }
285
253
  }
286
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, decorators: [{
254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationService, decorators: [{
287
255
  type: Injectable,
288
- args: [{
289
- providedIn: 'root'
290
- }]
256
+ args: [{ providedIn: 'root' }]
291
257
  }], ctorParameters: () => [] });
292
258
 
293
259
  const loadTranslationScope = (route, state) => {
294
- const translationLoaderService = inject(AXTranslationLoaderService);
295
- const translationService = inject(AXTranslationService);
260
+ const translatorService = inject(AXTranslationService);
296
261
  const config = inject(AX_TRANSLATION_CONFIG);
297
- const scopeValue = route.data[config.resolver?.scopeKey];
262
+ const scopeValue = route.data[config.scopeResolverKey];
298
263
  const scopes = Array.isArray(scopeValue) ? scopeValue : [scopeValue];
299
- return translationLoaderService.preload({ langs: [translationService.getActiveLang()], scopes });
264
+ return translatorService.loadLanguagesAndScopes([translatorService.getActiveLang()], scopes);
300
265
  };
301
266
 
302
267
  class AXTranslatorDirective {
@@ -313,17 +278,17 @@ class AXTranslatorDirective {
313
278
  },
314
279
  });
315
280
  }
316
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: AXTranslationService }], target: i0.ɵɵFactoryTarget.Directive }); }
317
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: AXTranslatorDirective, isStandalone: true, selector: "[translate]", ngImport: i0 }); }
281
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslatorDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: AXTranslationService }], target: i0.ɵɵFactoryTarget.Directive }); }
282
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.9", type: AXTranslatorDirective, isStandalone: true, selector: "[translate]", ngImport: i0 }); }
318
283
  }
319
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorDirective, decorators: [{
284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslatorDirective, decorators: [{
320
285
  type: Directive,
321
286
  args: [{ selector: '[translate]' }]
322
287
  }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: AXTranslationService }] });
323
288
 
324
289
  class AXTranslatorPipe {
325
- constructor() {
326
- this.service = inject(AXTranslationService);
290
+ constructor(service) {
291
+ this.service = service;
327
292
  }
328
293
  transform(key, options) {
329
294
  if (!key) {
@@ -331,40 +296,40 @@ class AXTranslatorPipe {
331
296
  }
332
297
  return this.service.translate$(key, options);
333
298
  }
334
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
335
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, isStandalone: true, name: "translate" }); }
299
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslatorPipe, deps: [{ token: AXTranslationService }], target: i0.ɵɵFactoryTarget.Pipe }); }
300
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.9", ngImport: i0, type: AXTranslatorPipe, isStandalone: true, name: "translate" }); }
336
301
  }
337
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, decorators: [{
302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslatorPipe, decorators: [{
338
303
  type: Pipe,
339
304
  args: [{
340
305
  name: 'translate',
341
306
  pure: true,
342
307
  }]
343
- }] });
308
+ }], ctorParameters: () => [{ type: AXTranslationService }] });
344
309
 
345
- function initializeApp(translatorService) {
310
+ function initializeApp(translatorService, config) {
346
311
  return () => {
347
- return translatorService.preload();
312
+ return translatorService.loadLanguagesAndScopes(config.preloadLangs ?? [config.defaultLang], config.preloadScopes ?? [config.defaultScope]);
348
313
  };
349
314
  }
350
315
  class AXTranslationModule {
351
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
352
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, imports: [AXTranslatorPipe, AXTranslatorDirective], exports: [AXTranslatorPipe, AXTranslatorDirective] }); }
353
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, providers: [
316
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
317
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationModule, imports: [AXTranslatorPipe, AXTranslatorDirective], exports: [AXTranslatorPipe, AXTranslatorDirective] }); }
318
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationModule, providers: [
354
319
  provideAppInitializer(() => {
355
- const initializerFn = initializeApp(inject(AXTranslationLoaderService));
320
+ const initializerFn = initializeApp(inject(AXTranslationService), inject(AX_TRANSLATION_CONFIG));
356
321
  return initializerFn();
357
322
  }),
358
323
  ] }); }
359
324
  }
360
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, decorators: [{
325
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXTranslationModule, decorators: [{
361
326
  type: NgModule,
362
327
  args: [{
363
328
  imports: [AXTranslatorPipe, AXTranslatorDirective],
364
329
  exports: [AXTranslatorPipe, AXTranslatorDirective],
365
330
  providers: [
366
331
  provideAppInitializer(() => {
367
- const initializerFn = initializeApp(inject(AXTranslationLoaderService));
332
+ const initializerFn = initializeApp(inject(AXTranslationService), inject(AX_TRANSLATION_CONFIG));
368
333
  return initializerFn();
369
334
  }),
370
335
  ],