@acorex/core 19.13.0-next.0 → 19.13.0-next.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 (37) hide show
  1. package/fesm2022/acorex-core-components.mjs +3 -3
  2. package/fesm2022/acorex-core-config.mjs +3 -3
  3. package/fesm2022/acorex-core-date-time.mjs +25 -25
  4. package/fesm2022/acorex-core-events.mjs +3 -3
  5. package/fesm2022/acorex-core-file.mjs +10 -10
  6. package/fesm2022/acorex-core-format.mjs +36 -35
  7. package/fesm2022/acorex-core-format.mjs.map +1 -1
  8. package/fesm2022/acorex-core-image.mjs +3 -3
  9. package/fesm2022/acorex-core-locale.mjs +10 -10
  10. package/fesm2022/acorex-core-network.mjs +3 -3
  11. package/fesm2022/acorex-core-pipes.mjs +3 -3
  12. package/fesm2022/acorex-core-platform.mjs +3 -3
  13. package/fesm2022/acorex-core-storage.mjs +9 -9
  14. package/fesm2022/acorex-core-translation.mjs +245 -213
  15. package/fesm2022/acorex-core-translation.mjs.map +1 -1
  16. package/fesm2022/acorex-core-utils.mjs +143 -18
  17. package/fesm2022/acorex-core-utils.mjs.map +1 -1
  18. package/fesm2022/acorex-core-validation.mjs +40 -40
  19. package/format/lib/format.service.d.ts +8 -7
  20. package/package.json +1 -1
  21. package/translation/index.d.ts +1 -1
  22. package/translation/lib/translation-loader.service.d.ts +20 -0
  23. package/translation/lib/translation.config.d.ts +19 -6
  24. package/translation/lib/translation.loader.d.ts +2 -2
  25. package/translation/lib/translation.parser.d.ts +7 -0
  26. package/translation/lib/translation.resolver.d.ts +9 -0
  27. package/translation/lib/translation.service.d.ts +12 -32
  28. package/translation/lib/translation.types.d.ts +8 -2
  29. package/translation/lib/translator.pipe.d.ts +0 -2
  30. package/utils/index.d.ts +6 -5
  31. package/utils/lib/execution.utils.d.ts +33 -0
  32. package/utils/lib/string.utils.d.ts +4 -0
  33. package/utils/lib/string-util.d.ts +0 -6
  34. /package/utils/lib/{color-util.d.ts → color.utils.d.ts} +0 -0
  35. /package/utils/lib/{drawing-util.d.ts → drawing.utils.d.ts} +0 -0
  36. /package/utils/lib/{html-util.d.ts → html-utils.d.ts} +0 -0
  37. /package/utils/lib/{auto-unsubscribe.d.ts → lifecycle-helpers.utils.d.ts} +0 -0
@@ -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.5", 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.5", ngImport: i0, type: AXCookieStorageService }); }
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 }); }
189
189
  }
190
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXCookieStorageService, decorators: [{
190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", 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.5", 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.5", ngImport: i0, type: AXLocalStorageService }); }
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 }); }
247
247
  }
248
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXLocalStorageService, decorators: [{
248
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", 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.5", ngImport: i0, type: AXSessionStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
281
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXSessionStorageService }); }
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 }); }
282
282
  }
283
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXSessionStorageService, decorators: [{
283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", 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 { 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';
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';
7
7
 
8
8
  const AX_TRANSLATION_CONFIG = new InjectionToken('AX_TRANSLATION_CONFIG', {
9
9
  providedIn: 'root',
@@ -12,9 +12,17 @@ const AX_TRANSLATION_CONFIG = new InjectionToken('AX_TRANSLATION_CONFIG', {
12
12
  },
13
13
  });
14
14
  const AXTranslationDefaultConfig = {
15
- defaultLang: 'en-US',
16
- defaultScope: 'common',
17
- scopeResolverKey: 'scope',
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
+ },
18
26
  };
19
27
  function translationConfig(config = {}) {
20
28
  const result = {
@@ -25,7 +33,7 @@ function translationConfig(config = {}) {
25
33
  }
26
34
 
27
35
  class AXTranslationLoaderDefault {
28
- getTranslation(options) {
36
+ load(options) {
29
37
  return of({});
30
38
  }
31
39
  }
@@ -36,232 +44,256 @@ const AX_TRANSLATION_LOADER = new InjectionToken('AX_TRANSLATION_LOADER', {
36
44
  },
37
45
  });
38
46
 
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;
46
- }
47
- getActiveLang() {
48
- return this.activeLang.getValue();
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();
49
53
  }
50
- setActiveLang(lang) {
51
- if (lang != this.getActiveLang()) {
52
- this.activeLang.next(lang);
53
- this.eventService.emitEvent({
54
- type: AXLocaleEvents.AXLanguageChanged,
55
- payload: lang,
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
+ },
56
71
  });
57
72
  }
73
+ return this.inflight.get(key);
58
74
  }
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
- }
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
+ });
79
89
  });
90
+ await Promise.all(promises);
80
91
  }
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);
92
+ clear() {
93
+ this.cache.clear();
94
+ this.inflight.clear();
91
95
  }
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;
96
+ getFallbackLangs(lang) {
97
+ return this.config.fallbacks?.langs?.[lang] ?? [];
111
98
  }
112
- //#region Helpers Methods
113
- /**
114
- * Set translation data into cache
115
- */
116
- setTranslationCache(lang, scope, translations) {
117
- set(this.translationCache, `${lang}.${scope}`, translations);
99
+ getFallbackScopes(scope) {
100
+ return this.config.fallbacks?.scopes?.[scope] ?? [];
118
101
  }
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);
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;
124
114
  }
125
- isLangAvailable(lang, scope = null) {
126
- return !this.translationCache[lang] && (!scope || !this.translationCache[lang][scope]);
115
+ isExpression(value) {
116
+ return typeof value === 'string' && (value.includes('t(') || value.includes('@'));
127
117
  }
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
- });
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 });
142
128
  }
143
- return translation || key;
144
- }
145
- decodeExpression(expression) {
146
- if (this.expressionCache.has(expression)) {
147
- return this.expressionCache.get(expression);
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] });
148
150
  }
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;
151
+ return tokens;
152
+ }
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);
158
172
  }
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
- });
173
+ else {
174
+ await this.inflight.get(cacheKey); // wait for the inflight fetch
170
175
  }
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] });
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);
174
185
  }
175
186
  }
176
- this.expressionCache.set(expression, matches);
177
- return matches;
187
+ return this.format(result, params);
188
+ }
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);
178
196
  }
179
- handleError(message, error) {
180
- console.error(message, error);
181
- this.eventService.emitEvent({
182
- type: 'error',
183
- payload: { message, error },
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('en-US');
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
+ }
184
217
  });
185
218
  }
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
- }));
219
+ setActiveLang(lang) {
220
+ if (lang && lang !== this.activeLang.getValue()) {
221
+ this.activeLang.next(lang);
194
222
  }
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
- }));
223
+ }
224
+ getActiveLang() {
225
+ return this.activeLang.getValue();
215
226
  }
216
227
  translate$(text, options) {
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);
228
+ if (!text)
229
+ return of('');
230
+ const staticOptions = { ...options };
231
+ const staticScope = staticOptions.scope ?? this.config.defaults.scope;
232
+ return this.langChanges$.pipe(switchMap(lang => {
233
+ return from(this.translateAsync(text, {
234
+ ...staticOptions,
235
+ lang: staticOptions.lang ?? lang,
236
+ scope: staticScope
237
+ }));
222
238
  }));
223
239
  }
224
240
  async translateAsync(text, options) {
225
- return firstValueFrom(this.translate$(text, options));
241
+ const lang = options?.lang ?? this.getActiveLang() ?? this.config.defaults.lang;
242
+ const scope = options?.scope ?? this.config.defaults.scope;
243
+ const params = options?.params;
244
+ if (!this.parser.isExpression(text)) {
245
+ const resolved = await this.resolver.resolve(lang, scope, text, params);
246
+ return this.resolver.format(resolved, params);
247
+ }
248
+ const tokens = this.parser.parse(text, lang, scope);
249
+ const results = await Promise.all(tokens.map(token => this.resolver.resolve(token.lang ?? lang, token.scope ?? scope, token.key, params)
250
+ .then(translated => [token.fullMatch, this.resolver.format(translated, params)])));
251
+ return results.reduce((output, [match, translated]) => output.replace(match, translated), text);
226
252
  }
227
- //#endregion
228
- //#region Sync Translation Methods
229
253
  translateSync(text, options) {
230
- if (this.isExpression(text)) {
231
- return this.translateTextSync(text, options?.lang ?? this.getActiveLang(), options?.scope ?? this.config.defaultScope);
232
- }
233
- else {
234
- return this.translateKey(text, options?.lang ?? this.getActiveLang(), options?.scope ?? this.config.defaultScope);
254
+ const lang = options?.lang ?? this.getActiveLang() ?? this.config.defaults.lang;
255
+ const scope = options?.scope ?? this.config.defaults.scope;
256
+ const params = options?.params;
257
+ const waitForLoad = options?.waitForLoad ?? false;
258
+ const timeoutMs = options?.timeoutMs ?? 100;
259
+ if (!this.parser.isExpression(text)) {
260
+ let translated = this.loader.peek(lang, scope, text);
261
+ if (!translated && waitForLoad) {
262
+ translated = waitFor(() => this.loader.peek(lang, scope, text), () => this.loader.load(lang, scope).subscribe(), timeoutMs);
263
+ }
264
+ return this.resolver.format(translated ?? text, params);
235
265
  }
236
- }
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);
266
+ const tokens = this.parser.parse(text, lang, scope);
267
+ let result = text;
268
+ for (const token of tokens) {
269
+ const targetLang = token.lang ?? lang;
270
+ const targetScope = token.scope ?? scope;
271
+ const key = token.key;
272
+ let translation = this.loader.peek(targetLang, targetScope, key);
273
+ if (!translation) {
274
+ translation = waitFor(() => this.loader.peek(targetLang, targetScope, key), () => this.loader.load(targetLang, targetScope).subscribe(), waitForLoad ? timeoutMs : 0) ?? key;
245
275
  }
246
- return acc;
247
- }, {});
248
- // Replace all matches in one go
249
- return matches.reduce((result, { fullMatch }) => result.replace(fullMatch, translations[fullMatch]), text);
276
+ result = result.replace(token.fullMatch, this.resolver.format(translation, params));
277
+ }
278
+ return result;
250
279
  }
251
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
252
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationService, providedIn: 'root' }); }
280
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
281
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, providedIn: 'root' }); }
253
282
  }
254
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationService, decorators: [{
283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationService, decorators: [{
255
284
  type: Injectable,
256
- args: [{ providedIn: 'root' }]
285
+ args: [{
286
+ providedIn: 'root'
287
+ }]
257
288
  }], ctorParameters: () => [] });
258
289
 
259
290
  const loadTranslationScope = (route, state) => {
260
- const translatorService = inject(AXTranslationService);
291
+ const translationLoaderService = inject(AXTranslationLoaderService);
292
+ const translationService = inject(AXTranslationService);
261
293
  const config = inject(AX_TRANSLATION_CONFIG);
262
- const scopeValue = route.data[config.scopeResolverKey];
294
+ const scopeValue = route.data[config.resolver?.scopeKey];
263
295
  const scopes = Array.isArray(scopeValue) ? scopeValue : [scopeValue];
264
- return translatorService.loadLanguagesAndScopes([translatorService.getActiveLang()], scopes);
296
+ return translationLoaderService.preload({ langs: [translationService.getActiveLang()], scopes });
265
297
  };
266
298
 
267
299
  class AXTranslatorDirective {
@@ -278,17 +310,17 @@ class AXTranslatorDirective {
278
310
  },
279
311
  });
280
312
  }
281
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", 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.5", type: AXTranslatorDirective, isStandalone: true, selector: "[translate]", ngImport: i0 }); }
313
+ 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 }); }
314
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: AXTranslatorDirective, isStandalone: true, selector: "[translate]", ngImport: i0 }); }
283
315
  }
284
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslatorDirective, decorators: [{
316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorDirective, decorators: [{
285
317
  type: Directive,
286
318
  args: [{ selector: '[translate]' }]
287
319
  }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: AXTranslationService }] });
288
320
 
289
321
  class AXTranslatorPipe {
290
- constructor(service) {
291
- this.service = service;
322
+ constructor() {
323
+ this.service = inject(AXTranslationService);
292
324
  }
293
325
  transform(key, options) {
294
326
  if (!key) {
@@ -296,40 +328,40 @@ class AXTranslatorPipe {
296
328
  }
297
329
  return this.service.translate$(key, options);
298
330
  }
299
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslatorPipe, deps: [{ token: AXTranslationService }], target: i0.ɵɵFactoryTarget.Pipe }); }
300
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.5", ngImport: i0, type: AXTranslatorPipe, isStandalone: true, name: "translate" }); }
331
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
332
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, isStandalone: true, name: "translate" }); }
301
333
  }
302
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslatorPipe, decorators: [{
334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslatorPipe, decorators: [{
303
335
  type: Pipe,
304
336
  args: [{
305
337
  name: 'translate',
306
338
  pure: true,
307
339
  }]
308
- }], ctorParameters: () => [{ type: AXTranslationService }] });
340
+ }] });
309
341
 
310
- function initializeApp(translatorService, config) {
342
+ function initializeApp(translatorService) {
311
343
  return () => {
312
- return translatorService.loadLanguagesAndScopes(config.preloadLangs ?? [config.defaultLang], config.preloadScopes ?? [config.defaultScope]);
344
+ return translatorService.preload();
313
345
  };
314
346
  }
315
347
  class AXTranslationModule {
316
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
317
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationModule, imports: [AXTranslatorPipe, AXTranslatorDirective], exports: [AXTranslatorPipe, AXTranslatorDirective] }); }
318
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationModule, providers: [
348
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
349
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, imports: [AXTranslatorPipe, AXTranslatorDirective], exports: [AXTranslatorPipe, AXTranslatorDirective] }); }
350
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, providers: [
319
351
  provideAppInitializer(() => {
320
- const initializerFn = initializeApp(inject(AXTranslationService), inject(AX_TRANSLATION_CONFIG));
352
+ const initializerFn = initializeApp(inject(AXTranslationLoaderService));
321
353
  return initializerFn();
322
354
  }),
323
355
  ] }); }
324
356
  }
325
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: AXTranslationModule, decorators: [{
357
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AXTranslationModule, decorators: [{
326
358
  type: NgModule,
327
359
  args: [{
328
360
  imports: [AXTranslatorPipe, AXTranslatorDirective],
329
361
  exports: [AXTranslatorPipe, AXTranslatorDirective],
330
362
  providers: [
331
363
  provideAppInitializer(() => {
332
- const initializerFn = initializeApp(inject(AXTranslationService), inject(AX_TRANSLATION_CONFIG));
364
+ const initializerFn = initializeApp(inject(AXTranslationLoaderService));
333
365
  return initializerFn();
334
366
  }),
335
367
  ],