@mmstack/translate 21.1.13 → 21.1.15
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 +232 -165
- package/fesm2022/mmstack-translate.mjs +86 -24
- package/fesm2022/mmstack-translate.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-translate.d.ts +24 -9
|
@@ -3,7 +3,6 @@ import { computed, inject, InjectionToken, LOCALE_ID, signal, untracked, isDevMo
|
|
|
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';
|
|
6
|
-
import { inject as inject$1 } from '@angular/core/primitives/di';
|
|
7
6
|
|
|
8
7
|
const KEY_DELIM = '::MMT_DELIM::';
|
|
9
8
|
function prependDelim(prefix, key) {
|
|
@@ -56,6 +55,10 @@ function createNamespace(ns, translation) {
|
|
|
56
55
|
return namespace;
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Dynamic path parameter signal, that allows resolution even when paramsInheritenceStrategy isnt always
|
|
60
|
+
* This helper depends on an internal
|
|
61
|
+
*/
|
|
59
62
|
function pathParam(key, route = inject(ActivatedRoute)) {
|
|
60
63
|
const keySignal = typeof key === 'string' ? computed(() => key) : computed(key);
|
|
61
64
|
const routerOptions = inject(Router)['options'];
|
|
@@ -143,7 +146,7 @@ function readLocaleUnsafe() {
|
|
|
143
146
|
function injectLocaleInternal() {
|
|
144
147
|
return STORE_LOCALE;
|
|
145
148
|
}
|
|
146
|
-
function
|
|
149
|
+
function proxyToGlobalSingleton(src) {
|
|
147
150
|
const originalSet = src.set;
|
|
148
151
|
src.set = (next) => {
|
|
149
152
|
originalSet(next);
|
|
@@ -249,7 +252,7 @@ class TranslationStore {
|
|
|
249
252
|
messages: this.messages(),
|
|
250
253
|
}, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : /* istanbul ignore next */ []));
|
|
251
254
|
constructor() {
|
|
252
|
-
this.locale =
|
|
255
|
+
this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
|
|
253
256
|
const paramName = this.config?.localeParamName;
|
|
254
257
|
if (paramName) {
|
|
255
258
|
const param = pathParam(paramName);
|
|
@@ -486,7 +489,7 @@ function createFormatterProvider(formatterName, libraryDefaults, nonLocaleEqual)
|
|
|
486
489
|
},
|
|
487
490
|
};
|
|
488
491
|
};
|
|
489
|
-
const injectFn = () => inject
|
|
492
|
+
const injectFn = () => inject(token);
|
|
490
493
|
return [provider, injectFn];
|
|
491
494
|
}
|
|
492
495
|
|
|
@@ -630,9 +633,9 @@ function injectFormatDisplayName() {
|
|
|
630
633
|
return formatDisplayName(value, type, defaults());
|
|
631
634
|
const unwrapped = unwrap(localeOrOpt);
|
|
632
635
|
const opt = typeof unwrapped === 'object'
|
|
633
|
-
? { ...defaults, ...unwrapped }
|
|
636
|
+
? { ...defaults(), ...unwrapped }
|
|
634
637
|
: {
|
|
635
|
-
...defaults,
|
|
638
|
+
...defaults(),
|
|
636
639
|
locale: unwrapped,
|
|
637
640
|
};
|
|
638
641
|
return formatDisplayName(value, type, opt);
|
|
@@ -966,6 +969,9 @@ function injectFormatters() {
|
|
|
966
969
|
};
|
|
967
970
|
}
|
|
968
971
|
|
|
972
|
+
/**
|
|
973
|
+
* @internal
|
|
974
|
+
*/
|
|
969
975
|
function injectResolveParamLocale(snapshot) {
|
|
970
976
|
let locale = null;
|
|
971
977
|
const paramName = injectIntlConfig()?.localeParamName;
|
|
@@ -978,7 +984,7 @@ function injectResolveParamLocale(snapshot) {
|
|
|
978
984
|
if (!locale && !alwaysInheritParams) {
|
|
979
985
|
let currentRoute = snapshot;
|
|
980
986
|
while (currentRoute && !locale) {
|
|
981
|
-
locale = currentRoute.paramMap.get(
|
|
987
|
+
locale = currentRoute.paramMap.get(paramName);
|
|
982
988
|
currentRoute = currentRoute.parent;
|
|
983
989
|
}
|
|
984
990
|
}
|
|
@@ -1045,44 +1051,85 @@ function createT(store, keyMap = new Map()) {
|
|
|
1045
1051
|
};
|
|
1046
1052
|
return fn;
|
|
1047
1053
|
}
|
|
1054
|
+
function isCompiledTranslation(value) {
|
|
1055
|
+
return (!!value &&
|
|
1056
|
+
typeof value === 'object' &&
|
|
1057
|
+
'flat' in value &&
|
|
1058
|
+
'namespace' in value);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* @internal exported for unit testing
|
|
1062
|
+
*
|
|
1063
|
+
* Unwraps a loader result to a `CompiledTranslation`. Detection order:
|
|
1064
|
+
* 1. value is already a `CompiledTranslation` (has `flat` + `namespace`)
|
|
1065
|
+
* 2. value has a `default` export holding a `CompiledTranslation` (ESM default)
|
|
1066
|
+
* 3. value has a `translation` export holding a `CompiledTranslation` (named export)
|
|
1067
|
+
*/
|
|
1068
|
+
function resolveTranslationModule(loaded) {
|
|
1069
|
+
if (isCompiledTranslation(loaded))
|
|
1070
|
+
return loaded;
|
|
1071
|
+
if (loaded && typeof loaded === 'object') {
|
|
1072
|
+
const def = loaded.default;
|
|
1073
|
+
if (isCompiledTranslation(def))
|
|
1074
|
+
return def;
|
|
1075
|
+
const tr = loaded.translation;
|
|
1076
|
+
if (isCompiledTranslation(tr))
|
|
1077
|
+
return tr;
|
|
1078
|
+
}
|
|
1079
|
+
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`.');
|
|
1080
|
+
}
|
|
1048
1081
|
function registerNamespace(defaultTranslation, other) {
|
|
1049
1082
|
const keyMap = new Map();
|
|
1083
|
+
// Pre-wrap loaders once so the rest of the pipeline — including the
|
|
1084
|
+
// dynamic-locale loader in `TranslationStore`, which reads `.namespace`
|
|
1085
|
+
// and `.flat` directly off the result — always sees a `CompiledTranslation`
|
|
1086
|
+
// regardless of which loader shape the caller used.
|
|
1087
|
+
const unwrappedDefault = () => defaultTranslation().then(resolveTranslationModule);
|
|
1088
|
+
const unwrappedOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
|
|
1089
|
+
loc,
|
|
1090
|
+
() => loader().then(resolveTranslationModule),
|
|
1091
|
+
]));
|
|
1050
1092
|
const injectT = () => {
|
|
1051
1093
|
const store = inject(TranslationStore);
|
|
1052
1094
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1053
1095
|
};
|
|
1096
|
+
// Tracks whether the default locale's translation has been loaded by any prior
|
|
1097
|
+
// resolver call. Captured in closure across navigations on purpose — concurrent
|
|
1098
|
+
// resolves may each see `false` and both queue a default preload; that's harmless
|
|
1099
|
+
// because `store.register` is idempotent (same payload overwrites with the same
|
|
1100
|
+
// value). Do not add locking here.
|
|
1054
1101
|
let defaultTranslationLoaded = false;
|
|
1055
1102
|
const resolver = async (snapshot) => {
|
|
1056
1103
|
const store = inject(TranslationStore);
|
|
1057
1104
|
const locale = injectResolveParamLocale(snapshot);
|
|
1058
1105
|
const defaultLocale = injectDefaultLocale();
|
|
1059
1106
|
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
1060
|
-
const tPromise =
|
|
1061
|
-
const promise = tPromise ??
|
|
1107
|
+
const tPromise = unwrappedOther[locale];
|
|
1108
|
+
const promise = tPromise ?? unwrappedDefault;
|
|
1062
1109
|
if (!promise && isDevMode()) {
|
|
1063
1110
|
return console.warn(`No translation found for locale: ${locale}`);
|
|
1064
1111
|
}
|
|
1065
|
-
if (promise ===
|
|
1112
|
+
if (promise === unwrappedDefault && defaultTranslationLoaded)
|
|
1066
1113
|
return;
|
|
1067
1114
|
try {
|
|
1068
1115
|
const promises = [promise()];
|
|
1069
1116
|
if (shouldPreloadDefault &&
|
|
1070
1117
|
!defaultTranslationLoaded &&
|
|
1071
|
-
promise !==
|
|
1072
|
-
promises.push(
|
|
1118
|
+
promise !== unwrappedDefault)
|
|
1119
|
+
promises.push(unwrappedDefault());
|
|
1073
1120
|
const translations = await Promise.allSettled(promises);
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1121
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1122
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1076
1123
|
throw new Error('Failed to load translations');
|
|
1077
|
-
const [t, defaultT] =
|
|
1124
|
+
const [t, defaultT] = fulfilled;
|
|
1078
1125
|
const ns = t?.namespace ?? defaultT?.namespace;
|
|
1079
1126
|
if (!ns)
|
|
1080
1127
|
throw new Error('No namespace found in translation');
|
|
1081
1128
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1082
1129
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1083
1130
|
store.registerOnDemandLoaders(ns, {
|
|
1084
|
-
...
|
|
1085
|
-
[defaultLocale]:
|
|
1131
|
+
...unwrappedOther,
|
|
1132
|
+
[defaultLocale]: unwrappedDefault,
|
|
1086
1133
|
});
|
|
1087
1134
|
const toRegister = {};
|
|
1088
1135
|
if (t)
|
|
@@ -1090,7 +1137,7 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1090
1137
|
if (defaultT)
|
|
1091
1138
|
toRegister[defaultLocale] = defaultT.flat;
|
|
1092
1139
|
store.register(ns, toRegister);
|
|
1093
|
-
if (promise ===
|
|
1140
|
+
if (promise === unwrappedDefault || defaultT)
|
|
1094
1141
|
defaultTranslationLoaded = true;
|
|
1095
1142
|
}
|
|
1096
1143
|
catch {
|
|
@@ -1114,10 +1161,21 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1114
1161
|
*/
|
|
1115
1162
|
function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
1116
1163
|
const keyMap = new Map();
|
|
1164
|
+
// The dynamic-locale loader in TranslationStore reads `.namespace` and `.flat`
|
|
1165
|
+
// off the loader result, so on-demand loaders must return CompiledTranslation.
|
|
1166
|
+
// Wrap the raw remote fetchers once here at registration time rather than every
|
|
1167
|
+
// resolver call.
|
|
1168
|
+
const compileLoader = (loader, locale) => () => loader().then((raw) => compileTranslation(raw, ns, locale));
|
|
1169
|
+
const compiledOther = Object.fromEntries(Object.entries(other).map(([loc, loader]) => [
|
|
1170
|
+
loc,
|
|
1171
|
+
compileLoader(loader, loc),
|
|
1172
|
+
]));
|
|
1117
1173
|
const injectT = () => {
|
|
1118
1174
|
const store = inject(TranslationStore);
|
|
1119
1175
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1120
1176
|
};
|
|
1177
|
+
// See `defaultTranslationLoaded` in `registerNamespace` for rationale —
|
|
1178
|
+
// intentionally racy, safe via idempotent `store.register`.
|
|
1121
1179
|
let defaultTranslationLoaded = false;
|
|
1122
1180
|
const resolver = async (snapshot) => {
|
|
1123
1181
|
const store = inject(TranslationStore);
|
|
@@ -1138,10 +1196,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1138
1196
|
promise !== defaultTranslation)
|
|
1139
1197
|
promises.push(defaultTranslation());
|
|
1140
1198
|
const translations = await Promise.allSettled(promises);
|
|
1141
|
-
const
|
|
1142
|
-
if (
|
|
1199
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1200
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1143
1201
|
throw new Error('Failed to load translations');
|
|
1144
|
-
const [baseT, baseDefaultT] =
|
|
1202
|
+
const [baseT, baseDefaultT] = fulfilled;
|
|
1145
1203
|
const t = baseT ? compileTranslation(baseT, ns, locale) : null;
|
|
1146
1204
|
const defaultT = baseDefaultT
|
|
1147
1205
|
? compileTranslation(baseDefaultT, ns, defaultLocale)
|
|
@@ -1149,8 +1207,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1149
1207
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1150
1208
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1151
1209
|
store.registerOnDemandLoaders(ns, {
|
|
1152
|
-
...
|
|
1153
|
-
[defaultLocale]: defaultTranslation,
|
|
1210
|
+
...compiledOther,
|
|
1211
|
+
[defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
|
|
1154
1212
|
});
|
|
1155
1213
|
const toRegister = {};
|
|
1156
1214
|
if (t)
|
|
@@ -1373,6 +1431,10 @@ function createTransformFn() {
|
|
|
1373
1431
|
const store = inject(TranslationStore);
|
|
1374
1432
|
const t = createT(store);
|
|
1375
1433
|
const fn = (key, variablesOrLocale,
|
|
1434
|
+
// The locale argument is unused at runtime — the active locale comes from
|
|
1435
|
+
// `TranslationStore`. It exists on the signature so consumers can pass
|
|
1436
|
+
// `locale()` as a pipe argument (`'key' | translate : vars : locale()`)
|
|
1437
|
+
// to bust Angular's pure-pipe memoization on locale changes.
|
|
1376
1438
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1377
1439
|
_) => {
|
|
1378
1440
|
const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
|
|
@@ -1423,5 +1485,5 @@ function withParams(message) {
|
|
|
1423
1485
|
* Generated bundle index. Do not edit.
|
|
1424
1486
|
*/
|
|
1425
1487
|
|
|
1426
|
-
export { Translate, Translator, canMatchLocale, compileTranslation, createNamespace, formatCurrency, formatDate, formatDisplayName, formatList, formatNumber, formatPercent, formatRelativeTime, injectAddTranslations, 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 };
|
|
1488
|
+
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 };
|
|
1427
1489
|
//# sourceMappingURL=mmstack-translate.mjs.map
|