@mmstack/translate 20.5.11 → 20.5.13
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/README.md +298 -181
- package/fesm2022/mmstack-translate.mjs +484 -96
- package/fesm2022/mmstack-translate.mjs.map +1 -1
- package/index.d.ts +316 -71
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { computed, inject, InjectionToken, LOCALE_ID, signal, isDevMode, effect, resource,
|
|
2
|
+
import { computed, inject, InjectionToken, LOCALE_ID, signal, untracked, isDevMode, effect, resource, Injectable, isSignal, input, Renderer2, ElementRef, afterRenderEffect, Directive, ChangeDetectorRef } from '@angular/core';
|
|
3
3
|
import { createIntlCache, createIntl } from '@formatjs/intl';
|
|
4
4
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
@@ -55,6 +55,10 @@ function createNamespace(ns, translation) {
|
|
|
55
55
|
return namespace;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Dynamic path parameter signal, that allows resolution even when paramsInheritenceStrategy isnt always
|
|
60
|
+
* This helper depends on an internal
|
|
61
|
+
*/
|
|
58
62
|
function pathParam(key, route = inject(ActivatedRoute)) {
|
|
59
63
|
const keySignal = typeof key === 'string' ? computed(() => key) : computed(key);
|
|
60
64
|
const routerOptions = inject(Router)['options'];
|
|
@@ -132,9 +136,27 @@ function injectSupportedLocales() {
|
|
|
132
136
|
* the actual locale signal used to store the current locale string
|
|
133
137
|
*/
|
|
134
138
|
const STORE_LOCALE = signal('en-US', ...(ngDevMode ? [{ debugName: "STORE_LOCALE" }] : []));
|
|
139
|
+
/**
|
|
140
|
+
* @internal
|
|
141
|
+
* @deprecated will be removed when ng23 drops
|
|
142
|
+
*/
|
|
143
|
+
function readLocaleUnsafe() {
|
|
144
|
+
return STORE_LOCALE();
|
|
145
|
+
}
|
|
135
146
|
function injectLocaleInternal() {
|
|
136
147
|
return STORE_LOCALE;
|
|
137
148
|
}
|
|
149
|
+
function proxyToGlobalSingleton(src) {
|
|
150
|
+
const originalSet = src.set;
|
|
151
|
+
src.set = (next) => {
|
|
152
|
+
originalSet(next);
|
|
153
|
+
STORE_LOCALE.set(next);
|
|
154
|
+
};
|
|
155
|
+
src.update = (updater) => {
|
|
156
|
+
src.set(updater(untracked(src)));
|
|
157
|
+
};
|
|
158
|
+
return src;
|
|
159
|
+
}
|
|
138
160
|
function isDynamicConfig(cfg) {
|
|
139
161
|
return !!cfg && 'localeStorage' in cfg && !!cfg.localeStorage;
|
|
140
162
|
}
|
|
@@ -232,7 +254,7 @@ class TranslationStore {
|
|
|
232
254
|
messages: this.messages(),
|
|
233
255
|
}, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : []));
|
|
234
256
|
constructor() {
|
|
235
|
-
this.locale = initLocale(
|
|
257
|
+
this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
|
|
236
258
|
const paramName = this.config?.localeParamName;
|
|
237
259
|
if (paramName) {
|
|
238
260
|
const param = pathParam(paramName);
|
|
@@ -426,6 +448,53 @@ function injectAddTranslations() {
|
|
|
426
448
|
};
|
|
427
449
|
}
|
|
428
450
|
|
|
451
|
+
function equalLocale(a, b) {
|
|
452
|
+
return a.locale === b.locale;
|
|
453
|
+
}
|
|
454
|
+
function createFormatterProvider(formatterName, libraryDefaults, nonLocaleEqual) {
|
|
455
|
+
const token = new InjectionToken(`@mmstack/translate:format-${formatterName}-config`, {
|
|
456
|
+
factory: () => {
|
|
457
|
+
const loc = injectDynamicLocale();
|
|
458
|
+
return computed(() => ({
|
|
459
|
+
...libraryDefaults,
|
|
460
|
+
locale: loc(),
|
|
461
|
+
}), { equal: equalLocale });
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
const provider = (valueOrFn) => {
|
|
465
|
+
const fnProvider = typeof valueOrFn === 'function'
|
|
466
|
+
? valueOrFn
|
|
467
|
+
: () => valueOrFn;
|
|
468
|
+
return {
|
|
469
|
+
provide: token,
|
|
470
|
+
useFactory: () => {
|
|
471
|
+
const loc = injectDynamicLocale();
|
|
472
|
+
const providedDefaultsOrSignal = fnProvider();
|
|
473
|
+
if (isSignal(providedDefaultsOrSignal))
|
|
474
|
+
return computed(() => ({
|
|
475
|
+
...libraryDefaults,
|
|
476
|
+
...providedDefaultsOrSignal(),
|
|
477
|
+
locale: loc(),
|
|
478
|
+
}), {
|
|
479
|
+
equal: (a, b) => equalLocale(a, b) && nonLocaleEqual(a, b),
|
|
480
|
+
});
|
|
481
|
+
const defaults = {
|
|
482
|
+
...libraryDefaults,
|
|
483
|
+
...providedDefaultsOrSignal,
|
|
484
|
+
};
|
|
485
|
+
return computed(() => ({
|
|
486
|
+
...defaults,
|
|
487
|
+
locale: loc(),
|
|
488
|
+
}), {
|
|
489
|
+
equal: equalLocale,
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
const injectFn = () => inject(token);
|
|
495
|
+
return [provider, injectFn];
|
|
496
|
+
}
|
|
497
|
+
|
|
429
498
|
function unwrap(value) {
|
|
430
499
|
return isSignal(value) ? value() : value;
|
|
431
500
|
}
|
|
@@ -452,32 +521,68 @@ function validDateOrNull(date) {
|
|
|
452
521
|
}
|
|
453
522
|
const cache$4 = new Map();
|
|
454
523
|
function getFormatter$4(locale, format, timeZone) {
|
|
455
|
-
const cacheKey = `${locale}|${format}|${timeZone ?? ''}`;
|
|
524
|
+
const cacheKey = `${locale}|${typeof format === 'string' ? format : JSON.stringify(format)}|${timeZone ?? ''}`;
|
|
456
525
|
let formatter = cache$4.get(cacheKey);
|
|
457
526
|
if (!formatter) {
|
|
458
527
|
formatter = new Intl.DateTimeFormat(locale, {
|
|
459
|
-
...FORMAT_PRESETS[format],
|
|
528
|
+
...(typeof format === 'string' ? FORMAT_PRESETS[format] : format),
|
|
460
529
|
timeZone,
|
|
461
530
|
});
|
|
462
531
|
cache$4.set(cacheKey, formatter);
|
|
463
532
|
}
|
|
464
533
|
return formatter;
|
|
465
534
|
}
|
|
466
|
-
|
|
467
|
-
* Format a date using the current or provided locale & timezone
|
|
468
|
-
* 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
|
|
469
|
-
*
|
|
470
|
-
* @param date - Date to format
|
|
471
|
-
* @param opt - Options for formatting
|
|
472
|
-
* @returns Formatted date string
|
|
473
|
-
*/
|
|
474
|
-
function formatDate(date, opt) {
|
|
535
|
+
function formatDate(date, optOrLocale) {
|
|
475
536
|
const validDate = validDateOrNull(unwrap(date));
|
|
476
537
|
if (validDate === null)
|
|
477
538
|
return '';
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
539
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
540
|
+
let locale;
|
|
541
|
+
let format = 'medium';
|
|
542
|
+
let tz;
|
|
543
|
+
if (typeof unwrappedArgs === 'string') {
|
|
544
|
+
locale = unwrappedArgs;
|
|
545
|
+
}
|
|
546
|
+
else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
|
|
547
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
548
|
+
format = unwrappedArgs.format ?? 'medium';
|
|
549
|
+
tz = unwrappedArgs.tz;
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
locale = readLocaleUnsafe();
|
|
553
|
+
}
|
|
554
|
+
return getFormatter$4(locale, format, tz).format(validDate);
|
|
555
|
+
}
|
|
556
|
+
const [provideFormatDateDefaults, injectFormatDateOptions] = createFormatterProvider('date', {
|
|
557
|
+
format: 'medium',
|
|
558
|
+
}, (a, b) => {
|
|
559
|
+
if (a.tz !== b.tz)
|
|
560
|
+
return false;
|
|
561
|
+
if (a.format === b.format)
|
|
562
|
+
return true;
|
|
563
|
+
return JSON.stringify(a.format) === JSON.stringify(b.format);
|
|
564
|
+
});
|
|
565
|
+
/**
|
|
566
|
+
* Inject a context-safe date formatting function tied to the current injector.
|
|
567
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
568
|
+
* @example
|
|
569
|
+
* const formatDate = injectFormatDate();
|
|
570
|
+
* readonly displayDate = computed(() => formatDate(this.date()));
|
|
571
|
+
*/
|
|
572
|
+
function injectFormatDate() {
|
|
573
|
+
const defaults = injectFormatDateOptions();
|
|
574
|
+
return (date, optOrLocale) => {
|
|
575
|
+
if (!optOrLocale)
|
|
576
|
+
return formatDate(date, defaults());
|
|
577
|
+
const unwrapped = unwrap(optOrLocale);
|
|
578
|
+
const opt = typeof unwrapped === 'object'
|
|
579
|
+
? { ...defaults(), ...unwrapped }
|
|
580
|
+
: {
|
|
581
|
+
...defaults(),
|
|
582
|
+
locale: unwrapped,
|
|
583
|
+
};
|
|
584
|
+
return formatDate(date, opt);
|
|
585
|
+
};
|
|
481
586
|
}
|
|
482
587
|
|
|
483
588
|
const cache$3 = new Map();
|
|
@@ -495,21 +600,48 @@ function getFormatter$3(locale, type, style) {
|
|
|
495
600
|
}
|
|
496
601
|
/**
|
|
497
602
|
* Format a display name using the current or provided locale
|
|
498
|
-
* 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
|
|
499
603
|
*
|
|
500
604
|
* @param value - The code to format
|
|
501
605
|
* @param type - The type of display name to format
|
|
502
606
|
* @param opt - Options for formatting
|
|
503
607
|
* @returns Formatted display name string
|
|
504
608
|
*/
|
|
505
|
-
function formatDisplayName(value, type,
|
|
506
|
-
const
|
|
507
|
-
if (!
|
|
609
|
+
function formatDisplayName(value, type, localeOrOpt) {
|
|
610
|
+
const unwrappedValue = unwrap(value);
|
|
611
|
+
if (!unwrappedValue?.trim())
|
|
508
612
|
return '';
|
|
509
613
|
const unwrappedType = unwrap(type);
|
|
510
|
-
const
|
|
511
|
-
const locale =
|
|
512
|
-
|
|
614
|
+
const unwrapped = unwrap(localeOrOpt);
|
|
615
|
+
const locale = typeof unwrapped === 'string'
|
|
616
|
+
? unwrapped
|
|
617
|
+
: (unwrapped?.locale ?? readLocaleUnsafe());
|
|
618
|
+
const opt = typeof unwrapped === 'object' ? unwrapped : undefined;
|
|
619
|
+
return (getFormatter$3(locale, unwrappedType, opt?.style ?? 'long').of(unwrappedValue) ?? '');
|
|
620
|
+
}
|
|
621
|
+
const [provideFormatDisplayNameDefaults, injectFormatDisplayNameDefaults] = createFormatterProvider('displayName', {
|
|
622
|
+
style: 'long',
|
|
623
|
+
}, (a, b) => a.style === b.style);
|
|
624
|
+
/**
|
|
625
|
+
* Inject a context-safe date formatting function tied to the current injector.
|
|
626
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
627
|
+
* @example
|
|
628
|
+
* const formatDisplayName = injectFormatDisplayName();
|
|
629
|
+
* readonly region = computed(() => formatDisplayName('US', 'region'));
|
|
630
|
+
*/
|
|
631
|
+
function injectFormatDisplayName() {
|
|
632
|
+
const defaults = injectFormatDisplayNameDefaults();
|
|
633
|
+
return (value, type, localeOrOpt) => {
|
|
634
|
+
if (!localeOrOpt)
|
|
635
|
+
return formatDisplayName(value, type, defaults());
|
|
636
|
+
const unwrapped = unwrap(localeOrOpt);
|
|
637
|
+
const opt = typeof unwrapped === 'object'
|
|
638
|
+
? { ...defaults(), ...unwrapped }
|
|
639
|
+
: {
|
|
640
|
+
...defaults(),
|
|
641
|
+
locale: unwrapped,
|
|
642
|
+
};
|
|
643
|
+
return formatDisplayName(value, type, opt);
|
|
644
|
+
};
|
|
513
645
|
}
|
|
514
646
|
|
|
515
647
|
const cache$2 = new Map();
|
|
@@ -527,21 +659,52 @@ function getFormatter$2(locale, type, style) {
|
|
|
527
659
|
}
|
|
528
660
|
return formatter;
|
|
529
661
|
}
|
|
662
|
+
function formatList(value, optOrLocale) {
|
|
663
|
+
const unwrappedValue = unwrapList(value);
|
|
664
|
+
if (unwrappedValue.length === 0)
|
|
665
|
+
return '';
|
|
666
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
667
|
+
let locale;
|
|
668
|
+
let type = 'conjunction';
|
|
669
|
+
let style = 'long';
|
|
670
|
+
if (typeof unwrappedArgs === 'string') {
|
|
671
|
+
locale = unwrappedArgs;
|
|
672
|
+
}
|
|
673
|
+
else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
|
|
674
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
675
|
+
type = unwrappedArgs.type ?? 'conjunction';
|
|
676
|
+
style = unwrappedArgs.style ?? 'long';
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
locale = readLocaleUnsafe();
|
|
680
|
+
}
|
|
681
|
+
return getFormatter$2(locale, type, style).format(unwrappedValue);
|
|
682
|
+
}
|
|
683
|
+
const [provideFormatListDefaults, injectFormatListOptions] = createFormatterProvider('list', {
|
|
684
|
+
type: 'conjunction',
|
|
685
|
+
style: 'long',
|
|
686
|
+
}, (a, b) => a.type === b.type && a.style === b.style);
|
|
530
687
|
/**
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* @returns Formatted list string
|
|
688
|
+
* Inject a context-safe list formatting function tied to the current injector.
|
|
689
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
690
|
+
* @example
|
|
691
|
+
* const formatList = injectFormatList();
|
|
692
|
+
* readonly displayList = computed(() => formatList(this.items()));
|
|
537
693
|
*/
|
|
538
|
-
function
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
694
|
+
function injectFormatList() {
|
|
695
|
+
const defaults = injectFormatListOptions();
|
|
696
|
+
return (value, optOrLocale) => {
|
|
697
|
+
if (!optOrLocale)
|
|
698
|
+
return formatList(value, defaults());
|
|
699
|
+
const unwrapped = unwrap(optOrLocale);
|
|
700
|
+
const opt = typeof unwrapped === 'object'
|
|
701
|
+
? { ...defaults(), ...unwrapped }
|
|
702
|
+
: {
|
|
703
|
+
...defaults(),
|
|
704
|
+
locale: unwrapped,
|
|
705
|
+
};
|
|
706
|
+
return formatList(value, opt);
|
|
707
|
+
};
|
|
545
708
|
}
|
|
546
709
|
|
|
547
710
|
const cache$1 = new Map();
|
|
@@ -568,45 +731,151 @@ function getFormatter$1(locale, minFractionDigits, maxFractionDigits, useGroupin
|
|
|
568
731
|
}
|
|
569
732
|
return formatter;
|
|
570
733
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
* @param opt - Options for formatting
|
|
577
|
-
* @returns Formatted number string
|
|
578
|
-
*/
|
|
579
|
-
function formatNumber(value, opt) {
|
|
580
|
-
const unwrappedOpt = unwrap(opt);
|
|
581
|
-
const unwrappedNumber = unwrapValue(value, unwrappedOpt?.fallbackToZero);
|
|
734
|
+
function formatNumber(value, optOrLocale) {
|
|
735
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
736
|
+
const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
|
|
737
|
+
const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
|
|
738
|
+
const unwrappedNumber = unwrapValue(value, fallbackToZero);
|
|
582
739
|
if (unwrappedNumber === null)
|
|
583
740
|
return '';
|
|
584
|
-
|
|
585
|
-
|
|
741
|
+
let locale;
|
|
742
|
+
let notation;
|
|
743
|
+
let minFractionDigits;
|
|
744
|
+
let maxFractionDigits;
|
|
745
|
+
let useGrouping = true;
|
|
746
|
+
if (typeof unwrappedArgs === 'string') {
|
|
747
|
+
locale = unwrappedArgs;
|
|
748
|
+
}
|
|
749
|
+
else if (isOpt) {
|
|
750
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
751
|
+
notation = unwrappedArgs.notation ?? 'standard';
|
|
752
|
+
minFractionDigits = unwrappedArgs.minFractionDigits;
|
|
753
|
+
maxFractionDigits = unwrappedArgs.maxFractionDigits;
|
|
754
|
+
useGrouping = unwrappedArgs.useGrouping ?? true;
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
locale = readLocaleUnsafe();
|
|
758
|
+
notation = 'standard';
|
|
759
|
+
}
|
|
760
|
+
return getFormatter$1(locale, minFractionDigits, maxFractionDigits, useGrouping, notation).format(unwrappedNumber);
|
|
586
761
|
}
|
|
762
|
+
const [provideFormatNumberDefaults, injectFormatNumberOptions] = createFormatterProvider('number', {
|
|
763
|
+
notation: 'standard',
|
|
764
|
+
useGrouping: true,
|
|
765
|
+
}, (a, b) => a.notation === b.notation &&
|
|
766
|
+
a.minFractionDigits === b.minFractionDigits &&
|
|
767
|
+
a.maxFractionDigits === b.maxFractionDigits &&
|
|
768
|
+
a.useGrouping === b.useGrouping &&
|
|
769
|
+
a.fallbackToZero === b.fallbackToZero);
|
|
587
770
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* @returns Formatted percentage string
|
|
771
|
+
* Inject a context-safe number formatting function tied to the current injector.
|
|
772
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
773
|
+
* @example
|
|
774
|
+
* const formatNumber = injectFormatNumber();
|
|
775
|
+
* readonly display = computed(() => formatNumber(this.value()));
|
|
594
776
|
*/
|
|
595
|
-
function
|
|
596
|
-
const
|
|
597
|
-
|
|
777
|
+
function injectFormatNumber() {
|
|
778
|
+
const defaults = injectFormatNumberOptions();
|
|
779
|
+
return (value, optOrLocale) => {
|
|
780
|
+
if (!optOrLocale)
|
|
781
|
+
return formatNumber(value, defaults());
|
|
782
|
+
const unwrapped = unwrap(optOrLocale);
|
|
783
|
+
const opt = typeof unwrapped === 'object'
|
|
784
|
+
? { ...defaults(), ...unwrapped }
|
|
785
|
+
: {
|
|
786
|
+
...defaults(),
|
|
787
|
+
locale: unwrapped,
|
|
788
|
+
};
|
|
789
|
+
return formatNumber(value, opt);
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function formatPercent(value, optOrLocale) {
|
|
793
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
794
|
+
const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
|
|
795
|
+
const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
|
|
796
|
+
const unwrappedNumber = unwrapValue(value, fallbackToZero);
|
|
598
797
|
if (unwrappedNumber === null)
|
|
599
798
|
return '';
|
|
600
|
-
|
|
601
|
-
|
|
799
|
+
let locale;
|
|
800
|
+
let minFractionDigits;
|
|
801
|
+
let maxFractionDigits;
|
|
802
|
+
if (typeof unwrappedArgs === 'string') {
|
|
803
|
+
locale = unwrappedArgs;
|
|
804
|
+
}
|
|
805
|
+
else if (isOpt) {
|
|
806
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
807
|
+
minFractionDigits = unwrappedArgs.minFractionDigits;
|
|
808
|
+
maxFractionDigits = unwrappedArgs.maxFractionDigits;
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
locale = readLocaleUnsafe();
|
|
812
|
+
}
|
|
813
|
+
return getFormatter$1(locale, minFractionDigits, maxFractionDigits, undefined, undefined, undefined, undefined, 'percent').format(unwrappedNumber);
|
|
602
814
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
815
|
+
const [provideFormatPercentDefaults, injectFormatPercentOptions] = createFormatterProvider('percent', {}, (a, b) => a.minFractionDigits === b.minFractionDigits &&
|
|
816
|
+
a.maxFractionDigits === b.maxFractionDigits &&
|
|
817
|
+
a.fallbackToZero === b.fallbackToZero);
|
|
818
|
+
/**
|
|
819
|
+
* Inject a context-safe percent formatting function tied to the current injector.
|
|
820
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
821
|
+
*/
|
|
822
|
+
function injectFormatPercent() {
|
|
823
|
+
const defaults = injectFormatPercentOptions();
|
|
824
|
+
return (value, optOrLocale) => {
|
|
825
|
+
if (!optOrLocale)
|
|
826
|
+
return formatPercent(value, defaults());
|
|
827
|
+
const unwrapped = unwrap(optOrLocale);
|
|
828
|
+
const opt = typeof unwrapped === 'object'
|
|
829
|
+
? { ...defaults(), ...unwrapped }
|
|
830
|
+
: {
|
|
831
|
+
...defaults(),
|
|
832
|
+
locale: unwrapped,
|
|
833
|
+
};
|
|
834
|
+
return formatPercent(value, opt);
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function formatCurrency(value, currency, optOrLocale) {
|
|
838
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
839
|
+
const isOpt = unwrappedArgs != null && typeof unwrappedArgs === 'object';
|
|
840
|
+
const fallbackToZero = isOpt ? unwrappedArgs.fallbackToZero : undefined;
|
|
841
|
+
const unwrappedValue = unwrapValue(value, fallbackToZero);
|
|
606
842
|
if (unwrappedValue === null)
|
|
607
843
|
return '';
|
|
608
|
-
|
|
609
|
-
|
|
844
|
+
let locale;
|
|
845
|
+
let display = 'symbol';
|
|
846
|
+
if (typeof unwrappedArgs === 'string') {
|
|
847
|
+
locale = unwrappedArgs;
|
|
848
|
+
}
|
|
849
|
+
else if (isOpt) {
|
|
850
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
851
|
+
display = unwrappedArgs.display ?? 'symbol';
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
locale = readLocaleUnsafe();
|
|
855
|
+
}
|
|
856
|
+
return getFormatter$1(locale, undefined, undefined, undefined, undefined, unwrap(currency), display, 'currency').format(unwrappedValue);
|
|
857
|
+
}
|
|
858
|
+
const [provideFormatCurrencyDefaults, injectFormatCurrencyOptions] = createFormatterProvider('currency', {
|
|
859
|
+
display: 'symbol',
|
|
860
|
+
}, (a, b) => a.display === b.display && a.fallbackToZero === b.fallbackToZero);
|
|
861
|
+
/**
|
|
862
|
+
* Inject a context-safe currency formatting function tied to the current injector.
|
|
863
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
864
|
+
*/
|
|
865
|
+
function injectFormatCurrency() {
|
|
866
|
+
const defaults = injectFormatCurrencyOptions();
|
|
867
|
+
return (value, currency, optOrLocale) => {
|
|
868
|
+
if (!optOrLocale)
|
|
869
|
+
return formatCurrency(value, currency, defaults());
|
|
870
|
+
const unwrapped = unwrap(optOrLocale);
|
|
871
|
+
const opt = typeof unwrapped === 'object'
|
|
872
|
+
? { ...defaults(), ...unwrapped }
|
|
873
|
+
: {
|
|
874
|
+
...defaults(),
|
|
875
|
+
locale: unwrapped,
|
|
876
|
+
};
|
|
877
|
+
return formatCurrency(value, currency, opt);
|
|
878
|
+
};
|
|
610
879
|
}
|
|
611
880
|
|
|
612
881
|
const cache = new Map();
|
|
@@ -619,16 +888,7 @@ function getFormatter(locale, style, numeric) {
|
|
|
619
888
|
}
|
|
620
889
|
return formatter;
|
|
621
890
|
}
|
|
622
|
-
|
|
623
|
-
* Format a relative time using the current or provided locale
|
|
624
|
-
* 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
|
|
625
|
-
*
|
|
626
|
-
* @param value - The numeric value to use in the relative time internationalization message
|
|
627
|
-
* @param unit - The unit to use in the relative time internationalization message
|
|
628
|
-
* @param opt - Options for formatting
|
|
629
|
-
* @returns Formatted relative time string
|
|
630
|
-
*/
|
|
631
|
-
function formatRelativeTime(value, unit, opt) {
|
|
891
|
+
function formatRelativeTime(value, unit, optOrLocale) {
|
|
632
892
|
const unwrappedValue = unwrap(value);
|
|
633
893
|
if (unwrappedValue === null ||
|
|
634
894
|
unwrappedValue === undefined ||
|
|
@@ -637,11 +897,83 @@ function formatRelativeTime(value, unit, opt) {
|
|
|
637
897
|
const unwrappedUnit = unwrap(unit);
|
|
638
898
|
if (!unwrappedUnit)
|
|
639
899
|
return '';
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
900
|
+
const unwrappedArgs = unwrap(optOrLocale);
|
|
901
|
+
let locale;
|
|
902
|
+
let style = 'long';
|
|
903
|
+
let numeric = 'always';
|
|
904
|
+
if (typeof unwrappedArgs === 'string') {
|
|
905
|
+
locale = unwrappedArgs;
|
|
906
|
+
}
|
|
907
|
+
else if (unwrappedArgs && typeof unwrappedArgs === 'object') {
|
|
908
|
+
locale = unwrappedArgs.locale ?? readLocaleUnsafe();
|
|
909
|
+
style = unwrappedArgs.style ?? 'long';
|
|
910
|
+
numeric = unwrappedArgs.numeric ?? 'always';
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
locale = readLocaleUnsafe();
|
|
914
|
+
}
|
|
915
|
+
return getFormatter(locale, style, numeric).format(unwrappedValue, unwrappedUnit);
|
|
916
|
+
}
|
|
917
|
+
const [provideFormatRelativeTimeDefaults, injectFormatRelativeTimeOptions] = createFormatterProvider('relativeTime', {
|
|
918
|
+
style: 'long',
|
|
919
|
+
numeric: 'always',
|
|
920
|
+
}, (a, b) => a.style === b.style && a.numeric === b.numeric);
|
|
921
|
+
/**
|
|
922
|
+
* Inject a context-safe relative time formatting function tied to the current injector.
|
|
923
|
+
* Uses the libraries locale signal & provided default configuration to react to locale/config changes
|
|
924
|
+
* @example
|
|
925
|
+
* const formatRelativeTime = injectFormatRelativeTime();
|
|
926
|
+
* readonly relativeAge = computed(() => formatRelativeTime(this.delta(), 'day'));
|
|
927
|
+
*/
|
|
928
|
+
function injectFormatRelativeTime() {
|
|
929
|
+
const defaults = injectFormatRelativeTimeOptions();
|
|
930
|
+
return (value, unit, optOrLocale) => {
|
|
931
|
+
if (!optOrLocale)
|
|
932
|
+
return formatRelativeTime(value, unit, defaults());
|
|
933
|
+
const unwrapped = unwrap(optOrLocale);
|
|
934
|
+
const opt = typeof unwrapped === 'object'
|
|
935
|
+
? { ...defaults(), ...unwrapped }
|
|
936
|
+
: {
|
|
937
|
+
...defaults(),
|
|
938
|
+
locale: unwrapped,
|
|
939
|
+
};
|
|
940
|
+
return formatRelativeTime(value, unit, opt);
|
|
941
|
+
};
|
|
643
942
|
}
|
|
644
943
|
|
|
944
|
+
function provideFormatDefaults(cfg) {
|
|
945
|
+
const providers = [];
|
|
946
|
+
if (cfg.date)
|
|
947
|
+
providers.push(provideFormatDateDefaults(cfg.date));
|
|
948
|
+
if (cfg.displayName)
|
|
949
|
+
providers.push(provideFormatDisplayNameDefaults(cfg.displayName));
|
|
950
|
+
if (cfg.list)
|
|
951
|
+
providers.push(provideFormatListDefaults(cfg.list));
|
|
952
|
+
if (cfg.relativeTime)
|
|
953
|
+
providers.push(provideFormatRelativeTimeDefaults(cfg.relativeTime));
|
|
954
|
+
if (cfg.number)
|
|
955
|
+
providers.push(provideFormatNumberDefaults(cfg.number));
|
|
956
|
+
if (cfg.percent)
|
|
957
|
+
providers.push(provideFormatPercentDefaults(cfg.percent));
|
|
958
|
+
if (cfg.currency)
|
|
959
|
+
providers.push(provideFormatCurrencyDefaults(cfg.currency));
|
|
960
|
+
return providers;
|
|
961
|
+
}
|
|
962
|
+
function injectFormatters() {
|
|
963
|
+
return {
|
|
964
|
+
date: injectFormatDate(),
|
|
965
|
+
displayName: injectFormatDisplayName(),
|
|
966
|
+
list: injectFormatList(),
|
|
967
|
+
relativeTime: injectFormatRelativeTime(),
|
|
968
|
+
number: injectFormatNumber(),
|
|
969
|
+
percent: injectFormatPercent(),
|
|
970
|
+
currency: injectFormatCurrency(),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* @internal
|
|
976
|
+
*/
|
|
645
977
|
function injectResolveParamLocale(snapshot) {
|
|
646
978
|
let locale = null;
|
|
647
979
|
const paramName = injectIntlConfig()?.localeParamName;
|
|
@@ -654,7 +986,7 @@ function injectResolveParamLocale(snapshot) {
|
|
|
654
986
|
if (!locale && !alwaysInheritParams) {
|
|
655
987
|
let currentRoute = snapshot;
|
|
656
988
|
while (currentRoute && !locale) {
|
|
657
|
-
locale = currentRoute.paramMap.get(
|
|
989
|
+
locale = currentRoute.paramMap.get(paramName);
|
|
658
990
|
currentRoute = currentRoute.parent;
|
|
659
991
|
}
|
|
660
992
|
}
|
|
@@ -721,44 +1053,85 @@ function createT(store, keyMap = new Map()) {
|
|
|
721
1053
|
};
|
|
722
1054
|
return fn;
|
|
723
1055
|
}
|
|
1056
|
+
function isCompiledTranslation(value) {
|
|
1057
|
+
return (!!value &&
|
|
1058
|
+
typeof value === 'object' &&
|
|
1059
|
+
'flat' in value &&
|
|
1060
|
+
'namespace' in value);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* @internal exported for unit testing
|
|
1064
|
+
*
|
|
1065
|
+
* Unwraps a loader result to a `CompiledTranslation`. Detection order:
|
|
1066
|
+
* 1. value is already a `CompiledTranslation` (has `flat` + `namespace`)
|
|
1067
|
+
* 2. value has a `default` export holding a `CompiledTranslation` (ESM default)
|
|
1068
|
+
* 3. value has a `translation` export holding a `CompiledTranslation` (named export)
|
|
1069
|
+
*/
|
|
1070
|
+
function resolveTranslationModule(loaded) {
|
|
1071
|
+
if (isCompiledTranslation(loaded))
|
|
1072
|
+
return loaded;
|
|
1073
|
+
if (loaded && typeof loaded === 'object') {
|
|
1074
|
+
const def = loaded.default;
|
|
1075
|
+
if (isCompiledTranslation(def))
|
|
1076
|
+
return def;
|
|
1077
|
+
const tr = loaded.translation;
|
|
1078
|
+
if (isCompiledTranslation(tr))
|
|
1079
|
+
return tr;
|
|
1080
|
+
}
|
|
1081
|
+
throw new Error('[@mmstack/translate] Loader did not return a CompiledTranslation. Expected the value from `createNamespace` / `createTranslation`, or a module exporting one as `default` or `translation`.');
|
|
1082
|
+
}
|
|
724
1083
|
function registerNamespace(defaultTranslation, other) {
|
|
725
1084
|
const keyMap = new Map();
|
|
1085
|
+
// Pre-wrap loaders once so the rest of the pipeline — including the
|
|
1086
|
+
// dynamic-locale loader in `TranslationStore`, which reads `.namespace`
|
|
1087
|
+
// and `.flat` directly off the result — always sees a `CompiledTranslation`
|
|
1088
|
+
// regardless of which loader shape the caller used.
|
|
1089
|
+
const unwrappedDefault = () => defaultTranslation().then(resolveTranslationModule);
|
|
1090
|
+
const unwrappedOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
|
|
1091
|
+
loc,
|
|
1092
|
+
() => loader().then(resolveTranslationModule),
|
|
1093
|
+
]));
|
|
726
1094
|
const injectT = () => {
|
|
727
1095
|
const store = inject(TranslationStore);
|
|
728
1096
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
729
1097
|
};
|
|
1098
|
+
// Tracks whether the default locale's translation has been loaded by any prior
|
|
1099
|
+
// resolver call. Captured in closure across navigations on purpose — concurrent
|
|
1100
|
+
// resolves may each see `false` and both queue a default preload; that's harmless
|
|
1101
|
+
// because `store.register` is idempotent (same payload overwrites with the same
|
|
1102
|
+
// value). Do not add locking here.
|
|
730
1103
|
let defaultTranslationLoaded = false;
|
|
731
1104
|
const resolver = async (snapshot) => {
|
|
732
1105
|
const store = inject(TranslationStore);
|
|
733
1106
|
const locale = injectResolveParamLocale(snapshot);
|
|
734
1107
|
const defaultLocale = injectDefaultLocale();
|
|
735
1108
|
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
736
|
-
const tPromise =
|
|
737
|
-
const promise = tPromise ??
|
|
1109
|
+
const tPromise = unwrappedOther[locale];
|
|
1110
|
+
const promise = tPromise ?? unwrappedDefault;
|
|
738
1111
|
if (!promise && isDevMode()) {
|
|
739
1112
|
return console.warn(`No translation found for locale: ${locale}`);
|
|
740
1113
|
}
|
|
741
|
-
if (promise ===
|
|
1114
|
+
if (promise === unwrappedDefault && defaultTranslationLoaded)
|
|
742
1115
|
return;
|
|
743
1116
|
try {
|
|
744
1117
|
const promises = [promise()];
|
|
745
1118
|
if (shouldPreloadDefault &&
|
|
746
1119
|
!defaultTranslationLoaded &&
|
|
747
|
-
promise !==
|
|
748
|
-
promises.push(
|
|
1120
|
+
promise !== unwrappedDefault)
|
|
1121
|
+
promises.push(unwrappedDefault());
|
|
749
1122
|
const translations = await Promise.allSettled(promises);
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
1123
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1124
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
752
1125
|
throw new Error('Failed to load translations');
|
|
753
|
-
const [t, defaultT] =
|
|
1126
|
+
const [t, defaultT] = fulfilled;
|
|
754
1127
|
const ns = t?.namespace ?? defaultT?.namespace;
|
|
755
1128
|
if (!ns)
|
|
756
1129
|
throw new Error('No namespace found in translation');
|
|
757
1130
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
758
1131
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
759
1132
|
store.registerOnDemandLoaders(ns, {
|
|
760
|
-
...
|
|
761
|
-
[defaultLocale]:
|
|
1133
|
+
...unwrappedOther,
|
|
1134
|
+
[defaultLocale]: unwrappedDefault,
|
|
762
1135
|
});
|
|
763
1136
|
const toRegister = {};
|
|
764
1137
|
if (t)
|
|
@@ -766,7 +1139,7 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
766
1139
|
if (defaultT)
|
|
767
1140
|
toRegister[defaultLocale] = defaultT.flat;
|
|
768
1141
|
store.register(ns, toRegister);
|
|
769
|
-
if (promise ===
|
|
1142
|
+
if (promise === unwrappedDefault || defaultT)
|
|
770
1143
|
defaultTranslationLoaded = true;
|
|
771
1144
|
}
|
|
772
1145
|
catch {
|
|
@@ -790,10 +1163,21 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
790
1163
|
*/
|
|
791
1164
|
function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
792
1165
|
const keyMap = new Map();
|
|
1166
|
+
// The dynamic-locale loader in TranslationStore reads `.namespace` and `.flat`
|
|
1167
|
+
// off the loader result, so on-demand loaders must return CompiledTranslation.
|
|
1168
|
+
// Wrap the raw remote fetchers once here at registration time rather than every
|
|
1169
|
+
// resolver call.
|
|
1170
|
+
const compileLoader = (loader, locale) => () => loader().then((raw) => compileTranslation(raw, ns, locale));
|
|
1171
|
+
const compiledOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
|
|
1172
|
+
loc,
|
|
1173
|
+
compileLoader(loader, loc),
|
|
1174
|
+
]));
|
|
793
1175
|
const injectT = () => {
|
|
794
1176
|
const store = inject(TranslationStore);
|
|
795
1177
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
796
1178
|
};
|
|
1179
|
+
// See `defaultTranslationLoaded` in `registerNamespace` for rationale —
|
|
1180
|
+
// intentionally racy, safe via idempotent `store.register`.
|
|
797
1181
|
let defaultTranslationLoaded = false;
|
|
798
1182
|
const resolver = async (snapshot) => {
|
|
799
1183
|
const store = inject(TranslationStore);
|
|
@@ -814,10 +1198,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
814
1198
|
promise !== defaultTranslation)
|
|
815
1199
|
promises.push(defaultTranslation());
|
|
816
1200
|
const translations = await Promise.allSettled(promises);
|
|
817
|
-
const
|
|
818
|
-
if (
|
|
1201
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1202
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
819
1203
|
throw new Error('Failed to load translations');
|
|
820
|
-
const [baseT, baseDefaultT] =
|
|
1204
|
+
const [baseT, baseDefaultT] = fulfilled;
|
|
821
1205
|
const t = baseT ? compileTranslation(baseT, ns, locale) : null;
|
|
822
1206
|
const defaultT = baseDefaultT
|
|
823
1207
|
? compileTranslation(baseDefaultT, ns, defaultLocale)
|
|
@@ -825,8 +1209,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
825
1209
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
826
1210
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
827
1211
|
store.registerOnDemandLoaders(ns, {
|
|
828
|
-
...
|
|
829
|
-
[defaultLocale]: defaultTranslation,
|
|
1212
|
+
...compiledOther,
|
|
1213
|
+
[defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
|
|
830
1214
|
});
|
|
831
1215
|
const toRegister = {};
|
|
832
1216
|
if (t)
|
|
@@ -1059,6 +1443,10 @@ function createTransformFn() {
|
|
|
1059
1443
|
const store = inject(TranslationStore);
|
|
1060
1444
|
const t = createT(store);
|
|
1061
1445
|
const fn = (key, variablesOrLocale,
|
|
1446
|
+
// The locale argument is unused at runtime — the active locale comes from
|
|
1447
|
+
// `TranslationStore`. It exists on the signature so consumers can pass
|
|
1448
|
+
// `locale()` as a pipe argument (`'key' | translate : vars : locale()`)
|
|
1449
|
+
// to bust Angular's pure-pipe memoization on locale changes.
|
|
1062
1450
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1063
1451
|
_) => {
|
|
1064
1452
|
const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
|
|
@@ -1109,5 +1497,5 @@ function withParams(message) {
|
|
|
1109
1497
|
* Generated bundle index. Do not edit.
|
|
1110
1498
|
*/
|
|
1111
1499
|
|
|
1112
|
-
export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectAddTranslations, injectDynamicLocale, injectIntl, injectResolveParamLocale, injectSupportedLocales, injectUnsafeT, provideIntlConfig, provideMockTranslations, registerNamespace, registerRemoteNamespace, withParams };
|
|
1500
|
+
export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectAddTranslations, injectDefaultLocale, injectDynamicLocale, injectFormatCurrency, injectFormatDate, injectFormatDisplayName, injectFormatList, injectFormatNumber, injectFormatPercent, injectFormatRelativeTime, injectFormatters, injectIntl, injectResolveParamLocale, injectSupportedLocales, injectUnsafeT, provideFormatCurrencyDefaults, provideFormatDateDefaults, provideFormatDefaults, provideFormatDisplayNameDefaults, provideFormatListDefaults, provideFormatNumberDefaults, provideFormatPercentDefaults, provideFormatRelativeTimeDefaults, provideIntlConfig, provideMockTranslations, registerNamespace, registerRemoteNamespace, withParams };
|
|
1113
1501
|
//# sourceMappingURL=mmstack-translate.mjs.map
|