@mmstack/translate 20.5.3 → 20.5.5
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/LICENSE +21 -21
- package/README.md +603 -349
- package/fesm2022/mmstack-translate.mjs +497 -34
- package/fesm2022/mmstack-translate.mjs.map +1 -1
- package/index.d.ts +257 -5
- package/package.json +3 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { computed, inject, InjectionToken, LOCALE_ID, signal, resource, untracked, isDevMode, effect, Injectable, isSignal, input, Renderer2, ElementRef, afterRenderEffect, Directive, ChangeDetectorRef } from '@angular/core';
|
|
3
3
|
import { createIntlCache, createIntl } from '@formatjs/intl';
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
4
6
|
|
|
5
7
|
const KEY_DELIM = '::MMT_DELIM::';
|
|
6
8
|
function prependDelim(prefix, key) {
|
|
@@ -53,28 +55,96 @@ function createNamespace(ns, translation) {
|
|
|
53
55
|
return namespace;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
function pathParam(key, route = inject(ActivatedRoute)) {
|
|
59
|
+
const keySignal = typeof key === 'string' ? computed(() => key) : computed(key);
|
|
60
|
+
const routerOptions = inject(Router)['options'];
|
|
61
|
+
if (routerOptions &&
|
|
62
|
+
typeof routerOptions === 'object' &&
|
|
63
|
+
routerOptions.paramsInheritanceStrategy === 'always') {
|
|
64
|
+
const params = toSignal(route.paramMap, {
|
|
65
|
+
initialValue: route.snapshot.paramMap,
|
|
66
|
+
});
|
|
67
|
+
return computed(() => params().get(keySignal()));
|
|
68
|
+
}
|
|
69
|
+
const paramMapSignals = [];
|
|
70
|
+
let currentRoute = route;
|
|
71
|
+
const isStatic = typeof key === 'string';
|
|
72
|
+
while (currentRoute) {
|
|
73
|
+
const initial = currentRoute.snapshot.paramMap;
|
|
74
|
+
paramMapSignals.push(toSignal(currentRoute.paramMap, {
|
|
75
|
+
initialValue: initial,
|
|
76
|
+
}));
|
|
77
|
+
// For static keys, stop once we find the param, will find first in computed for loop already so basically noop for for loop
|
|
78
|
+
if (isStatic && initial.has(key))
|
|
79
|
+
break;
|
|
80
|
+
currentRoute = currentRoute.parent;
|
|
81
|
+
}
|
|
82
|
+
return computed(() => {
|
|
83
|
+
const paramKey = keySignal();
|
|
84
|
+
for (const map of paramMapSignals) {
|
|
85
|
+
const v = map().get(paramKey);
|
|
86
|
+
if (v)
|
|
87
|
+
return v;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
56
93
|
const CONFIG_TOKEN = new InjectionToken('mmstack-intl-config');
|
|
57
94
|
function provideIntlConfig(config) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
95
|
+
const providers = [
|
|
96
|
+
{
|
|
97
|
+
useFactory: (localeId) => {
|
|
98
|
+
const next = {
|
|
99
|
+
...config,
|
|
100
|
+
};
|
|
101
|
+
const defaultLocale = config.defaultLocale ?? config.supportedLocales?.at(0) ?? localeId;
|
|
102
|
+
if (next.supportedLocales &&
|
|
103
|
+
!next.supportedLocales.includes(defaultLocale)) {
|
|
104
|
+
next.supportedLocales = [...next.supportedLocales, defaultLocale];
|
|
105
|
+
}
|
|
106
|
+
return next;
|
|
107
|
+
},
|
|
108
|
+
deps: [LOCALE_ID],
|
|
109
|
+
provide: CONFIG_TOKEN,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const defaultLocale = config.defaultLocale ?? config.supportedLocales?.at(0);
|
|
113
|
+
if (!defaultLocale)
|
|
114
|
+
return providers;
|
|
115
|
+
providers.push({
|
|
116
|
+
provide: LOCALE_ID,
|
|
117
|
+
useValue: defaultLocale,
|
|
118
|
+
});
|
|
119
|
+
return providers;
|
|
62
120
|
}
|
|
63
121
|
function injectIntlConfig() {
|
|
64
122
|
return inject(CONFIG_TOKEN, { optional: true }) ?? undefined;
|
|
65
123
|
}
|
|
66
124
|
function injectDefaultLocale() {
|
|
67
|
-
return injectIntlConfig()?.defaultLocale ?? 'en-US';
|
|
125
|
+
return injectIntlConfig()?.defaultLocale ?? inject(LOCALE_ID) ?? 'en-US';
|
|
126
|
+
}
|
|
127
|
+
function injectSupportedLocales() {
|
|
128
|
+
return injectIntlConfig()?.supportedLocales ?? [injectDefaultLocale()];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @internal
|
|
132
|
+
* the actual locale signal used to store the current locale string
|
|
133
|
+
*/
|
|
134
|
+
const STORE_LOCALE = signal('en-US', ...(ngDevMode ? [{ debugName: "STORE_LOCALE" }] : []));
|
|
135
|
+
function injectLocaleInternal() {
|
|
136
|
+
return STORE_LOCALE;
|
|
68
137
|
}
|
|
69
138
|
class TranslationStore {
|
|
70
139
|
cache = createIntlCache();
|
|
71
140
|
config = injectIntlConfig();
|
|
72
141
|
loadQueue = signal([], ...(ngDevMode ? [{ debugName: "loadQueue" }] : []));
|
|
73
|
-
locale
|
|
142
|
+
locale;
|
|
74
143
|
defaultLocale = injectDefaultLocale();
|
|
75
144
|
translations = signal({
|
|
76
145
|
[this.defaultLocale]: {},
|
|
77
146
|
}, ...(ngDevMode ? [{ debugName: "translations" }] : []));
|
|
147
|
+
attemptedFallbackLoad = false;
|
|
78
148
|
onDemandLoaders = new Map();
|
|
79
149
|
nonMessageConfig = computed(() => ({
|
|
80
150
|
...this.config,
|
|
@@ -127,6 +197,23 @@ class TranslationStore {
|
|
|
127
197
|
messages: this.messages(),
|
|
128
198
|
}, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : []));
|
|
129
199
|
constructor() {
|
|
200
|
+
this.locale = STORE_LOCALE;
|
|
201
|
+
this.locale.set(injectDefaultLocale());
|
|
202
|
+
const paramName = this.config?.localeParamName;
|
|
203
|
+
if (paramName) {
|
|
204
|
+
const param = pathParam(paramName);
|
|
205
|
+
effect(() => {
|
|
206
|
+
const loc = param();
|
|
207
|
+
if (!loc ||
|
|
208
|
+
loc === untracked(this.locale) ||
|
|
209
|
+
untracked(this.loadQueue).includes(loc))
|
|
210
|
+
return;
|
|
211
|
+
if (this.hasLocaleLoaders(loc))
|
|
212
|
+
this.locale.set(loc);
|
|
213
|
+
else
|
|
214
|
+
this.loadQueue.update((q) => [...q, loc]);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
130
217
|
effect(() => {
|
|
131
218
|
if (
|
|
132
219
|
// should never be in error state, but best to check in case something throws
|
|
@@ -136,19 +223,34 @@ class TranslationStore {
|
|
|
136
223
|
const dynamicLocales = this.dynamicLocaleLoader.value();
|
|
137
224
|
if (!dynamicLocales)
|
|
138
225
|
return;
|
|
226
|
+
// Register loaded translations
|
|
139
227
|
for (const locale of dynamicLocales.locales) {
|
|
140
228
|
this.register(locale.namespace, {
|
|
141
229
|
[dynamicLocales.locale]: locale.flat,
|
|
142
230
|
});
|
|
143
231
|
}
|
|
144
|
-
|
|
145
|
-
|
|
232
|
+
const hasTranslations = dynamicLocales.locales.length > 0 ||
|
|
233
|
+
this.translations()[dynamicLocales.locale];
|
|
234
|
+
if (hasTranslations) {
|
|
235
|
+
this.loadQueue.update((q) => q.filter((l) => l !== dynamicLocales.locale));
|
|
236
|
+
this.locale.set(dynamicLocales.locale);
|
|
237
|
+
}
|
|
146
238
|
});
|
|
147
239
|
}
|
|
148
240
|
formatMessage(key, values) {
|
|
149
|
-
const message = this.translations()[this.locale()]?.[key] ??
|
|
150
|
-
|
|
241
|
+
const message = this.translations()[this.locale()]?.[key] ??
|
|
242
|
+
this.translations()[this.defaultLocale]?.[key] ??
|
|
243
|
+
'';
|
|
244
|
+
if (!message) {
|
|
245
|
+
if (this.attemptedFallbackLoad)
|
|
246
|
+
return '';
|
|
247
|
+
this.attemptedFallbackLoad = true;
|
|
248
|
+
untracked(() => {
|
|
249
|
+
if (!this.loadQueue().includes(this.defaultLocale))
|
|
250
|
+
this.loadQueue.update((q) => [...q, this.defaultLocale]);
|
|
251
|
+
});
|
|
151
252
|
return '';
|
|
253
|
+
}
|
|
152
254
|
return this.intl().formatMessage({ id: key, defaultMessage: message }, values);
|
|
153
255
|
}
|
|
154
256
|
register(namespace, flat) {
|
|
@@ -173,10 +275,10 @@ class TranslationStore {
|
|
|
173
275
|
hasLocaleLoaders(locale) {
|
|
174
276
|
return Array.from(this.onDemandLoaders.values()).some((loaders) => loaders[locale]);
|
|
175
277
|
}
|
|
176
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
177
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
278
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TranslationStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
279
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TranslationStore, providedIn: 'root' });
|
|
178
280
|
}
|
|
179
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
281
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TranslationStore, decorators: [{
|
|
180
282
|
type: Injectable,
|
|
181
283
|
args: [{
|
|
182
284
|
providedIn: 'root',
|
|
@@ -205,15 +307,23 @@ function injectIntl() {
|
|
|
205
307
|
* ```
|
|
206
308
|
*/
|
|
207
309
|
function injectDynamicLocale() {
|
|
208
|
-
const defaultLocale = injectDefaultLocale();
|
|
209
310
|
const store = inject(TranslationStore);
|
|
311
|
+
const supportedLocales = injectIntlConfig()?.supportedLocales;
|
|
210
312
|
const source = computed(() => store.locale());
|
|
313
|
+
const inSupportedLocales = supportedLocales === undefined
|
|
314
|
+
? () => true
|
|
315
|
+
: (locale) => supportedLocales.includes(locale);
|
|
211
316
|
const set = (value) => {
|
|
212
|
-
const isDefaultOrHasLoaders = value === defaultLocale || store.hasLocaleLoaders(value);
|
|
213
317
|
if (value === untracked(source) ||
|
|
214
|
-
!isDefaultOrHasLoaders ||
|
|
215
318
|
untracked(store.loadQueue).includes(value))
|
|
216
319
|
return;
|
|
320
|
+
if (!inSupportedLocales(value)) {
|
|
321
|
+
if (isDevMode())
|
|
322
|
+
console.warn(`[Translate] Locale "${value}" is not in supportedLocales, switch prevented. Available options are:`, supportedLocales);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (isDevMode() && !store.hasLocaleLoaders(value))
|
|
326
|
+
console.warn(`[Translate] No loaders registered for locale "${value}". Switching to this locale will have no effect.`);
|
|
217
327
|
store.loadQueue.update((q) => [...q, value]);
|
|
218
328
|
};
|
|
219
329
|
source.set = set;
|
|
@@ -226,6 +336,243 @@ function injectDynamicLocale() {
|
|
|
226
336
|
return source;
|
|
227
337
|
}
|
|
228
338
|
|
|
339
|
+
function unwrap(value) {
|
|
340
|
+
return isSignal(value) ? value() : value;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const FORMAT_PRESETS = {
|
|
344
|
+
short: { dateStyle: 'short', timeStyle: 'short' },
|
|
345
|
+
medium: { dateStyle: 'medium', timeStyle: 'medium' },
|
|
346
|
+
long: { dateStyle: 'long', timeStyle: 'long' },
|
|
347
|
+
full: { dateStyle: 'full', timeStyle: 'full' },
|
|
348
|
+
shortDate: { dateStyle: 'short' },
|
|
349
|
+
mediumDate: { dateStyle: 'medium' },
|
|
350
|
+
longDate: { dateStyle: 'long' },
|
|
351
|
+
fullDate: { dateStyle: 'full' },
|
|
352
|
+
shortTime: { timeStyle: 'short' },
|
|
353
|
+
mediumTime: { timeStyle: 'medium' },
|
|
354
|
+
longTime: { timeStyle: 'long' },
|
|
355
|
+
fullTime: { timeStyle: 'full' },
|
|
356
|
+
};
|
|
357
|
+
function validDateOrNull(date) {
|
|
358
|
+
if (date == null)
|
|
359
|
+
return null;
|
|
360
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
361
|
+
return isNaN(d.getTime()) ? null : d;
|
|
362
|
+
}
|
|
363
|
+
const cache$4 = new Map();
|
|
364
|
+
function getFormatter$4(locale, format, timeZone) {
|
|
365
|
+
const cacheKey = `${locale}|${format}|${timeZone ?? ''}`;
|
|
366
|
+
let formatter = cache$4.get(cacheKey);
|
|
367
|
+
if (!formatter) {
|
|
368
|
+
formatter = new Intl.DateTimeFormat(locale, {
|
|
369
|
+
...FORMAT_PRESETS[format],
|
|
370
|
+
timeZone,
|
|
371
|
+
});
|
|
372
|
+
cache$4.set(cacheKey, formatter);
|
|
373
|
+
}
|
|
374
|
+
return formatter;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Format a date using the current or provided locale & timezone
|
|
378
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
379
|
+
*
|
|
380
|
+
* @param date - Date to format
|
|
381
|
+
* @param opt - Options for formatting
|
|
382
|
+
* @returns Formatted date string
|
|
383
|
+
*/
|
|
384
|
+
function formatDate(date, opt) {
|
|
385
|
+
const validDate = validDateOrNull(unwrap(date));
|
|
386
|
+
if (validDate === null)
|
|
387
|
+
return '';
|
|
388
|
+
const unwrappedOpt = unwrap(opt);
|
|
389
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
390
|
+
return getFormatter$4(loc, unwrappedOpt?.format ?? 'medium', unwrappedOpt?.tz).format(validDate);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const cache$3 = new Map();
|
|
394
|
+
function getFormatter$3(locale, type, style) {
|
|
395
|
+
const cacheKey = `${locale}|${type}|${style}`;
|
|
396
|
+
let formatter = cache$3.get(cacheKey);
|
|
397
|
+
if (!formatter) {
|
|
398
|
+
formatter = new Intl.DisplayNames(locale, {
|
|
399
|
+
type,
|
|
400
|
+
style,
|
|
401
|
+
});
|
|
402
|
+
cache$3.set(cacheKey, formatter);
|
|
403
|
+
}
|
|
404
|
+
return formatter;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Format a display name using the current or provided locale
|
|
408
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
409
|
+
*
|
|
410
|
+
* @param value - The code to format
|
|
411
|
+
* @param type - The type of display name to format
|
|
412
|
+
* @param opt - Options for formatting
|
|
413
|
+
* @returns Formatted display name string
|
|
414
|
+
*/
|
|
415
|
+
function formatDisplayName(value, type, opt) {
|
|
416
|
+
const unwrapped = unwrap(value);
|
|
417
|
+
if (!unwrapped?.trim())
|
|
418
|
+
return '';
|
|
419
|
+
const unwrappedType = unwrap(type);
|
|
420
|
+
const unwrappedOpt = unwrap(opt);
|
|
421
|
+
const locale = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
422
|
+
return (getFormatter$3(locale, unwrappedType, unwrappedOpt?.style ?? 'long').of(unwrapped) ?? '');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const cache$2 = new Map();
|
|
426
|
+
const EMPTY_ARRAY = [];
|
|
427
|
+
function unwrapList(value) {
|
|
428
|
+
const unwrapped = unwrap(value);
|
|
429
|
+
return Array.isArray(unwrapped) ? unwrapped : EMPTY_ARRAY;
|
|
430
|
+
}
|
|
431
|
+
function getFormatter$2(locale, type, style) {
|
|
432
|
+
const cacheKey = `${locale}|${type}|${style}`;
|
|
433
|
+
let formatter = cache$2.get(cacheKey);
|
|
434
|
+
if (!formatter) {
|
|
435
|
+
formatter = new Intl.ListFormat(locale, { type, style });
|
|
436
|
+
cache$2.set(cacheKey, formatter);
|
|
437
|
+
}
|
|
438
|
+
return formatter;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Format a list using the current or provided locale
|
|
442
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
443
|
+
*
|
|
444
|
+
* @param value - The list to format
|
|
445
|
+
* @param opt - Options for formatting
|
|
446
|
+
* @returns Formatted list string
|
|
447
|
+
*/
|
|
448
|
+
function formatList(value, opt) {
|
|
449
|
+
const unwrapped = unwrapList(value);
|
|
450
|
+
if (unwrapped.length === 0)
|
|
451
|
+
return '';
|
|
452
|
+
const unwrappedOpt = unwrap(opt);
|
|
453
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
454
|
+
return getFormatter$2(loc, unwrappedOpt?.type ?? 'conjunction', unwrappedOpt?.style ?? 'long').format(unwrapped);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const cache$1 = new Map();
|
|
458
|
+
function unwrapValue(value, fallbackToZero = false) {
|
|
459
|
+
const unwrapped = unwrap(value);
|
|
460
|
+
if (unwrapped === null || unwrapped === undefined || isNaN(unwrapped))
|
|
461
|
+
return fallbackToZero ? 0 : null;
|
|
462
|
+
return unwrapped;
|
|
463
|
+
}
|
|
464
|
+
function getFormatter$1(locale, minFractionDigits, maxFractionDigits, useGrouping, notation, currency, display, style) {
|
|
465
|
+
const cacheKey = `${locale}|${notation ?? 'standard'}|${minFractionDigits}|${maxFractionDigits}|${useGrouping ?? true}|${currency ?? 'none'}|${display ?? 'none'}|${style ?? 'decimal'}`;
|
|
466
|
+
let formatter = cache$1.get(cacheKey);
|
|
467
|
+
if (!formatter) {
|
|
468
|
+
formatter = new Intl.NumberFormat(locale, {
|
|
469
|
+
style,
|
|
470
|
+
notation,
|
|
471
|
+
minimumFractionDigits: minFractionDigits,
|
|
472
|
+
maximumFractionDigits: maxFractionDigits,
|
|
473
|
+
useGrouping,
|
|
474
|
+
});
|
|
475
|
+
cache$1.set(cacheKey, formatter);
|
|
476
|
+
}
|
|
477
|
+
return formatter;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Format a number using the current or provided locale
|
|
481
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
482
|
+
*
|
|
483
|
+
* @param number - Number to format
|
|
484
|
+
* @param opt - Options for formatting
|
|
485
|
+
* @returns Formatted number string
|
|
486
|
+
*/
|
|
487
|
+
function formatNumber(value, opt) {
|
|
488
|
+
const unwrappedOpt = unwrap(opt);
|
|
489
|
+
const unwrappedNumber = unwrapValue(value, unwrappedOpt?.fallbackToZero);
|
|
490
|
+
if (unwrappedNumber === null)
|
|
491
|
+
return '';
|
|
492
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
493
|
+
return getFormatter$1(loc, unwrappedOpt?.minFractionDigits ?? 0, unwrappedOpt?.maxFractionDigits ?? 0, unwrappedOpt?.useGrouping ?? true, unwrappedOpt?.notation ?? 'standard').format(unwrappedNumber);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Format a percentage using the current or provided locale
|
|
497
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
498
|
+
*
|
|
499
|
+
* @param number - Number to format
|
|
500
|
+
* @param opt - Options for formatting
|
|
501
|
+
* @returns Formatted percentage string
|
|
502
|
+
*/
|
|
503
|
+
function formatPercent(value, opt) {
|
|
504
|
+
const unwrappedOpt = unwrap(opt);
|
|
505
|
+
const unwrappedNumber = unwrapValue(value, unwrappedOpt?.fallbackToZero);
|
|
506
|
+
if (unwrappedNumber === null)
|
|
507
|
+
return '';
|
|
508
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
509
|
+
return getFormatter$1(loc, unwrappedOpt?.minFractionDigits ?? 0, unwrappedOpt?.maxFractionDigits ?? 0, undefined, undefined, undefined, undefined, 'percent').format(unwrappedNumber);
|
|
510
|
+
}
|
|
511
|
+
function formatCurrency(value, currency, opt) {
|
|
512
|
+
const unwrappedOpt = unwrap(opt);
|
|
513
|
+
const unwrappedValue = unwrapValue(value, unwrappedOpt?.fallbackToZero);
|
|
514
|
+
if (unwrappedValue === null)
|
|
515
|
+
return '';
|
|
516
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
517
|
+
return getFormatter$1(loc, 0, 0, undefined, undefined, unwrap(currency), unwrappedOpt?.display ?? 'symbol', 'currency').format(unwrappedValue);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const cache = new Map();
|
|
521
|
+
function getFormatter(locale, style, numeric) {
|
|
522
|
+
const cacheKey = `${locale}|${style}|${numeric}`;
|
|
523
|
+
let formatter = cache.get(cacheKey);
|
|
524
|
+
if (!formatter) {
|
|
525
|
+
formatter = new Intl.RelativeTimeFormat(locale, { style, numeric });
|
|
526
|
+
cache.set(cacheKey, formatter);
|
|
527
|
+
}
|
|
528
|
+
return formatter;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Format a relative time using the current or provided locale
|
|
532
|
+
* By default it is reactive to the global dynamic locale, works best when wrapped in a computed() if you need to react to locale changes
|
|
533
|
+
*
|
|
534
|
+
* @param value - The numeric value to use in the relative time internationalization message
|
|
535
|
+
* @param unit - The unit to use in the relative time internationalization message
|
|
536
|
+
* @param opt - Options for formatting
|
|
537
|
+
* @returns Formatted relative time string
|
|
538
|
+
*/
|
|
539
|
+
function formatRelativeTime(value, unit, opt) {
|
|
540
|
+
const unwrappedValue = unwrap(value);
|
|
541
|
+
if (unwrappedValue === null ||
|
|
542
|
+
unwrappedValue === undefined ||
|
|
543
|
+
isNaN(unwrappedValue))
|
|
544
|
+
return '';
|
|
545
|
+
const unwrappedUnit = unwrap(unit);
|
|
546
|
+
if (!unwrappedUnit)
|
|
547
|
+
return '';
|
|
548
|
+
const unwrappedOpt = unwrap(opt);
|
|
549
|
+
const loc = unwrappedOpt?.locale ?? injectLocaleInternal()();
|
|
550
|
+
return getFormatter(loc, unwrappedOpt?.style ?? 'long', unwrappedOpt?.numeric ?? 'always').format(unwrappedValue, unwrappedUnit);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function injectResolveParamLocale(snapshot) {
|
|
554
|
+
let locale = null;
|
|
555
|
+
const paramName = injectIntlConfig()?.localeParamName;
|
|
556
|
+
const routerConfig = inject(Router)['options'];
|
|
557
|
+
const alwaysInheritParams = typeof routerConfig === 'object' &&
|
|
558
|
+
!!routerConfig &&
|
|
559
|
+
routerConfig.paramsInheritanceStrategy === 'always';
|
|
560
|
+
if (paramName) {
|
|
561
|
+
locale = snapshot.paramMap.get(paramName);
|
|
562
|
+
if (!locale && !alwaysInheritParams) {
|
|
563
|
+
let currentRoute = snapshot;
|
|
564
|
+
while (currentRoute && !locale) {
|
|
565
|
+
locale = currentRoute.paramMap.get('locale');
|
|
566
|
+
currentRoute = currentRoute.parent;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!locale) {
|
|
571
|
+
locale = untracked(inject(TranslationStore).locale);
|
|
572
|
+
}
|
|
573
|
+
return locale;
|
|
574
|
+
}
|
|
575
|
+
|
|
229
576
|
function createEqualsRecord(keys = []) {
|
|
230
577
|
let keyMatcher;
|
|
231
578
|
if (keys.length === 0) {
|
|
@@ -278,9 +625,11 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
278
625
|
return addSignalFn(createT(store), store);
|
|
279
626
|
};
|
|
280
627
|
let defaultTranslationLoaded = false;
|
|
281
|
-
const resolver = async () => {
|
|
628
|
+
const resolver = async (snapshot) => {
|
|
282
629
|
const store = inject(TranslationStore);
|
|
283
|
-
const locale =
|
|
630
|
+
const locale = injectResolveParamLocale(snapshot);
|
|
631
|
+
const defaultLocale = injectDefaultLocale();
|
|
632
|
+
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
284
633
|
const tPromise = other[locale];
|
|
285
634
|
const promise = tPromise ?? defaultTranslation;
|
|
286
635
|
if (!promise && isDevMode()) {
|
|
@@ -289,25 +638,110 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
289
638
|
if (promise === defaultTranslation && defaultTranslationLoaded)
|
|
290
639
|
return;
|
|
291
640
|
try {
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
641
|
+
const promises = [promise()];
|
|
642
|
+
if (shouldPreloadDefault &&
|
|
643
|
+
!defaultTranslationLoaded &&
|
|
644
|
+
promise !== defaultTranslation)
|
|
645
|
+
promises.push(defaultTranslation());
|
|
646
|
+
const translations = await Promise.allSettled(promises);
|
|
647
|
+
const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
648
|
+
if (fullfilled.at(0) === null && fullfilled.at(1) === null)
|
|
649
|
+
throw new Error('Failed to load translations');
|
|
650
|
+
const [t, defaultT] = fullfilled;
|
|
651
|
+
const ns = t?.namespace ?? defaultT?.namespace;
|
|
652
|
+
if (!ns)
|
|
653
|
+
throw new Error('No namespace found in translation');
|
|
654
|
+
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
655
|
+
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
656
|
+
store.registerOnDemandLoaders(ns, {
|
|
657
|
+
...other,
|
|
658
|
+
[defaultLocale]: defaultTranslation,
|
|
301
659
|
});
|
|
302
|
-
|
|
660
|
+
const toRegister = {};
|
|
661
|
+
if (t)
|
|
662
|
+
toRegister[locale] = t.flat;
|
|
663
|
+
if (defaultT)
|
|
664
|
+
toRegister[defaultLocale] = defaultT.flat;
|
|
665
|
+
store.register(ns, toRegister);
|
|
666
|
+
if (promise === defaultTranslation || defaultT)
|
|
303
667
|
defaultTranslationLoaded = true;
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
if (isDevMode()) {
|
|
671
|
+
console.warn(`Failed to load translation for locale: ${locale}`);
|
|
304
672
|
}
|
|
305
673
|
}
|
|
674
|
+
finally {
|
|
675
|
+
if (locale !== untracked(store.locale))
|
|
676
|
+
store.locale.set(locale);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
return {
|
|
680
|
+
injectNamespaceT: injectT,
|
|
681
|
+
resolveNamespaceTranslation: resolver,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Registers a type-unsafe namespace, meant for remote loading of unknown key-value pairs using mmstack/translate infrastructure
|
|
686
|
+
* The resolver & t function work the same as they would with typed namespaces, but without type safety
|
|
687
|
+
*/
|
|
688
|
+
function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
689
|
+
const injectT = () => {
|
|
690
|
+
const store = inject(TranslationStore);
|
|
691
|
+
return addSignalFn(createT(store), store);
|
|
692
|
+
};
|
|
693
|
+
let defaultTranslationLoaded = false;
|
|
694
|
+
const resolver = async (snapshot) => {
|
|
695
|
+
const store = inject(TranslationStore);
|
|
696
|
+
const locale = injectResolveParamLocale(snapshot);
|
|
697
|
+
const defaultLocale = injectDefaultLocale();
|
|
698
|
+
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
699
|
+
const tPromise = other[locale];
|
|
700
|
+
const promise = tPromise ?? defaultTranslation;
|
|
701
|
+
if (!promise && isDevMode()) {
|
|
702
|
+
return console.warn(`No translation found for locale: ${locale}`);
|
|
703
|
+
}
|
|
704
|
+
if (promise === defaultTranslation && defaultTranslationLoaded)
|
|
705
|
+
return;
|
|
706
|
+
try {
|
|
707
|
+
const promises = [promise()];
|
|
708
|
+
if (shouldPreloadDefault &&
|
|
709
|
+
!defaultTranslationLoaded &&
|
|
710
|
+
promise !== defaultTranslation)
|
|
711
|
+
promises.push(defaultTranslation());
|
|
712
|
+
const translations = await Promise.allSettled(promises);
|
|
713
|
+
const fullfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
714
|
+
if (fullfilled.at(0) === null && fullfilled.at(1) === null)
|
|
715
|
+
throw new Error('Failed to load translations');
|
|
716
|
+
const [baseT, baseDefaultT] = fullfilled;
|
|
717
|
+
const t = baseT ? compileTranslation(baseT, ns, locale) : null;
|
|
718
|
+
const defaultT = baseDefaultT
|
|
719
|
+
? compileTranslation(baseDefaultT, ns, defaultLocale)
|
|
720
|
+
: null;
|
|
721
|
+
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
722
|
+
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
723
|
+
store.registerOnDemandLoaders(ns, {
|
|
724
|
+
...other,
|
|
725
|
+
[defaultLocale]: defaultTranslation,
|
|
726
|
+
});
|
|
727
|
+
const toRegister = {};
|
|
728
|
+
if (t)
|
|
729
|
+
toRegister[locale] = t.flat;
|
|
730
|
+
if (defaultT)
|
|
731
|
+
toRegister[defaultLocale] = defaultT.flat;
|
|
732
|
+
store.register(ns, toRegister);
|
|
733
|
+
if (promise === defaultTranslation || defaultT)
|
|
734
|
+
defaultTranslationLoaded = true;
|
|
735
|
+
}
|
|
306
736
|
catch {
|
|
307
737
|
if (isDevMode()) {
|
|
308
738
|
console.warn(`Failed to load translation for locale: ${locale}`);
|
|
309
739
|
}
|
|
310
740
|
}
|
|
741
|
+
finally {
|
|
742
|
+
if (locale !== untracked(store.locale))
|
|
743
|
+
store.locale.set(locale);
|
|
744
|
+
}
|
|
311
745
|
};
|
|
312
746
|
return {
|
|
313
747
|
injectNamespaceT: injectT,
|
|
@@ -315,6 +749,35 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
315
749
|
};
|
|
316
750
|
}
|
|
317
751
|
|
|
752
|
+
/**
|
|
753
|
+
* Guard that validates the locale parameter against supported locales.
|
|
754
|
+
* Redirects to default locale if the locale is invalid.
|
|
755
|
+
*
|
|
756
|
+
* @param prefixSegments Optional array of path segments preceding the locale segment.
|
|
757
|
+
* if (you wanted to match /app/:locale/... you would pass ['app'] here) & the function would match the second parameter + redirect accordingly
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```typescript
|
|
761
|
+
* {
|
|
762
|
+
* path: ':locale',
|
|
763
|
+
* canMatch: [canMatchLocale()],
|
|
764
|
+
* children: [...]
|
|
765
|
+
* }
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
function canMatchLocale(prefixSegments = []) {
|
|
769
|
+
return (_route, segments) => {
|
|
770
|
+
const supportedLocales = injectSupportedLocales();
|
|
771
|
+
const locale = segments.at(prefixSegments.length)?.path;
|
|
772
|
+
if (!locale || !supportedLocales.includes(locale))
|
|
773
|
+
return inject(Router).createUrlTree([
|
|
774
|
+
...prefixSegments,
|
|
775
|
+
injectDefaultLocale(),
|
|
776
|
+
]);
|
|
777
|
+
return true;
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
318
781
|
class Translate {
|
|
319
782
|
t = createT(inject(TranslationStore));
|
|
320
783
|
translate = input.required(...(ngDevMode ? [{ debugName: "translate" }] : []));
|
|
@@ -360,12 +823,12 @@ class Translate {
|
|
|
360
823
|
},
|
|
361
824
|
});
|
|
362
825
|
}
|
|
363
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
364
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.
|
|
826
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: Translate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
827
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.17", type: Translate, isStandalone: true, inputs: { translate: { classPropertyName: "translate", publicName: "translate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
365
828
|
}
|
|
366
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
829
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: Translate, decorators: [{
|
|
367
830
|
type: Directive
|
|
368
|
-
}], ctorParameters: () => [] });
|
|
831
|
+
}], ctorParameters: () => [], propDecorators: { translate: [{ type: i0.Input, args: [{ isSignal: true, alias: "translate", required: true }] }] } });
|
|
369
832
|
|
|
370
833
|
class Translator {
|
|
371
834
|
store = inject(TranslationStore);
|
|
@@ -387,5 +850,5 @@ class Translator {
|
|
|
387
850
|
* Generated bundle index. Do not edit.
|
|
388
851
|
*/
|
|
389
852
|
|
|
390
|
-
export { Translate, Translator, compileTranslation, createNamespace, injectDynamicLocale, injectIntl, provideIntlConfig, registerNamespace };
|
|
853
|
+
export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectDynamicLocale, injectIntl, injectResolveParamLocale, injectSupportedLocales, provideIntlConfig, registerNamespace, registerRemoteNamespace };
|
|
391
854
|
//# sourceMappingURL=mmstack-translate.mjs.map
|