@jsverse/transloco 7.0.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.
- package/CHANGELOG.md +1005 -0
- package/LICENSE +22 -0
- package/README.md +44 -0
- package/esm2022/index.mjs +20 -0
- package/esm2022/jsverse-transloco.mjs +5 -0
- package/esm2022/lib/browser-lang.mjs +28 -0
- package/esm2022/lib/get-fallbacks-loaders.mjs +13 -0
- package/esm2022/lib/helpers.mjs +104 -0
- package/esm2022/lib/lang-resolver.mjs +54 -0
- package/esm2022/lib/loader-component.component.mjs +21 -0
- package/esm2022/lib/resolve-loader.mjs +13 -0
- package/esm2022/lib/scope-resolver.mjs +24 -0
- package/esm2022/lib/shared.mjs +73 -0
- package/esm2022/lib/template-handler.mjs +28 -0
- package/esm2022/lib/transloco-fallback-strategy.mjs +26 -0
- package/esm2022/lib/transloco-lang.mjs +3 -0
- package/esm2022/lib/transloco-loading-template.mjs +3 -0
- package/esm2022/lib/transloco-missing-handler.mjs +18 -0
- package/esm2022/lib/transloco-scope.mjs +3 -0
- package/esm2022/lib/transloco-testing.module.mjs +76 -0
- package/esm2022/lib/transloco.config.mjs +37 -0
- package/esm2022/lib/transloco.directive.mjs +169 -0
- package/esm2022/lib/transloco.interceptor.mjs +17 -0
- package/esm2022/lib/transloco.loader.mjs +13 -0
- package/esm2022/lib/transloco.module.mjs +18 -0
- package/esm2022/lib/transloco.pipe.mjs +97 -0
- package/esm2022/lib/transloco.providers.mjs +92 -0
- package/esm2022/lib/transloco.service.mjs +579 -0
- package/esm2022/lib/transloco.transpiler.mjs +145 -0
- package/esm2022/lib/types.mjs +2 -0
- package/fesm2022/jsverse-transloco.mjs +1580 -0
- package/fesm2022/jsverse-transloco.mjs.map +1 -0
- package/index.d.ts +19 -0
- package/lib/browser-lang.d.ts +8 -0
- package/lib/get-fallbacks-loaders.d.ts +14 -0
- package/lib/helpers.d.ts +21 -0
- package/lib/lang-resolver.d.ts +32 -0
- package/lib/loader-component.component.d.ts +6 -0
- package/lib/resolve-loader.d.ts +10 -0
- package/lib/scope-resolver.d.ts +12 -0
- package/lib/shared.d.ts +17 -0
- package/lib/template-handler.d.ts +9 -0
- package/lib/transloco-fallback-strategy.d.ts +14 -0
- package/lib/transloco-lang.d.ts +2 -0
- package/lib/transloco-loading-template.d.ts +3 -0
- package/lib/transloco-missing-handler.d.ts +16 -0
- package/lib/transloco-scope.d.ts +3 -0
- package/lib/transloco-testing.module.d.ts +27 -0
- package/lib/transloco.config.d.ts +27 -0
- package/lib/transloco.directive.d.ts +50 -0
- package/lib/transloco.interceptor.d.ts +14 -0
- package/lib/transloco.loader.d.ts +15 -0
- package/lib/transloco.module.d.ts +8 -0
- package/lib/transloco.pipe.d.ts +23 -0
- package/lib/transloco.providers.d.ts +30 -0
- package/lib/transloco.service.d.ts +199 -0
- package/lib/transloco.transpiler.d.ts +58 -0
- package/lib/types.d.ts +45 -0
- package/package.json +54 -0
- package/schematics/src/assets/i18n/en.json +1 -0
- package/schematics/src/collection.json +59 -0
- package/schematics/src/component/index.d.ts +2 -0
- package/schematics/src/component/index.js +21 -0
- package/schematics/src/component/index.js.map +1 -0
- package/schematics/src/join/index.d.ts +3 -0
- package/schematics/src/join/index.js +79 -0
- package/schematics/src/join/index.js.map +1 -0
- package/schematics/src/join/schema.d.ts +28 -0
- package/schematics/src/join/schema.js +3 -0
- package/schematics/src/join/schema.js.map +1 -0
- package/schematics/src/join/schema.json +43 -0
- package/schematics/src/keys-manager/index.d.ts +5 -0
- package/schematics/src/keys-manager/index.js +97 -0
- package/schematics/src/keys-manager/index.js.map +1 -0
- package/schematics/src/keys-manager/schema.d.ts +19 -0
- package/schematics/src/keys-manager/schema.js +3 -0
- package/schematics/src/keys-manager/schema.js.map +1 -0
- package/schematics/src/keys-manager/schema.json +33 -0
- package/schematics/src/migrate/index.d.ts +3 -0
- package/schematics/src/migrate/index.js +21 -0
- package/schematics/src/migrate/index.js.map +1 -0
- package/schematics/src/migrate/ngx-translate-migration.d.ts +3 -0
- package/schematics/src/migrate/ngx-translate-migration.js +169 -0
- package/schematics/src/migrate/ngx-translate-migration.js.map +1 -0
- package/schematics/src/migrate/schema.d.ts +10 -0
- package/schematics/src/migrate/schema.js +3 -0
- package/schematics/src/migrate/schema.js.map +1 -0
- package/schematics/src/migrate/schema.json +23 -0
- package/schematics/src/ng-add/files/transloco-loader/transloco-loader.__ts__ +12 -0
- package/schematics/src/ng-add/files/transloco-module/transloco-root.module.__ts__ +24 -0
- package/schematics/src/ng-add/generators/http-loader.gen.d.ts +4 -0
- package/schematics/src/ng-add/generators/http-loader.gen.js +15 -0
- package/schematics/src/ng-add/generators/http-loader.gen.js.map +1 -0
- package/schematics/src/ng-add/generators/root-module.gen.d.ts +9 -0
- package/schematics/src/ng-add/generators/root-module.gen.js +31 -0
- package/schematics/src/ng-add/generators/root-module.gen.js.map +1 -0
- package/schematics/src/ng-add/generators/translation-files.gen.d.ts +4 -0
- package/schematics/src/ng-add/generators/translation-files.gen.js +23 -0
- package/schematics/src/ng-add/generators/translation-files.gen.js.map +1 -0
- package/schematics/src/ng-add/index.d.ts +3 -0
- package/schematics/src/ng-add/index.js +103 -0
- package/schematics/src/ng-add/index.js.map +1 -0
- package/schematics/src/ng-add/schema.d.ts +42 -0
- package/schematics/src/ng-add/schema.js +14 -0
- package/schematics/src/ng-add/schema.js.map +1 -0
- package/schematics/src/ng-add/schema.json +53 -0
- package/schematics/src/ng-migrate/index.d.ts +3 -0
- package/schematics/src/ng-migrate/index.js +14 -0
- package/schematics/src/ng-migrate/index.js.map +1 -0
- package/schematics/src/ng-migrate/ng-migrate.d.ts +5 -0
- package/schematics/src/ng-migrate/ng-migrate.js +106 -0
- package/schematics/src/ng-migrate/ng-migrate.js.map +1 -0
- package/schematics/src/ng-migrate/schema.d.ts +14 -0
- package/schematics/src/ng-migrate/schema.js +3 -0
- package/schematics/src/ng-migrate/schema.js.map +1 -0
- package/schematics/src/ng-migrate/schema.json +27 -0
- package/schematics/src/schematics.consts.d.ts +4 -0
- package/schematics/src/schematics.consts.js +8 -0
- package/schematics/src/schematics.consts.js.map +1 -0
- package/schematics/src/scope/index.d.ts +3 -0
- package/schematics/src/scope/index.js +101 -0
- package/schematics/src/scope/index.js.map +1 -0
- package/schematics/src/scope/schema.d.ts +28 -0
- package/schematics/src/scope/schema.js +3 -0
- package/schematics/src/scope/schema.js.map +1 -0
- package/schematics/src/scope/schema.json +84 -0
- package/schematics/src/split/index.d.ts +3 -0
- package/schematics/src/split/index.js +66 -0
- package/schematics/src/split/index.js.map +1 -0
- package/schematics/src/split/schema.d.ts +20 -0
- package/schematics/src/split/schema.js +3 -0
- package/schematics/src/split/schema.js.map +1 -0
- package/schematics/src/split/schema.json +35 -0
- package/schematics/src/types.d.ts +5 -0
- package/schematics/src/types.js +10 -0
- package/schematics/src/types.js.map +1 -0
- package/schematics/src/upgrade/index.d.ts +3 -0
- package/schematics/src/upgrade/index.js +19 -0
- package/schematics/src/upgrade/index.js.map +1 -0
- package/schematics/src/upgrade/schema.d.ts +6 -0
- package/schematics/src/upgrade/schema.js +3 -0
- package/schematics/src/upgrade/schema.js.map +1 -0
- package/schematics/src/upgrade/schema.json +16 -0
- package/schematics/src/upgrade/v2.d.ts +1 -0
- package/schematics/src/upgrade/v2.js +89 -0
- package/schematics/src/upgrade/v2.js.map +1 -0
- package/schematics/src/utils/array.d.ts +3 -0
- package/schematics/src/utils/array.js +12 -0
- package/schematics/src/utils/array.js.map +1 -0
- package/schematics/src/utils/config.d.ts +2 -0
- package/schematics/src/utils/config.js +13 -0
- package/schematics/src/utils/config.js.map +1 -0
- package/schematics/src/utils/find-module.d.ts +23 -0
- package/schematics/src/utils/find-module.js +110 -0
- package/schematics/src/utils/find-module.js.map +1 -0
- package/schematics/src/utils/package.d.ts +5 -0
- package/schematics/src/utils/package.js +19 -0
- package/schematics/src/utils/package.js.map +1 -0
- package/schematics/src/utils/projects.d.ts +8 -0
- package/schematics/src/utils/projects.js +56 -0
- package/schematics/src/utils/projects.js.map +1 -0
- package/schematics/src/utils/translations.d.ts +7 -0
- package/schematics/src/utils/translations.js +31 -0
- package/schematics/src/utils/translations.js.map +1 -0
- package/schematics/src/utils/transloco.d.ts +24 -0
- package/schematics/src/utils/transloco.js +93 -0
- package/schematics/src/utils/transloco.js.map +1 -0
|
@@ -0,0 +1,1580 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, Injector, Inject, Optional, Component, Input, TemplateRef, ChangeDetectorRef, ElementRef, ViewContainerRef, Renderer2, Directive, Pipe, NgModule, makeEnvironmentProviders, APP_INITIALIZER } from '@angular/core';
|
|
3
|
+
import { of, take, from, map, Subject, BehaviorSubject, forkJoin, retry, tap, catchError, shareReplay, switchMap, combineLatest, EMPTY } from 'rxjs';
|
|
4
|
+
import { unflatten as unflatten$1, flatten as flatten$1 } from 'flat';
|
|
5
|
+
|
|
6
|
+
class DefaultLoader {
|
|
7
|
+
translations;
|
|
8
|
+
constructor(translations) {
|
|
9
|
+
this.translations = translations;
|
|
10
|
+
}
|
|
11
|
+
getTranslation(lang) {
|
|
12
|
+
return of(this.translations.get(lang) || {});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const TRANSLOCO_LOADER = new InjectionToken('TRANSLOCO_LOADER');
|
|
16
|
+
|
|
17
|
+
function getValue(obj, path) {
|
|
18
|
+
if (!obj) {
|
|
19
|
+
return obj;
|
|
20
|
+
}
|
|
21
|
+
/* For cases where the key is like: 'general.something.thing' */
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(obj, path)) {
|
|
23
|
+
return obj[path];
|
|
24
|
+
}
|
|
25
|
+
return path.split('.').reduce((p, c) => p?.[c], obj);
|
|
26
|
+
}
|
|
27
|
+
function setValue(obj, prop, val) {
|
|
28
|
+
obj = { ...obj };
|
|
29
|
+
const split = prop.split('.');
|
|
30
|
+
const lastIndex = split.length - 1;
|
|
31
|
+
split.reduce((acc, part, index) => {
|
|
32
|
+
if (index === lastIndex) {
|
|
33
|
+
acc[part] = val;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
acc[part] = Array.isArray(acc[part])
|
|
37
|
+
? acc[part].slice()
|
|
38
|
+
: { ...acc[part] };
|
|
39
|
+
}
|
|
40
|
+
return acc && acc[part];
|
|
41
|
+
}, obj);
|
|
42
|
+
return obj;
|
|
43
|
+
}
|
|
44
|
+
function size(collection) {
|
|
45
|
+
if (!collection) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(collection)) {
|
|
49
|
+
return collection.length;
|
|
50
|
+
}
|
|
51
|
+
if (isObject(collection)) {
|
|
52
|
+
return Object.keys(collection).length;
|
|
53
|
+
}
|
|
54
|
+
return collection ? collection.length : 0;
|
|
55
|
+
}
|
|
56
|
+
function isEmpty(collection) {
|
|
57
|
+
return size(collection) === 0;
|
|
58
|
+
}
|
|
59
|
+
function isFunction(val) {
|
|
60
|
+
return typeof val === 'function';
|
|
61
|
+
}
|
|
62
|
+
function isString(val) {
|
|
63
|
+
return typeof val === 'string';
|
|
64
|
+
}
|
|
65
|
+
function isNumber(val) {
|
|
66
|
+
return typeof val === 'number';
|
|
67
|
+
}
|
|
68
|
+
function isObject(item) {
|
|
69
|
+
return !!item && typeof item === 'object' && !Array.isArray(item);
|
|
70
|
+
}
|
|
71
|
+
function coerceArray(value) {
|
|
72
|
+
return Array.isArray(value) ? value : [value];
|
|
73
|
+
}
|
|
74
|
+
/*
|
|
75
|
+
* @example
|
|
76
|
+
*
|
|
77
|
+
* given: path-to-happiness => pathToHappiness
|
|
78
|
+
* given: path_to_happiness => pathToHappiness
|
|
79
|
+
* given: path-to_happiness => pathToHappiness
|
|
80
|
+
*
|
|
81
|
+
*/
|
|
82
|
+
function toCamelCase(str) {
|
|
83
|
+
return str
|
|
84
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => index == 0 ? word.toLowerCase() : word.toUpperCase())
|
|
85
|
+
.replace(/\s+|_|-|\//g, '');
|
|
86
|
+
}
|
|
87
|
+
function isBrowser() {
|
|
88
|
+
return typeof window !== 'undefined';
|
|
89
|
+
}
|
|
90
|
+
function isNil(value) {
|
|
91
|
+
return value === null || value === undefined;
|
|
92
|
+
}
|
|
93
|
+
function isDefined(value) {
|
|
94
|
+
return isNil(value) === false;
|
|
95
|
+
}
|
|
96
|
+
function toNumber(value) {
|
|
97
|
+
if (isNumber(value))
|
|
98
|
+
return value;
|
|
99
|
+
if (isString(value) && !isNaN(Number(value) - parseFloat(value))) {
|
|
100
|
+
return Number(value);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function isScopeObject(item) {
|
|
105
|
+
return item && typeof item.scope === 'string';
|
|
106
|
+
}
|
|
107
|
+
function isScopeArray(item) {
|
|
108
|
+
return Array.isArray(item) && item.every(isScopeObject);
|
|
109
|
+
}
|
|
110
|
+
function hasInlineLoader(item) {
|
|
111
|
+
return item && isObject(item.loader);
|
|
112
|
+
}
|
|
113
|
+
function unflatten(obj) {
|
|
114
|
+
return unflatten$1(obj);
|
|
115
|
+
}
|
|
116
|
+
function flatten(obj) {
|
|
117
|
+
return flatten$1(obj, { safe: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const TRANSLOCO_CONFIG = new InjectionToken('TRANSLOCO_CONFIG', {
|
|
121
|
+
providedIn: 'root',
|
|
122
|
+
factory: () => defaultConfig,
|
|
123
|
+
});
|
|
124
|
+
const defaultConfig = {
|
|
125
|
+
defaultLang: 'en',
|
|
126
|
+
reRenderOnLangChange: false,
|
|
127
|
+
prodMode: false,
|
|
128
|
+
failedRetries: 2,
|
|
129
|
+
fallbackLang: [],
|
|
130
|
+
availableLangs: [],
|
|
131
|
+
missingHandler: {
|
|
132
|
+
logMissingKey: true,
|
|
133
|
+
useFallbackTranslation: false,
|
|
134
|
+
allowEmpty: false,
|
|
135
|
+
},
|
|
136
|
+
flatten: {
|
|
137
|
+
aot: false,
|
|
138
|
+
},
|
|
139
|
+
interpolation: ['{{', '}}'],
|
|
140
|
+
};
|
|
141
|
+
function translocoConfig(config = {}) {
|
|
142
|
+
return {
|
|
143
|
+
...defaultConfig,
|
|
144
|
+
...config,
|
|
145
|
+
missingHandler: {
|
|
146
|
+
...defaultConfig.missingHandler,
|
|
147
|
+
...config.missingHandler,
|
|
148
|
+
},
|
|
149
|
+
flatten: {
|
|
150
|
+
...defaultConfig.flatten,
|
|
151
|
+
...config.flatten,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const TRANSLOCO_TRANSPILER = new InjectionToken('TRANSLOCO_TRANSPILER');
|
|
157
|
+
class DefaultTranspiler {
|
|
158
|
+
config = inject(TRANSLOCO_CONFIG, { optional: true }) ?? defaultConfig;
|
|
159
|
+
get interpolationMatcher() {
|
|
160
|
+
return resolveMatcher(this.config);
|
|
161
|
+
}
|
|
162
|
+
transpile({ value, params = {}, translation, key }) {
|
|
163
|
+
if (isString(value)) {
|
|
164
|
+
let paramMatch;
|
|
165
|
+
let parsedValue = value;
|
|
166
|
+
while ((paramMatch = this.interpolationMatcher.exec(parsedValue)) !== null) {
|
|
167
|
+
const [match, paramValue] = paramMatch;
|
|
168
|
+
parsedValue = parsedValue.replace(match, () => {
|
|
169
|
+
const match = paramValue.trim();
|
|
170
|
+
if (isDefined(params[match])) {
|
|
171
|
+
return params[match];
|
|
172
|
+
}
|
|
173
|
+
return isDefined(translation[match])
|
|
174
|
+
? this.transpile({
|
|
175
|
+
params, translation, key,
|
|
176
|
+
value: translation[match]
|
|
177
|
+
})
|
|
178
|
+
: '';
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return parsedValue;
|
|
182
|
+
}
|
|
183
|
+
else if (params) {
|
|
184
|
+
if (isObject(value)) {
|
|
185
|
+
value = this.handleObject({
|
|
186
|
+
value: value,
|
|
187
|
+
params,
|
|
188
|
+
translation,
|
|
189
|
+
key
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else if (Array.isArray(value)) {
|
|
193
|
+
value = this.handleArray({ value, params, translation, key });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
*
|
|
202
|
+
* const en = {
|
|
203
|
+
* a: {
|
|
204
|
+
* b: {
|
|
205
|
+
* c: "Hello {{ value }}"
|
|
206
|
+
* }
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
209
|
+
*
|
|
210
|
+
* const params = {
|
|
211
|
+
* "b.c": { value: "Transloco "}
|
|
212
|
+
* }
|
|
213
|
+
*
|
|
214
|
+
* service.selectTranslate('a', params);
|
|
215
|
+
*
|
|
216
|
+
* // the first param will be the result of `en.a`.
|
|
217
|
+
* // the second param will be `params`.
|
|
218
|
+
* parser.transpile(value, params, {});
|
|
219
|
+
*
|
|
220
|
+
*
|
|
221
|
+
*/
|
|
222
|
+
handleObject({ value, params = {}, translation, key }) {
|
|
223
|
+
let result = value;
|
|
224
|
+
Object.keys(params).forEach((p) => {
|
|
225
|
+
// transpile the value => "Hello Transloco"
|
|
226
|
+
const transpiled = this.transpile({
|
|
227
|
+
// get the value of "b.c" inside "a" => "Hello {{ value }}"
|
|
228
|
+
value: getValue(result, p),
|
|
229
|
+
// get the params of "b.c" => { value: "Transloco" }
|
|
230
|
+
params: getValue(params, p),
|
|
231
|
+
translation,
|
|
232
|
+
key
|
|
233
|
+
});
|
|
234
|
+
// set "b.c" to `transpiled`
|
|
235
|
+
result = setValue(result, p, transpiled);
|
|
236
|
+
});
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
handleArray({ value, ...rest }) {
|
|
240
|
+
return value.map((v) => this.transpile({
|
|
241
|
+
value: v,
|
|
242
|
+
...rest
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultTranspiler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
246
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultTranspiler });
|
|
247
|
+
}
|
|
248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultTranspiler, decorators: [{
|
|
249
|
+
type: Injectable
|
|
250
|
+
}] });
|
|
251
|
+
function resolveMatcher(config) {
|
|
252
|
+
const [start, end] = config.interpolation;
|
|
253
|
+
return new RegExp(`${start}([^${start}${end}]*?)${end}`, 'g');
|
|
254
|
+
}
|
|
255
|
+
function getFunctionArgs(argsString) {
|
|
256
|
+
const splitted = argsString ? argsString.split(',') : [];
|
|
257
|
+
const args = [];
|
|
258
|
+
for (let i = 0; i < splitted.length; i++) {
|
|
259
|
+
let value = splitted[i].trim();
|
|
260
|
+
while (value[value.length - 1] === '\\') {
|
|
261
|
+
i++;
|
|
262
|
+
value = value.replace('\\', ',') + splitted[i];
|
|
263
|
+
}
|
|
264
|
+
args.push(value);
|
|
265
|
+
}
|
|
266
|
+
return args;
|
|
267
|
+
}
|
|
268
|
+
class FunctionalTranspiler extends DefaultTranspiler {
|
|
269
|
+
injector = inject(Injector);
|
|
270
|
+
transpile({ value, ...rest }) {
|
|
271
|
+
let transpiled = value;
|
|
272
|
+
if (isString(value)) {
|
|
273
|
+
transpiled = value.replace(/\[\[\s*(\w+)\((.*?)\)\s*]]/g, (match, functionName, args) => {
|
|
274
|
+
try {
|
|
275
|
+
const func = this.injector.get(functionName);
|
|
276
|
+
return func.transpile(...getFunctionArgs(args));
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
let message = `There is an error in: '${value}'.
|
|
280
|
+
Check that the you used the right syntax in your translation and that the implementation of ${functionName} is correct.`;
|
|
281
|
+
if (e.message.includes('NullInjectorError')) {
|
|
282
|
+
message = `You are using the '${functionName}' function in your translation but no provider was found!`;
|
|
283
|
+
}
|
|
284
|
+
throw new Error(message);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return super.transpile({ value: transpiled, ...rest });
|
|
289
|
+
}
|
|
290
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: FunctionalTranspiler, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
291
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: FunctionalTranspiler });
|
|
292
|
+
}
|
|
293
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: FunctionalTranspiler, decorators: [{
|
|
294
|
+
type: Injectable
|
|
295
|
+
}] });
|
|
296
|
+
|
|
297
|
+
const TRANSLOCO_MISSING_HANDLER = new InjectionToken('TRANSLOCO_MISSING_HANDLER');
|
|
298
|
+
class DefaultHandler {
|
|
299
|
+
handle(key, config) {
|
|
300
|
+
if (config.missingHandler.logMissingKey && !config.prodMode) {
|
|
301
|
+
const msg = `Missing translation for '${key}'`;
|
|
302
|
+
console.warn(`%c ${msg}`, 'font-size: 12px; color: red');
|
|
303
|
+
}
|
|
304
|
+
return key;
|
|
305
|
+
}
|
|
306
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
307
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultHandler });
|
|
308
|
+
}
|
|
309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultHandler, decorators: [{
|
|
310
|
+
type: Injectable
|
|
311
|
+
}] });
|
|
312
|
+
|
|
313
|
+
const TRANSLOCO_INTERCEPTOR = new InjectionToken('TRANSLOCO_INTERCEPTOR');
|
|
314
|
+
class DefaultInterceptor {
|
|
315
|
+
preSaveTranslation(translation) {
|
|
316
|
+
return translation;
|
|
317
|
+
}
|
|
318
|
+
preSaveTranslationKey(_, value) {
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
322
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultInterceptor });
|
|
323
|
+
}
|
|
324
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultInterceptor, decorators: [{
|
|
325
|
+
type: Injectable
|
|
326
|
+
}] });
|
|
327
|
+
|
|
328
|
+
const TRANSLOCO_FALLBACK_STRATEGY = new InjectionToken('TRANSLOCO_FALLBACK_STRATEGY');
|
|
329
|
+
class DefaultFallbackStrategy {
|
|
330
|
+
userConfig;
|
|
331
|
+
constructor(userConfig) {
|
|
332
|
+
this.userConfig = userConfig;
|
|
333
|
+
}
|
|
334
|
+
getNextLangs() {
|
|
335
|
+
const fallbackLang = this.userConfig.fallbackLang;
|
|
336
|
+
if (!fallbackLang) {
|
|
337
|
+
throw new Error('When using the default fallback, a fallback language must be provided in the config!');
|
|
338
|
+
}
|
|
339
|
+
return Array.isArray(fallbackLang) ? fallbackLang : [fallbackLang];
|
|
340
|
+
}
|
|
341
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultFallbackStrategy, deps: [{ token: TRANSLOCO_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
342
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultFallbackStrategy });
|
|
343
|
+
}
|
|
344
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: DefaultFallbackStrategy, decorators: [{
|
|
345
|
+
type: Injectable
|
|
346
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
347
|
+
type: Inject,
|
|
348
|
+
args: [TRANSLOCO_CONFIG]
|
|
349
|
+
}] }] });
|
|
350
|
+
|
|
351
|
+
/*
|
|
352
|
+
* @example
|
|
353
|
+
*
|
|
354
|
+
* given: lazy-page/en => lazy-page
|
|
355
|
+
*
|
|
356
|
+
*/
|
|
357
|
+
function getScopeFromLang(lang) {
|
|
358
|
+
if (!lang) {
|
|
359
|
+
return '';
|
|
360
|
+
}
|
|
361
|
+
const split = lang.split('/');
|
|
362
|
+
split.pop();
|
|
363
|
+
return split.join('/');
|
|
364
|
+
}
|
|
365
|
+
/*
|
|
366
|
+
* @example
|
|
367
|
+
*
|
|
368
|
+
* given: lazy-page/en => en
|
|
369
|
+
*
|
|
370
|
+
*/
|
|
371
|
+
function getLangFromScope(lang) {
|
|
372
|
+
if (!lang) {
|
|
373
|
+
return '';
|
|
374
|
+
}
|
|
375
|
+
return lang.split('/').pop();
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* @example
|
|
379
|
+
*
|
|
380
|
+
* getPipeValue('todos|scoped', 'scoped') [true, 'todos']
|
|
381
|
+
* getPipeValue('en|static', 'static') [true, 'en']
|
|
382
|
+
* getPipeValue('en', 'static') [false, 'en']
|
|
383
|
+
*/
|
|
384
|
+
function getPipeValue(str, value, char = '|') {
|
|
385
|
+
if (isString(str)) {
|
|
386
|
+
const splitted = str.split(char);
|
|
387
|
+
const lastItem = splitted.pop();
|
|
388
|
+
return lastItem === value ? [true, splitted.toString()] : [false, lastItem];
|
|
389
|
+
}
|
|
390
|
+
return [false, ''];
|
|
391
|
+
}
|
|
392
|
+
function shouldListenToLangChanges(service, lang) {
|
|
393
|
+
const [hasStatic] = getPipeValue(lang, 'static');
|
|
394
|
+
if (!hasStatic) {
|
|
395
|
+
// If we didn't get 'lang|static' check if it's set in the global level
|
|
396
|
+
return !!service.config.reRenderOnLangChange;
|
|
397
|
+
}
|
|
398
|
+
// We have 'lang|static' so don't listen to lang changes
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
function listenOrNotOperator(listenToLangChange) {
|
|
402
|
+
return listenToLangChange ? (source) => source : take(1);
|
|
403
|
+
}
|
|
404
|
+
function prependScope(inlineLoader, scope) {
|
|
405
|
+
return Object.keys(inlineLoader).reduce((acc, lang) => {
|
|
406
|
+
acc[`${scope}/${lang}`] = inlineLoader[lang];
|
|
407
|
+
return acc;
|
|
408
|
+
}, {});
|
|
409
|
+
}
|
|
410
|
+
function resolveInlineLoader(providerScope, scope) {
|
|
411
|
+
return hasInlineLoader(providerScope)
|
|
412
|
+
? prependScope(providerScope.loader, scope)
|
|
413
|
+
: undefined;
|
|
414
|
+
}
|
|
415
|
+
function getEventPayload(lang) {
|
|
416
|
+
return {
|
|
417
|
+
scope: getScopeFromLang(lang) || null,
|
|
418
|
+
langName: getLangFromScope(lang),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resolveLoader(options) {
|
|
423
|
+
const { path, inlineLoader, mainLoader, data } = options;
|
|
424
|
+
if (inlineLoader) {
|
|
425
|
+
const pathLoader = inlineLoader[path];
|
|
426
|
+
if (isFunction(pathLoader) === false) {
|
|
427
|
+
throw `You're using an inline loader but didn't provide a loader for ${path}`;
|
|
428
|
+
}
|
|
429
|
+
return inlineLoader[path]().then((res) => res.default ? res.default : res);
|
|
430
|
+
}
|
|
431
|
+
return mainLoader.getTranslation(path, data);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function getFallbacksLoaders({ mainLoader, path, data, fallbackPath, inlineLoader, }) {
|
|
435
|
+
const paths = fallbackPath ? [path, fallbackPath] : [path];
|
|
436
|
+
return paths.map((path) => {
|
|
437
|
+
const loader = resolveLoader({ path, mainLoader, inlineLoader, data });
|
|
438
|
+
return from(loader).pipe(map((translation) => ({
|
|
439
|
+
translation,
|
|
440
|
+
lang: path,
|
|
441
|
+
})));
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let service;
|
|
446
|
+
function translate(key, params = {}, lang) {
|
|
447
|
+
return service.translate(key, params, lang);
|
|
448
|
+
}
|
|
449
|
+
function translateObject(key, params = {}, lang) {
|
|
450
|
+
return service.translateObject(key, params, lang);
|
|
451
|
+
}
|
|
452
|
+
class TranslocoService {
|
|
453
|
+
loader;
|
|
454
|
+
parser;
|
|
455
|
+
missingHandler;
|
|
456
|
+
interceptor;
|
|
457
|
+
fallbackStrategy;
|
|
458
|
+
langChanges$;
|
|
459
|
+
subscription = null;
|
|
460
|
+
translations = new Map();
|
|
461
|
+
cache = new Map();
|
|
462
|
+
firstFallbackLang;
|
|
463
|
+
defaultLang = '';
|
|
464
|
+
availableLangs = [];
|
|
465
|
+
isResolvedMissingOnce = false;
|
|
466
|
+
lang;
|
|
467
|
+
failedLangs = new Set();
|
|
468
|
+
events = new Subject();
|
|
469
|
+
events$ = this.events.asObservable();
|
|
470
|
+
config;
|
|
471
|
+
constructor(loader, parser, missingHandler, interceptor, userConfig, fallbackStrategy) {
|
|
472
|
+
this.loader = loader;
|
|
473
|
+
this.parser = parser;
|
|
474
|
+
this.missingHandler = missingHandler;
|
|
475
|
+
this.interceptor = interceptor;
|
|
476
|
+
this.fallbackStrategy = fallbackStrategy;
|
|
477
|
+
if (!this.loader) {
|
|
478
|
+
this.loader = new DefaultLoader(this.translations);
|
|
479
|
+
}
|
|
480
|
+
service = this;
|
|
481
|
+
this.config = JSON.parse(JSON.stringify(userConfig));
|
|
482
|
+
this.setAvailableLangs(this.config.availableLangs || []);
|
|
483
|
+
this.setFallbackLangForMissingTranslation(this.config);
|
|
484
|
+
this.setDefaultLang(this.config.defaultLang);
|
|
485
|
+
this.lang = new BehaviorSubject(this.getDefaultLang());
|
|
486
|
+
// Don't use distinctUntilChanged as we need the ability to update
|
|
487
|
+
// the value when using setTranslation or setTranslationKeys
|
|
488
|
+
this.langChanges$ = this.lang.asObservable();
|
|
489
|
+
/**
|
|
490
|
+
* When we have a failure, we want to define the next language that succeeded as the active
|
|
491
|
+
*/
|
|
492
|
+
this.subscription = this.events$.subscribe((e) => {
|
|
493
|
+
if (e.type === 'translationLoadSuccess' && e.wasFailure) {
|
|
494
|
+
this.setActiveLang(e.payload.langName);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
getDefaultLang() {
|
|
499
|
+
return this.defaultLang;
|
|
500
|
+
}
|
|
501
|
+
setDefaultLang(lang) {
|
|
502
|
+
this.defaultLang = lang;
|
|
503
|
+
}
|
|
504
|
+
getActiveLang() {
|
|
505
|
+
return this.lang.getValue();
|
|
506
|
+
}
|
|
507
|
+
setActiveLang(lang) {
|
|
508
|
+
this.parser.onLangChanged?.(lang);
|
|
509
|
+
this.lang.next(lang);
|
|
510
|
+
this.events.next({
|
|
511
|
+
type: 'langChanged',
|
|
512
|
+
payload: getEventPayload(lang),
|
|
513
|
+
});
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
setAvailableLangs(langs) {
|
|
517
|
+
this.availableLangs = langs;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Gets the available languages.
|
|
521
|
+
*
|
|
522
|
+
* @returns
|
|
523
|
+
* An array of the available languages. Can be either a `string[]` or a `{ id: string; label: string }[]`
|
|
524
|
+
* depending on how the available languages are set in your module.
|
|
525
|
+
*/
|
|
526
|
+
getAvailableLangs() {
|
|
527
|
+
return this.availableLangs;
|
|
528
|
+
}
|
|
529
|
+
load(path, options = {}) {
|
|
530
|
+
const cached = this.cache.get(path);
|
|
531
|
+
if (cached) {
|
|
532
|
+
return cached;
|
|
533
|
+
}
|
|
534
|
+
let loadTranslation;
|
|
535
|
+
const isScope = this._isLangScoped(path);
|
|
536
|
+
let scope;
|
|
537
|
+
if (isScope) {
|
|
538
|
+
scope = getScopeFromLang(path);
|
|
539
|
+
}
|
|
540
|
+
const loadersOptions = {
|
|
541
|
+
path,
|
|
542
|
+
mainLoader: this.loader,
|
|
543
|
+
inlineLoader: options.inlineLoader,
|
|
544
|
+
data: isScope ? { scope: scope } : undefined,
|
|
545
|
+
};
|
|
546
|
+
if (this.useFallbackTranslation(path)) {
|
|
547
|
+
// if the path is scope the fallback should be `scope/fallbackLang`;
|
|
548
|
+
const fallback = isScope
|
|
549
|
+
? `${scope}/${this.firstFallbackLang}`
|
|
550
|
+
: this.firstFallbackLang;
|
|
551
|
+
const loaders = getFallbacksLoaders({
|
|
552
|
+
...loadersOptions,
|
|
553
|
+
fallbackPath: fallback,
|
|
554
|
+
});
|
|
555
|
+
loadTranslation = forkJoin(loaders);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const loader = resolveLoader(loadersOptions);
|
|
559
|
+
loadTranslation = from(loader);
|
|
560
|
+
}
|
|
561
|
+
const load$ = loadTranslation.pipe(retry(this.config.failedRetries), tap((translation) => {
|
|
562
|
+
if (Array.isArray(translation)) {
|
|
563
|
+
translation.forEach((t) => {
|
|
564
|
+
this.handleSuccess(t.lang, t.translation);
|
|
565
|
+
// Save the fallback in cache so we'll not create a redundant request
|
|
566
|
+
if (t.lang !== path) {
|
|
567
|
+
this.cache.set(t.lang, of({}));
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
this.handleSuccess(path, translation);
|
|
573
|
+
}), catchError((error) => {
|
|
574
|
+
if (!this.config.prodMode) {
|
|
575
|
+
console.error(`Error while trying to load "${path}"`, error);
|
|
576
|
+
}
|
|
577
|
+
return this.handleFailure(path, options);
|
|
578
|
+
}), shareReplay(1));
|
|
579
|
+
this.cache.set(path, load$);
|
|
580
|
+
return load$;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Gets the instant translated value of a key
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
*
|
|
587
|
+
* translate<string>('hello')
|
|
588
|
+
* translate('hello', { value: 'value' })
|
|
589
|
+
* translate<string[]>(['hello', 'key'])
|
|
590
|
+
* translate('hello', { }, 'en')
|
|
591
|
+
* translate('scope.someKey', { }, 'en')
|
|
592
|
+
*/
|
|
593
|
+
translate(key, params = {}, lang = this.getActiveLang()) {
|
|
594
|
+
if (!key)
|
|
595
|
+
return key;
|
|
596
|
+
const { scope, resolveLang } = this.resolveLangAndScope(lang);
|
|
597
|
+
if (Array.isArray(key)) {
|
|
598
|
+
return key.map((k) => this.translate(scope ? `${scope}.${k}` : k, params, resolveLang));
|
|
599
|
+
}
|
|
600
|
+
key = scope ? `${scope}.${key}` : key;
|
|
601
|
+
const translation = this.getTranslation(resolveLang);
|
|
602
|
+
const value = translation[key];
|
|
603
|
+
if (!value) {
|
|
604
|
+
return this._handleMissingKey(key, value, params);
|
|
605
|
+
}
|
|
606
|
+
return this.parser.transpile({
|
|
607
|
+
value, params, translation, key
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Gets the translated value of a key as observable
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
*
|
|
615
|
+
* selectTranslate<string>('hello').subscribe(value => ...)
|
|
616
|
+
* selectTranslate<string>('hello', {}, 'es').subscribe(value => ...)
|
|
617
|
+
* selectTranslate<string>('hello', {}, 'todos').subscribe(value => ...)
|
|
618
|
+
* selectTranslate<string>('hello', {}, { scope: 'todos' }).subscribe(value => ...)
|
|
619
|
+
*
|
|
620
|
+
*/
|
|
621
|
+
selectTranslate(key, params, lang, _isObject = false) {
|
|
622
|
+
let inlineLoader;
|
|
623
|
+
const load = (lang, options) => this.load(lang, options).pipe(map(() => _isObject
|
|
624
|
+
? this.translateObject(key, params, lang)
|
|
625
|
+
: this.translate(key, params, lang)));
|
|
626
|
+
if (isNil(lang)) {
|
|
627
|
+
return this.langChanges$.pipe(switchMap((lang) => load(lang)));
|
|
628
|
+
}
|
|
629
|
+
if (isScopeArray(lang) || isScopeObject(lang)) {
|
|
630
|
+
// it's a scope object.
|
|
631
|
+
const providerScope = Array.isArray(lang) ? lang[0] : lang;
|
|
632
|
+
lang = providerScope.scope;
|
|
633
|
+
inlineLoader = resolveInlineLoader(providerScope, providerScope.scope);
|
|
634
|
+
}
|
|
635
|
+
lang = lang;
|
|
636
|
+
if (this.isLang(lang) || this.isScopeWithLang(lang)) {
|
|
637
|
+
return load(lang);
|
|
638
|
+
}
|
|
639
|
+
// it's a scope
|
|
640
|
+
const scope = lang;
|
|
641
|
+
return this.langChanges$.pipe(switchMap((lang) => load(`${scope}/${lang}`, { inlineLoader })));
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Whether the scope with lang
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
*
|
|
648
|
+
* todos/en => true
|
|
649
|
+
* todos => false
|
|
650
|
+
*/
|
|
651
|
+
isScopeWithLang(lang) {
|
|
652
|
+
return this.isLang(getLangFromScope(lang));
|
|
653
|
+
}
|
|
654
|
+
translateObject(key, params = {}, lang = this.getActiveLang()) {
|
|
655
|
+
if (isString(key) || Array.isArray(key)) {
|
|
656
|
+
const { resolveLang, scope } = this.resolveLangAndScope(lang);
|
|
657
|
+
if (Array.isArray(key)) {
|
|
658
|
+
return key.map((k) => this.translateObject(scope ? `${scope}.${k}` : k, params, resolveLang));
|
|
659
|
+
}
|
|
660
|
+
const translation = this.getTranslation(resolveLang);
|
|
661
|
+
key = scope ? `${scope}.${key}` : key;
|
|
662
|
+
const value = unflatten(this.getObjectByKey(translation, key));
|
|
663
|
+
/* If an empty object was returned we want to try and translate the key as a string and not an object */
|
|
664
|
+
return isEmpty(value)
|
|
665
|
+
? this.translate(key, params, lang)
|
|
666
|
+
: this.parser.transpile({ value, params: params, translation, key });
|
|
667
|
+
}
|
|
668
|
+
const translations = [];
|
|
669
|
+
for (const [_key, _params] of this.getEntries(key)) {
|
|
670
|
+
translations.push(this.translateObject(_key, _params, lang));
|
|
671
|
+
}
|
|
672
|
+
return translations;
|
|
673
|
+
}
|
|
674
|
+
selectTranslateObject(key, params, lang) {
|
|
675
|
+
if (isString(key) || Array.isArray(key)) {
|
|
676
|
+
return this.selectTranslate(key, params, lang, true);
|
|
677
|
+
}
|
|
678
|
+
const [[firstKey, firstParams], ...rest] = this.getEntries(key);
|
|
679
|
+
/* In order to avoid subscribing multiple times to the load language event by calling selectTranslateObject for each pair,
|
|
680
|
+
* we listen to when the first key has been translated (the language is loaded) and translate the rest synchronously */
|
|
681
|
+
return this.selectTranslateObject(firstKey, firstParams, lang).pipe(map((value) => {
|
|
682
|
+
const translations = [value];
|
|
683
|
+
for (const [_key, _params] of rest) {
|
|
684
|
+
translations.push(this.translateObject(_key, _params, lang));
|
|
685
|
+
}
|
|
686
|
+
return translations;
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
getTranslation(langOrScope) {
|
|
690
|
+
if (langOrScope) {
|
|
691
|
+
if (this.isLang(langOrScope)) {
|
|
692
|
+
return this.translations.get(langOrScope) || {};
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
// This is a scope, build the scope value from the translation object
|
|
696
|
+
const { scope, resolveLang } = this.resolveLangAndScope(langOrScope);
|
|
697
|
+
const translation = this.translations.get(resolveLang) || {};
|
|
698
|
+
return this.getObjectByKey(translation, scope);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return this.translations;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Gets an object of translations for a given language
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
*
|
|
708
|
+
* selectTranslation().subscribe() - will return the current lang translation
|
|
709
|
+
* selectTranslation('es').subscribe()
|
|
710
|
+
* selectTranslation('admin-page').subscribe() - will return the current lang scope translation
|
|
711
|
+
* selectTranslation('admin-page/es').subscribe()
|
|
712
|
+
*/
|
|
713
|
+
selectTranslation(lang) {
|
|
714
|
+
let language$ = this.langChanges$;
|
|
715
|
+
if (lang) {
|
|
716
|
+
const scopeLangSpecified = getLangFromScope(lang) !== lang;
|
|
717
|
+
if (this.isLang(lang) || scopeLangSpecified) {
|
|
718
|
+
language$ = of(lang);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
language$ = this.langChanges$.pipe(map((currentLang) => `${lang}/${currentLang}`));
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return language$.pipe(switchMap((language) => this.load(language).pipe(map(() => this.getTranslation(language)))));
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Sets or merge a given translation object to current lang
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
*
|
|
731
|
+
* setTranslation({ ... })
|
|
732
|
+
* setTranslation({ ... }, 'en')
|
|
733
|
+
* setTranslation({ ... }, 'es', { merge: false } )
|
|
734
|
+
* setTranslation({ ... }, 'todos/en', { merge: false } )
|
|
735
|
+
*/
|
|
736
|
+
setTranslation(translation, lang = this.getActiveLang(), options = {}) {
|
|
737
|
+
const defaults = { merge: true, emitChange: true };
|
|
738
|
+
const mergedOptions = { ...defaults, ...options };
|
|
739
|
+
const scope = getScopeFromLang(lang);
|
|
740
|
+
/**
|
|
741
|
+
* If this isn't a scope we use the whole translation as is
|
|
742
|
+
* otherwise we need to flat the scope and use it
|
|
743
|
+
*/
|
|
744
|
+
let flattenScopeOrTranslation = translation;
|
|
745
|
+
// Merged the scoped language into the active language
|
|
746
|
+
if (scope) {
|
|
747
|
+
const key = this.getMappedScope(scope);
|
|
748
|
+
flattenScopeOrTranslation = flatten({ [key]: translation });
|
|
749
|
+
}
|
|
750
|
+
const currentLang = scope ? getLangFromScope(lang) : lang;
|
|
751
|
+
const mergedTranslation = {
|
|
752
|
+
...(mergedOptions.merge && this.getTranslation(currentLang)),
|
|
753
|
+
...flattenScopeOrTranslation,
|
|
754
|
+
};
|
|
755
|
+
const flattenTranslation = this.config.flatten.aot
|
|
756
|
+
? mergedTranslation
|
|
757
|
+
: flatten(mergedTranslation);
|
|
758
|
+
const withHook = this.interceptor.preSaveTranslation(flattenTranslation, currentLang);
|
|
759
|
+
this.translations.set(currentLang, withHook);
|
|
760
|
+
mergedOptions.emitChange && this.setActiveLang(this.getActiveLang());
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Sets translation key with given value
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
*
|
|
767
|
+
* setTranslationKey('key', 'value')
|
|
768
|
+
* setTranslationKey('key.nested', 'value')
|
|
769
|
+
* setTranslationKey('key.nested', 'value', 'en')
|
|
770
|
+
* setTranslationKey('key.nested', 'value', 'en', { emitChange: false } )
|
|
771
|
+
*/
|
|
772
|
+
setTranslationKey(key, value, options = {}) {
|
|
773
|
+
const lang = options.lang || this.getActiveLang();
|
|
774
|
+
const withHook = this.interceptor.preSaveTranslationKey(key, value, lang);
|
|
775
|
+
const newValue = {
|
|
776
|
+
[key]: withHook,
|
|
777
|
+
};
|
|
778
|
+
this.setTranslation(newValue, lang, { ...options, merge: true });
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Sets the fallback lang for the currently active language
|
|
782
|
+
* @param fallbackLang
|
|
783
|
+
*/
|
|
784
|
+
setFallbackLangForMissingTranslation({ fallbackLang, }) {
|
|
785
|
+
const lang = Array.isArray(fallbackLang) ? fallbackLang[0] : fallbackLang;
|
|
786
|
+
if (fallbackLang && this.useFallbackTranslation(lang)) {
|
|
787
|
+
this.firstFallbackLang = lang;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* @internal
|
|
792
|
+
*/
|
|
793
|
+
_handleMissingKey(key, value, params) {
|
|
794
|
+
if (this.config.missingHandler.allowEmpty && value === '') {
|
|
795
|
+
return '';
|
|
796
|
+
}
|
|
797
|
+
if (!this.isResolvedMissingOnce && this.useFallbackTranslation()) {
|
|
798
|
+
// We need to set it to true to prevent a loop
|
|
799
|
+
this.isResolvedMissingOnce = true;
|
|
800
|
+
const fallbackValue = this.translate(key, params, this.firstFallbackLang);
|
|
801
|
+
this.isResolvedMissingOnce = false;
|
|
802
|
+
return fallbackValue;
|
|
803
|
+
}
|
|
804
|
+
return this.missingHandler.handle(key, this.getMissingHandlerData(), params);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* @internal
|
|
808
|
+
*/
|
|
809
|
+
_isLangScoped(lang) {
|
|
810
|
+
return this.getAvailableLangsIds().indexOf(lang) === -1;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Checks if a given string is one of the specified available languages.
|
|
814
|
+
* @returns
|
|
815
|
+
* True if the given string is an available language.
|
|
816
|
+
* False if the given string is not an available language.
|
|
817
|
+
*/
|
|
818
|
+
isLang(lang) {
|
|
819
|
+
return this.getAvailableLangsIds().indexOf(lang) !== -1;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* @internal
|
|
823
|
+
*
|
|
824
|
+
* We always want to make sure the global lang is loaded
|
|
825
|
+
* before loading the scope since you can access both via the pipe/directive.
|
|
826
|
+
*/
|
|
827
|
+
_loadDependencies(path, inlineLoader) {
|
|
828
|
+
const mainLang = getLangFromScope(path);
|
|
829
|
+
if (this._isLangScoped(path) && !this.isLoadedTranslation(mainLang)) {
|
|
830
|
+
return combineLatest([
|
|
831
|
+
this.load(mainLang),
|
|
832
|
+
this.load(path, { inlineLoader }),
|
|
833
|
+
]);
|
|
834
|
+
}
|
|
835
|
+
return this.load(path, { inlineLoader });
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* @internal
|
|
839
|
+
*/
|
|
840
|
+
_completeScopeWithLang(langOrScope) {
|
|
841
|
+
if (this._isLangScoped(langOrScope) &&
|
|
842
|
+
!this.isLang(getLangFromScope(langOrScope))) {
|
|
843
|
+
return `${langOrScope}/${this.getActiveLang()}`;
|
|
844
|
+
}
|
|
845
|
+
return langOrScope;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* @internal
|
|
849
|
+
*/
|
|
850
|
+
_setScopeAlias(scope, alias) {
|
|
851
|
+
if (!this.config.scopeMapping) {
|
|
852
|
+
this.config.scopeMapping = {};
|
|
853
|
+
}
|
|
854
|
+
this.config.scopeMapping[scope] = alias;
|
|
855
|
+
}
|
|
856
|
+
ngOnDestroy() {
|
|
857
|
+
if (this.subscription) {
|
|
858
|
+
this.subscription.unsubscribe();
|
|
859
|
+
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
|
|
860
|
+
// callback within its `destination` property, preventing classes from being GC'd.
|
|
861
|
+
this.subscription = null;
|
|
862
|
+
}
|
|
863
|
+
// Caretaker note: since this is the root provider, it'll be destroyed when the `NgModuleRef.destroy()` is run.
|
|
864
|
+
// Cached values capture `this`, thus leading to a circular reference and preventing the `TranslocoService` from
|
|
865
|
+
// being GC'd. This would lead to a memory leak when server-side rendering is used since the service is created
|
|
866
|
+
// and destroyed per each HTTP request, but any service is not getting GC'd.
|
|
867
|
+
this.cache.clear();
|
|
868
|
+
}
|
|
869
|
+
isLoadedTranslation(lang) {
|
|
870
|
+
return size(this.getTranslation(lang));
|
|
871
|
+
}
|
|
872
|
+
getAvailableLangsIds() {
|
|
873
|
+
const first = this.getAvailableLangs()[0];
|
|
874
|
+
if (isString(first)) {
|
|
875
|
+
return this.getAvailableLangs();
|
|
876
|
+
}
|
|
877
|
+
return this.getAvailableLangs().map((l) => l.id);
|
|
878
|
+
}
|
|
879
|
+
getMissingHandlerData() {
|
|
880
|
+
return {
|
|
881
|
+
...this.config,
|
|
882
|
+
activeLang: this.getActiveLang(),
|
|
883
|
+
availableLangs: this.availableLangs,
|
|
884
|
+
defaultLang: this.defaultLang,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Use a fallback translation set for missing keys of the primary language
|
|
889
|
+
* This is unrelated to the fallback language (which changes the active language)
|
|
890
|
+
*/
|
|
891
|
+
useFallbackTranslation(lang) {
|
|
892
|
+
return (this.config.missingHandler.useFallbackTranslation &&
|
|
893
|
+
lang !== this.firstFallbackLang);
|
|
894
|
+
}
|
|
895
|
+
handleSuccess(lang, translation) {
|
|
896
|
+
this.setTranslation(translation, lang, { emitChange: false });
|
|
897
|
+
this.events.next({
|
|
898
|
+
wasFailure: !!this.failedLangs.size,
|
|
899
|
+
type: 'translationLoadSuccess',
|
|
900
|
+
payload: getEventPayload(lang),
|
|
901
|
+
});
|
|
902
|
+
this.failedLangs.forEach((l) => this.cache.delete(l));
|
|
903
|
+
this.failedLangs.clear();
|
|
904
|
+
}
|
|
905
|
+
handleFailure(lang, loadOptions) {
|
|
906
|
+
// When starting to load a first choice language, initialize
|
|
907
|
+
// the failed counter and resolve the fallback langs.
|
|
908
|
+
if (isNil(loadOptions.failedCounter)) {
|
|
909
|
+
loadOptions.failedCounter = 0;
|
|
910
|
+
if (!loadOptions.fallbackLangs) {
|
|
911
|
+
loadOptions.fallbackLangs = this.fallbackStrategy.getNextLangs(lang);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const splitted = lang.split('/');
|
|
915
|
+
const fallbacks = loadOptions.fallbackLangs;
|
|
916
|
+
const nextLang = fallbacks[loadOptions.failedCounter];
|
|
917
|
+
this.failedLangs.add(lang);
|
|
918
|
+
// This handles the case where a loaded fallback language is requested again
|
|
919
|
+
if (this.cache.has(nextLang)) {
|
|
920
|
+
this.handleSuccess(nextLang, this.getTranslation(nextLang));
|
|
921
|
+
return EMPTY;
|
|
922
|
+
}
|
|
923
|
+
const isFallbackLang = nextLang === splitted[splitted.length - 1];
|
|
924
|
+
if (!nextLang || isFallbackLang) {
|
|
925
|
+
let msg = `Unable to load translation and all the fallback languages`;
|
|
926
|
+
if (splitted.length > 1) {
|
|
927
|
+
msg += `, did you misspelled the scope name?`;
|
|
928
|
+
}
|
|
929
|
+
throw new Error(msg);
|
|
930
|
+
}
|
|
931
|
+
let resolveLang = nextLang;
|
|
932
|
+
// if it's scoped lang
|
|
933
|
+
if (splitted.length > 1) {
|
|
934
|
+
// We need to resolve it to:
|
|
935
|
+
// todos/langNotExists => todos/nextLang
|
|
936
|
+
splitted[splitted.length - 1] = nextLang;
|
|
937
|
+
resolveLang = splitted.join('/');
|
|
938
|
+
}
|
|
939
|
+
loadOptions.failedCounter++;
|
|
940
|
+
this.events.next({
|
|
941
|
+
type: 'translationLoadFailure',
|
|
942
|
+
payload: getEventPayload(lang),
|
|
943
|
+
});
|
|
944
|
+
return this.load(resolveLang, loadOptions);
|
|
945
|
+
}
|
|
946
|
+
getMappedScope(scope) {
|
|
947
|
+
const { scopeMapping = {} } = this.config;
|
|
948
|
+
return scopeMapping[scope] || toCamelCase(scope);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* If lang is scope we need to check the following cases:
|
|
952
|
+
* todos/es => in this case we should take `es` as lang
|
|
953
|
+
* todos => in this case we should set the active lang as lang
|
|
954
|
+
*/
|
|
955
|
+
resolveLangAndScope(lang) {
|
|
956
|
+
let resolveLang = lang;
|
|
957
|
+
let scope;
|
|
958
|
+
if (this._isLangScoped(lang)) {
|
|
959
|
+
// en for example
|
|
960
|
+
const langFromScope = getLangFromScope(lang);
|
|
961
|
+
// en is lang
|
|
962
|
+
const hasLang = this.isLang(langFromScope);
|
|
963
|
+
// take en
|
|
964
|
+
resolveLang = hasLang ? langFromScope : this.getActiveLang();
|
|
965
|
+
// find the scope
|
|
966
|
+
scope = this.getMappedScope(hasLang ? getScopeFromLang(lang) : lang);
|
|
967
|
+
}
|
|
968
|
+
return { scope, resolveLang };
|
|
969
|
+
}
|
|
970
|
+
getObjectByKey(translation, key) {
|
|
971
|
+
const result = {};
|
|
972
|
+
const prefix = `${key}.`;
|
|
973
|
+
for (const currentKey in translation) {
|
|
974
|
+
if (currentKey.startsWith(prefix)) {
|
|
975
|
+
result[currentKey.replace(prefix, '')] = translation[currentKey];
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return result;
|
|
979
|
+
}
|
|
980
|
+
getEntries(key) {
|
|
981
|
+
return key instanceof Map ? key.entries() : Object.entries(key);
|
|
982
|
+
}
|
|
983
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoService, deps: [{ token: TRANSLOCO_LOADER, optional: true }, { token: TRANSLOCO_TRANSPILER }, { token: TRANSLOCO_MISSING_HANDLER }, { token: TRANSLOCO_INTERCEPTOR }, { token: TRANSLOCO_CONFIG }, { token: TRANSLOCO_FALLBACK_STRATEGY }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
984
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoService, providedIn: 'root' });
|
|
985
|
+
}
|
|
986
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoService, decorators: [{
|
|
987
|
+
type: Injectable,
|
|
988
|
+
args: [{ providedIn: 'root' }]
|
|
989
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
990
|
+
type: Optional
|
|
991
|
+
}, {
|
|
992
|
+
type: Inject,
|
|
993
|
+
args: [TRANSLOCO_LOADER]
|
|
994
|
+
}] }, { type: undefined, decorators: [{
|
|
995
|
+
type: Inject,
|
|
996
|
+
args: [TRANSLOCO_TRANSPILER]
|
|
997
|
+
}] }, { type: undefined, decorators: [{
|
|
998
|
+
type: Inject,
|
|
999
|
+
args: [TRANSLOCO_MISSING_HANDLER]
|
|
1000
|
+
}] }, { type: undefined, decorators: [{
|
|
1001
|
+
type: Inject,
|
|
1002
|
+
args: [TRANSLOCO_INTERCEPTOR]
|
|
1003
|
+
}] }, { type: undefined, decorators: [{
|
|
1004
|
+
type: Inject,
|
|
1005
|
+
args: [TRANSLOCO_CONFIG]
|
|
1006
|
+
}] }, { type: undefined, decorators: [{
|
|
1007
|
+
type: Inject,
|
|
1008
|
+
args: [TRANSLOCO_FALLBACK_STRATEGY]
|
|
1009
|
+
}] }] });
|
|
1010
|
+
|
|
1011
|
+
class TranslocoLoaderComponent {
|
|
1012
|
+
html;
|
|
1013
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1014
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.0.9", type: TranslocoLoaderComponent, isStandalone: true, selector: "ng-component", inputs: { html: "html" }, ngImport: i0, template: `
|
|
1015
|
+
<div class="transloco-loader-template" [innerHTML]="html"></div>
|
|
1016
|
+
`, isInline: true });
|
|
1017
|
+
}
|
|
1018
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoLoaderComponent, decorators: [{
|
|
1019
|
+
type: Component,
|
|
1020
|
+
args: [{
|
|
1021
|
+
template: `
|
|
1022
|
+
<div class="transloco-loader-template" [innerHTML]="html"></div>
|
|
1023
|
+
`,
|
|
1024
|
+
standalone: true,
|
|
1025
|
+
}]
|
|
1026
|
+
}], propDecorators: { html: [{
|
|
1027
|
+
type: Input
|
|
1028
|
+
}] } });
|
|
1029
|
+
|
|
1030
|
+
class TemplateHandler {
|
|
1031
|
+
view;
|
|
1032
|
+
vcr;
|
|
1033
|
+
constructor(view, vcr) {
|
|
1034
|
+
this.view = view;
|
|
1035
|
+
this.vcr = vcr;
|
|
1036
|
+
}
|
|
1037
|
+
attachView() {
|
|
1038
|
+
if (this.view instanceof TemplateRef) {
|
|
1039
|
+
this.vcr.createEmbeddedView(this.view);
|
|
1040
|
+
}
|
|
1041
|
+
else if (isString(this.view)) {
|
|
1042
|
+
const componentRef = this.vcr.createComponent(TranslocoLoaderComponent);
|
|
1043
|
+
componentRef.instance.html = this.view;
|
|
1044
|
+
componentRef.hostView.detectChanges();
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
this.vcr.createComponent(this.view);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
detachView() {
|
|
1051
|
+
this.vcr.clear();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const TRANSLOCO_LANG = new InjectionToken('TRANSLOCO_LANG');
|
|
1056
|
+
|
|
1057
|
+
const TRANSLOCO_LOADING_TEMPLATE = new InjectionToken('TRANSLOCO_LOADING_TEMPLATE');
|
|
1058
|
+
|
|
1059
|
+
const TRANSLOCO_SCOPE = new InjectionToken('TRANSLOCO_SCOPE');
|
|
1060
|
+
|
|
1061
|
+
class LangResolver {
|
|
1062
|
+
initialized = false;
|
|
1063
|
+
// inline => provider => active
|
|
1064
|
+
resolve({ inline, provider, active }) {
|
|
1065
|
+
let lang = active;
|
|
1066
|
+
/**
|
|
1067
|
+
* When the user changes the lang we need to update
|
|
1068
|
+
* the view. Otherwise, the lang will remain the inline/provided lang
|
|
1069
|
+
*/
|
|
1070
|
+
if (this.initialized) {
|
|
1071
|
+
lang = active;
|
|
1072
|
+
return lang;
|
|
1073
|
+
}
|
|
1074
|
+
if (provider) {
|
|
1075
|
+
const [, extracted] = getPipeValue(provider, 'static');
|
|
1076
|
+
lang = extracted;
|
|
1077
|
+
}
|
|
1078
|
+
if (inline) {
|
|
1079
|
+
const [, extracted] = getPipeValue(inline, 'static');
|
|
1080
|
+
lang = extracted;
|
|
1081
|
+
}
|
|
1082
|
+
this.initialized = true;
|
|
1083
|
+
return lang;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
*
|
|
1087
|
+
* Resolve the lang
|
|
1088
|
+
*
|
|
1089
|
+
* @example
|
|
1090
|
+
*
|
|
1091
|
+
* resolveLangBasedOnScope('todos/en') => en
|
|
1092
|
+
* resolveLangBasedOnScope('en') => en
|
|
1093
|
+
*
|
|
1094
|
+
*/
|
|
1095
|
+
resolveLangBasedOnScope(lang) {
|
|
1096
|
+
const scope = getScopeFromLang(lang);
|
|
1097
|
+
return scope ? getLangFromScope(lang) : lang;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
*
|
|
1101
|
+
* Resolve the lang path for loading
|
|
1102
|
+
*
|
|
1103
|
+
* @example
|
|
1104
|
+
*
|
|
1105
|
+
* resolveLangPath('todos', 'en') => todos/en
|
|
1106
|
+
* resolveLangPath('en') => en
|
|
1107
|
+
*
|
|
1108
|
+
*/
|
|
1109
|
+
resolveLangPath(lang, scope) {
|
|
1110
|
+
return scope ? `${scope}/${lang}` : lang;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
class ScopeResolver {
|
|
1115
|
+
service;
|
|
1116
|
+
constructor(service) {
|
|
1117
|
+
this.service = service;
|
|
1118
|
+
}
|
|
1119
|
+
// inline => provider
|
|
1120
|
+
resolve(params) {
|
|
1121
|
+
const { inline, provider } = params;
|
|
1122
|
+
if (inline) {
|
|
1123
|
+
return inline;
|
|
1124
|
+
}
|
|
1125
|
+
if (provider) {
|
|
1126
|
+
if (isScopeObject(provider)) {
|
|
1127
|
+
const { scope, alias = toCamelCase(scope) } = provider;
|
|
1128
|
+
this.service._setScopeAlias(scope, alias);
|
|
1129
|
+
return scope;
|
|
1130
|
+
}
|
|
1131
|
+
return provider;
|
|
1132
|
+
}
|
|
1133
|
+
return undefined;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
class TranslocoDirective {
|
|
1138
|
+
service = inject(TranslocoService);
|
|
1139
|
+
tpl = inject(TemplateRef, {
|
|
1140
|
+
optional: true,
|
|
1141
|
+
});
|
|
1142
|
+
providerLang = inject(TRANSLOCO_LANG, { optional: true });
|
|
1143
|
+
providerScope = inject(TRANSLOCO_SCOPE, { optional: true });
|
|
1144
|
+
providedLoadingTpl = inject(TRANSLOCO_LOADING_TEMPLATE, {
|
|
1145
|
+
optional: true,
|
|
1146
|
+
});
|
|
1147
|
+
cdr = inject(ChangeDetectorRef);
|
|
1148
|
+
host = inject(ElementRef);
|
|
1149
|
+
vcr = inject(ViewContainerRef);
|
|
1150
|
+
renderer = inject(Renderer2);
|
|
1151
|
+
subscription = null;
|
|
1152
|
+
view;
|
|
1153
|
+
translationMemo = {};
|
|
1154
|
+
key;
|
|
1155
|
+
params = {};
|
|
1156
|
+
inlineScope;
|
|
1157
|
+
inlineRead;
|
|
1158
|
+
inlineLang;
|
|
1159
|
+
inlineTpl;
|
|
1160
|
+
currentLang;
|
|
1161
|
+
loaderTplHandler;
|
|
1162
|
+
// Whether we already rendered the view once
|
|
1163
|
+
initialized = false;
|
|
1164
|
+
path;
|
|
1165
|
+
langResolver = new LangResolver();
|
|
1166
|
+
scopeResolver = new ScopeResolver(this.service);
|
|
1167
|
+
strategy = this.tpl === null ? 'attribute' : 'structural';
|
|
1168
|
+
static ngTemplateContextGuard(dir, ctx) {
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
ngOnInit() {
|
|
1172
|
+
const listenToLangChange = shouldListenToLangChanges(this.service, this.providerLang || this.inlineLang);
|
|
1173
|
+
this.subscription = this.service.langChanges$
|
|
1174
|
+
.pipe(switchMap((activeLang) => {
|
|
1175
|
+
const lang = this.langResolver.resolve({
|
|
1176
|
+
inline: this.inlineLang,
|
|
1177
|
+
provider: this.providerLang,
|
|
1178
|
+
active: activeLang,
|
|
1179
|
+
});
|
|
1180
|
+
return Array.isArray(this.providerScope)
|
|
1181
|
+
? forkJoin(this.providerScope.map((providerScope) => this.resolveScope(lang, providerScope)))
|
|
1182
|
+
: this.resolveScope(lang, this.providerScope);
|
|
1183
|
+
}), listenOrNotOperator(listenToLangChange))
|
|
1184
|
+
.subscribe(() => {
|
|
1185
|
+
this.currentLang = this.langResolver.resolveLangBasedOnScope(this.path);
|
|
1186
|
+
this.strategy === 'attribute'
|
|
1187
|
+
? this.attributeStrategy()
|
|
1188
|
+
: this.structuralStrategy(this.currentLang, this.inlineRead);
|
|
1189
|
+
this.cdr.markForCheck();
|
|
1190
|
+
this.initialized = true;
|
|
1191
|
+
});
|
|
1192
|
+
if (!this.initialized) {
|
|
1193
|
+
const loadingContent = this.resolveLoadingContent();
|
|
1194
|
+
if (loadingContent) {
|
|
1195
|
+
this.loaderTplHandler = new TemplateHandler(loadingContent, this.vcr);
|
|
1196
|
+
this.loaderTplHandler.attachView();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
ngOnChanges(changes) {
|
|
1201
|
+
// We need to support dynamic keys/params, so if this is not the first change CD cycle
|
|
1202
|
+
// we need to run the function again in order to update the value
|
|
1203
|
+
if (this.strategy === 'attribute') {
|
|
1204
|
+
const notInit = Object.keys(changes).some((v) => !changes[v].firstChange);
|
|
1205
|
+
notInit && this.attributeStrategy();
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
attributeStrategy() {
|
|
1209
|
+
this.detachLoader();
|
|
1210
|
+
this.renderer.setProperty(this.host.nativeElement, 'innerText', this.service.translate(this.key, this.params, this.currentLang));
|
|
1211
|
+
}
|
|
1212
|
+
structuralStrategy(lang, read) {
|
|
1213
|
+
this.translationMemo = {};
|
|
1214
|
+
if (this.view) {
|
|
1215
|
+
// when the lang changes we need to change the reference so Angular will update the view
|
|
1216
|
+
this.view.context['$implicit'] = this.getTranslateFn(lang, read);
|
|
1217
|
+
this.view.context['currentLang'] = this.currentLang;
|
|
1218
|
+
}
|
|
1219
|
+
else {
|
|
1220
|
+
this.detachLoader();
|
|
1221
|
+
this.view = this.vcr.createEmbeddedView(this.tpl, {
|
|
1222
|
+
$implicit: this.getTranslateFn(lang, read),
|
|
1223
|
+
currentLang: this.currentLang,
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
getTranslateFn(lang, read) {
|
|
1228
|
+
return (key, params) => {
|
|
1229
|
+
const withRead = read ? `${read}.${key}` : key;
|
|
1230
|
+
const withParams = params
|
|
1231
|
+
? `${withRead}${JSON.stringify(params)}`
|
|
1232
|
+
: withRead;
|
|
1233
|
+
if (Object.prototype.hasOwnProperty.call(this.translationMemo, withParams)) {
|
|
1234
|
+
return this.translationMemo[withParams].value;
|
|
1235
|
+
}
|
|
1236
|
+
this.translationMemo[withParams] = {
|
|
1237
|
+
params,
|
|
1238
|
+
value: this.service.translate(withRead, params, lang),
|
|
1239
|
+
};
|
|
1240
|
+
return this.translationMemo[withParams].value;
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
resolveLoadingContent() {
|
|
1244
|
+
return this.inlineTpl || this.providedLoadingTpl;
|
|
1245
|
+
}
|
|
1246
|
+
ngOnDestroy() {
|
|
1247
|
+
if (this.subscription) {
|
|
1248
|
+
this.subscription.unsubscribe();
|
|
1249
|
+
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
|
|
1250
|
+
// callback within its `destination` property, preventing classes from being GC'd.
|
|
1251
|
+
this.subscription = null;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
detachLoader() {
|
|
1255
|
+
this.loaderTplHandler?.detachView();
|
|
1256
|
+
}
|
|
1257
|
+
resolveScope(lang, providerScope) {
|
|
1258
|
+
const resolvedScope = this.scopeResolver.resolve({
|
|
1259
|
+
inline: this.inlineScope,
|
|
1260
|
+
provider: providerScope,
|
|
1261
|
+
});
|
|
1262
|
+
this.path = this.langResolver.resolveLangPath(lang, resolvedScope);
|
|
1263
|
+
const inlineLoader = resolveInlineLoader(providerScope, resolvedScope);
|
|
1264
|
+
return this.service._loadDependencies(this.path, inlineLoader);
|
|
1265
|
+
}
|
|
1266
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1267
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.0.9", type: TranslocoDirective, isStandalone: true, selector: "[transloco]", inputs: { key: ["transloco", "key"], params: ["translocoParams", "params"], inlineScope: ["translocoScope", "inlineScope"], inlineRead: ["translocoRead", "inlineRead"], inlineLang: ["translocoLang", "inlineLang"], inlineTpl: ["translocoLoadingTpl", "inlineTpl"] }, usesOnChanges: true, ngImport: i0 });
|
|
1268
|
+
}
|
|
1269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoDirective, decorators: [{
|
|
1270
|
+
type: Directive,
|
|
1271
|
+
args: [{
|
|
1272
|
+
selector: '[transloco]',
|
|
1273
|
+
standalone: true,
|
|
1274
|
+
}]
|
|
1275
|
+
}], propDecorators: { key: [{
|
|
1276
|
+
type: Input,
|
|
1277
|
+
args: ['transloco']
|
|
1278
|
+
}], params: [{
|
|
1279
|
+
type: Input,
|
|
1280
|
+
args: ['translocoParams']
|
|
1281
|
+
}], inlineScope: [{
|
|
1282
|
+
type: Input,
|
|
1283
|
+
args: ['translocoScope']
|
|
1284
|
+
}], inlineRead: [{
|
|
1285
|
+
type: Input,
|
|
1286
|
+
args: ['translocoRead']
|
|
1287
|
+
}], inlineLang: [{
|
|
1288
|
+
type: Input,
|
|
1289
|
+
args: ['translocoLang']
|
|
1290
|
+
}], inlineTpl: [{
|
|
1291
|
+
type: Input,
|
|
1292
|
+
args: ['translocoLoadingTpl']
|
|
1293
|
+
}] } });
|
|
1294
|
+
|
|
1295
|
+
class TranslocoPipe {
|
|
1296
|
+
service;
|
|
1297
|
+
providerScope;
|
|
1298
|
+
providerLang;
|
|
1299
|
+
cdr;
|
|
1300
|
+
subscription = null;
|
|
1301
|
+
lastValue = '';
|
|
1302
|
+
lastKey;
|
|
1303
|
+
path;
|
|
1304
|
+
langResolver = new LangResolver();
|
|
1305
|
+
scopeResolver;
|
|
1306
|
+
constructor(service, providerScope, providerLang, cdr) {
|
|
1307
|
+
this.service = service;
|
|
1308
|
+
this.providerScope = providerScope;
|
|
1309
|
+
this.providerLang = providerLang;
|
|
1310
|
+
this.cdr = cdr;
|
|
1311
|
+
this.scopeResolver = new ScopeResolver(this.service);
|
|
1312
|
+
}
|
|
1313
|
+
// null is for handling strict mode + async pipe types https://github.com/jsverse/transloco/issues/311
|
|
1314
|
+
// null is for handling strict mode + optional chaining types https://github.com/jsverse/transloco/issues/488
|
|
1315
|
+
transform(key, params, inlineLang) {
|
|
1316
|
+
if (!key) {
|
|
1317
|
+
return key;
|
|
1318
|
+
}
|
|
1319
|
+
const keyName = params ? `${key}${JSON.stringify(params)}` : key;
|
|
1320
|
+
if (keyName === this.lastKey) {
|
|
1321
|
+
return this.lastValue;
|
|
1322
|
+
}
|
|
1323
|
+
this.lastKey = keyName;
|
|
1324
|
+
this.subscription?.unsubscribe();
|
|
1325
|
+
const listenToLangChange = shouldListenToLangChanges(this.service, this.providerLang || inlineLang);
|
|
1326
|
+
this.subscription = this.service.langChanges$
|
|
1327
|
+
.pipe(switchMap((activeLang) => {
|
|
1328
|
+
const lang = this.langResolver.resolve({
|
|
1329
|
+
inline: inlineLang,
|
|
1330
|
+
provider: this.providerLang,
|
|
1331
|
+
active: activeLang,
|
|
1332
|
+
});
|
|
1333
|
+
return Array.isArray(this.providerScope)
|
|
1334
|
+
? forkJoin(this.providerScope.map((providerScope) => this.resolveScope(lang, providerScope)))
|
|
1335
|
+
: this.resolveScope(lang, this.providerScope);
|
|
1336
|
+
}), listenOrNotOperator(listenToLangChange))
|
|
1337
|
+
.subscribe(() => this.updateValue(key, params));
|
|
1338
|
+
return this.lastValue;
|
|
1339
|
+
}
|
|
1340
|
+
ngOnDestroy() {
|
|
1341
|
+
this.subscription?.unsubscribe();
|
|
1342
|
+
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
|
|
1343
|
+
// callback within its `destination` property, preventing classes from being GC'd.
|
|
1344
|
+
this.subscription = null;
|
|
1345
|
+
}
|
|
1346
|
+
updateValue(key, params) {
|
|
1347
|
+
const lang = this.langResolver.resolveLangBasedOnScope(this.path);
|
|
1348
|
+
this.lastValue = this.service.translate(key, params, lang);
|
|
1349
|
+
this.cdr.markForCheck();
|
|
1350
|
+
}
|
|
1351
|
+
resolveScope(lang, providerScope) {
|
|
1352
|
+
const resolvedScope = this.scopeResolver.resolve({
|
|
1353
|
+
inline: undefined,
|
|
1354
|
+
provider: providerScope,
|
|
1355
|
+
});
|
|
1356
|
+
this.path = this.langResolver.resolveLangPath(lang, resolvedScope);
|
|
1357
|
+
const inlineLoader = resolveInlineLoader(providerScope, resolvedScope);
|
|
1358
|
+
return this.service._loadDependencies(this.path, inlineLoader);
|
|
1359
|
+
}
|
|
1360
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoPipe, deps: [{ token: TranslocoService }, { token: TRANSLOCO_SCOPE, optional: true }, { token: TRANSLOCO_LANG, optional: true }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1361
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.0.9", ngImport: i0, type: TranslocoPipe, isStandalone: true, name: "transloco", pure: false });
|
|
1362
|
+
}
|
|
1363
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoPipe, decorators: [{
|
|
1364
|
+
type: Pipe,
|
|
1365
|
+
args: [{
|
|
1366
|
+
name: 'transloco',
|
|
1367
|
+
pure: false,
|
|
1368
|
+
standalone: true,
|
|
1369
|
+
}]
|
|
1370
|
+
}], ctorParameters: () => [{ type: TranslocoService }, { type: undefined, decorators: [{
|
|
1371
|
+
type: Optional
|
|
1372
|
+
}, {
|
|
1373
|
+
type: Inject,
|
|
1374
|
+
args: [TRANSLOCO_SCOPE]
|
|
1375
|
+
}] }, { type: undefined, decorators: [{
|
|
1376
|
+
type: Optional
|
|
1377
|
+
}, {
|
|
1378
|
+
type: Inject,
|
|
1379
|
+
args: [TRANSLOCO_LANG]
|
|
1380
|
+
}] }, { type: i0.ChangeDetectorRef }] });
|
|
1381
|
+
|
|
1382
|
+
const decl = [TranslocoDirective, TranslocoPipe];
|
|
1383
|
+
class TranslocoModule {
|
|
1384
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1385
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.0.9", ngImport: i0, type: TranslocoModule, imports: [TranslocoDirective, TranslocoPipe], exports: [TranslocoDirective, TranslocoPipe] });
|
|
1386
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoModule });
|
|
1387
|
+
}
|
|
1388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoModule, decorators: [{
|
|
1389
|
+
type: NgModule,
|
|
1390
|
+
args: [{
|
|
1391
|
+
imports: decl,
|
|
1392
|
+
exports: decl,
|
|
1393
|
+
}]
|
|
1394
|
+
}] });
|
|
1395
|
+
|
|
1396
|
+
function provideTransloco(options) {
|
|
1397
|
+
const providers = [
|
|
1398
|
+
provideTranslocoTranspiler(DefaultTranspiler),
|
|
1399
|
+
provideTranslocoMissingHandler(DefaultHandler),
|
|
1400
|
+
provideTranslocoInterceptor(DefaultInterceptor),
|
|
1401
|
+
provideTranslocoFallbackStrategy(DefaultFallbackStrategy),
|
|
1402
|
+
];
|
|
1403
|
+
if (options.config) {
|
|
1404
|
+
providers.push(provideTranslocoConfig(options.config));
|
|
1405
|
+
}
|
|
1406
|
+
if (options.loader) {
|
|
1407
|
+
providers.push(provideTranslocoLoader(options.loader));
|
|
1408
|
+
}
|
|
1409
|
+
return providers;
|
|
1410
|
+
}
|
|
1411
|
+
function provideTranslocoConfig(config) {
|
|
1412
|
+
return makeEnvironmentProviders([
|
|
1413
|
+
{
|
|
1414
|
+
provide: TRANSLOCO_CONFIG,
|
|
1415
|
+
useValue: translocoConfig(config),
|
|
1416
|
+
},
|
|
1417
|
+
]);
|
|
1418
|
+
}
|
|
1419
|
+
function provideTranslocoLoader(loader) {
|
|
1420
|
+
return makeEnvironmentProviders([
|
|
1421
|
+
{ provide: TRANSLOCO_LOADER, useClass: loader },
|
|
1422
|
+
]);
|
|
1423
|
+
}
|
|
1424
|
+
function provideTranslocoScope(scope) {
|
|
1425
|
+
return {
|
|
1426
|
+
provide: TRANSLOCO_SCOPE,
|
|
1427
|
+
useValue: scope,
|
|
1428
|
+
multi: true,
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function provideTranslocoLoadingTpl(content) {
|
|
1432
|
+
return {
|
|
1433
|
+
provide: TRANSLOCO_LOADING_TEMPLATE,
|
|
1434
|
+
useValue: content,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function provideTranslocoTranspiler(transpiler) {
|
|
1438
|
+
return makeEnvironmentProviders([
|
|
1439
|
+
{
|
|
1440
|
+
provide: TRANSLOCO_TRANSPILER,
|
|
1441
|
+
useClass: transpiler,
|
|
1442
|
+
deps: [TRANSLOCO_CONFIG],
|
|
1443
|
+
},
|
|
1444
|
+
]);
|
|
1445
|
+
}
|
|
1446
|
+
function provideTranslocoFallbackStrategy(strategy) {
|
|
1447
|
+
return makeEnvironmentProviders([
|
|
1448
|
+
{
|
|
1449
|
+
provide: TRANSLOCO_FALLBACK_STRATEGY,
|
|
1450
|
+
useClass: strategy,
|
|
1451
|
+
deps: [TRANSLOCO_CONFIG],
|
|
1452
|
+
},
|
|
1453
|
+
]);
|
|
1454
|
+
}
|
|
1455
|
+
function provideTranslocoMissingHandler(handler) {
|
|
1456
|
+
return makeEnvironmentProviders([
|
|
1457
|
+
{
|
|
1458
|
+
provide: TRANSLOCO_MISSING_HANDLER,
|
|
1459
|
+
useClass: handler,
|
|
1460
|
+
},
|
|
1461
|
+
]);
|
|
1462
|
+
}
|
|
1463
|
+
function provideTranslocoInterceptor(interceptor) {
|
|
1464
|
+
return makeEnvironmentProviders([
|
|
1465
|
+
{
|
|
1466
|
+
provide: TRANSLOCO_INTERCEPTOR,
|
|
1467
|
+
useClass: interceptor,
|
|
1468
|
+
},
|
|
1469
|
+
]);
|
|
1470
|
+
}
|
|
1471
|
+
function provideTranslocoLang(lang) {
|
|
1472
|
+
return {
|
|
1473
|
+
provide: TRANSLOCO_LANG,
|
|
1474
|
+
useValue: lang,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
const TRANSLOCO_TEST_LANGS = new InjectionToken('TRANSLOCO_TEST_LANGS - Available testing languages');
|
|
1479
|
+
const TRANSLOCO_TEST_OPTIONS = new InjectionToken('TRANSLOCO_TEST_OPTIONS - Testing options');
|
|
1480
|
+
class TestingLoader {
|
|
1481
|
+
langs;
|
|
1482
|
+
constructor(langs) {
|
|
1483
|
+
this.langs = langs;
|
|
1484
|
+
}
|
|
1485
|
+
getTranslation(lang) {
|
|
1486
|
+
return of(this.langs[lang]);
|
|
1487
|
+
}
|
|
1488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TestingLoader, deps: [{ token: TRANSLOCO_TEST_LANGS }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1489
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TestingLoader });
|
|
1490
|
+
}
|
|
1491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TestingLoader, decorators: [{
|
|
1492
|
+
type: Injectable
|
|
1493
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1494
|
+
type: Inject,
|
|
1495
|
+
args: [TRANSLOCO_TEST_LANGS]
|
|
1496
|
+
}] }] });
|
|
1497
|
+
function initTranslocoService(service, langs = {}, options) {
|
|
1498
|
+
const preloadAllLangs = () => options.preloadLangs
|
|
1499
|
+
? Promise.all(Object.keys(langs).map((lang) => service.load(lang).toPromise()))
|
|
1500
|
+
: Promise.resolve();
|
|
1501
|
+
return preloadAllLangs;
|
|
1502
|
+
}
|
|
1503
|
+
class TranslocoTestingModule {
|
|
1504
|
+
static forRoot(options) {
|
|
1505
|
+
return {
|
|
1506
|
+
ngModule: TranslocoTestingModule,
|
|
1507
|
+
providers: [
|
|
1508
|
+
provideTransloco({
|
|
1509
|
+
loader: TestingLoader,
|
|
1510
|
+
config: {
|
|
1511
|
+
prodMode: true,
|
|
1512
|
+
missingHandler: { logMissingKey: false },
|
|
1513
|
+
...options.translocoConfig,
|
|
1514
|
+
},
|
|
1515
|
+
}),
|
|
1516
|
+
{
|
|
1517
|
+
provide: TRANSLOCO_TEST_LANGS,
|
|
1518
|
+
useValue: options.langs,
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
provide: TRANSLOCO_TEST_OPTIONS,
|
|
1522
|
+
useValue: options,
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
provide: APP_INITIALIZER,
|
|
1526
|
+
useFactory: initTranslocoService,
|
|
1527
|
+
deps: [
|
|
1528
|
+
TranslocoService,
|
|
1529
|
+
TRANSLOCO_TEST_LANGS,
|
|
1530
|
+
TRANSLOCO_TEST_OPTIONS,
|
|
1531
|
+
],
|
|
1532
|
+
multi: true,
|
|
1533
|
+
},
|
|
1534
|
+
],
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1538
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.0.9", ngImport: i0, type: TranslocoTestingModule, exports: [TranslocoModule] });
|
|
1539
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoTestingModule, imports: [TranslocoModule] });
|
|
1540
|
+
}
|
|
1541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.9", ngImport: i0, type: TranslocoTestingModule, decorators: [{
|
|
1542
|
+
type: NgModule,
|
|
1543
|
+
args: [{
|
|
1544
|
+
exports: [TranslocoModule],
|
|
1545
|
+
}]
|
|
1546
|
+
}] });
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Returns the language code name from the browser, e.g. "en"
|
|
1550
|
+
*/
|
|
1551
|
+
function getBrowserLang() {
|
|
1552
|
+
let browserLang = getBrowserCultureLang();
|
|
1553
|
+
if (!browserLang || !isBrowser()) {
|
|
1554
|
+
return undefined;
|
|
1555
|
+
}
|
|
1556
|
+
if (browserLang.indexOf('-') !== -1) {
|
|
1557
|
+
browserLang = browserLang.split('-')[0];
|
|
1558
|
+
}
|
|
1559
|
+
if (browserLang.indexOf('_') !== -1) {
|
|
1560
|
+
browserLang = browserLang.split('_')[0];
|
|
1561
|
+
}
|
|
1562
|
+
return browserLang;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Returns the culture language code name from the browser, e.g. "en-US"
|
|
1566
|
+
*/
|
|
1567
|
+
function getBrowserCultureLang() {
|
|
1568
|
+
if (!isBrowser()) {
|
|
1569
|
+
return '';
|
|
1570
|
+
}
|
|
1571
|
+
const navigator = window.navigator;
|
|
1572
|
+
return navigator.languages?.[0] ?? navigator.language;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/**
|
|
1576
|
+
* Generated bundle index. Do not edit.
|
|
1577
|
+
*/
|
|
1578
|
+
|
|
1579
|
+
export { DefaultFallbackStrategy, DefaultTranspiler, FunctionalTranspiler, TRANSLOCO_CONFIG, TRANSLOCO_FALLBACK_STRATEGY, TRANSLOCO_INTERCEPTOR, TRANSLOCO_LANG, TRANSLOCO_LOADER, TRANSLOCO_LOADING_TEMPLATE, TRANSLOCO_MISSING_HANDLER, TRANSLOCO_SCOPE, TRANSLOCO_TRANSPILER, TestingLoader, TranslocoDirective, TranslocoModule, TranslocoPipe, TranslocoService, TranslocoTestingModule, coerceArray, defaultConfig, flatten, getBrowserCultureLang, getBrowserLang, getFunctionArgs, getLangFromScope, getPipeValue, getScopeFromLang, getValue, hasInlineLoader, isBrowser, isDefined, isEmpty, isFunction, isNil, isNumber, isObject, isScopeArray, isScopeObject, isString, provideTransloco, provideTranslocoConfig, provideTranslocoFallbackStrategy, provideTranslocoInterceptor, provideTranslocoLang, provideTranslocoLoader, provideTranslocoLoadingTpl, provideTranslocoMissingHandler, provideTranslocoScope, provideTranslocoTranspiler, setValue, size, toCamelCase, toNumber, translate, translateObject, translocoConfig, unflatten };
|
|
1580
|
+
//# sourceMappingURL=jsverse-transloco.mjs.map
|