@mmstack/translate 20.5.12 → 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 +232 -165
- package/fesm2022/mmstack-translate.mjs +85 -22
- package/fesm2022/mmstack-translate.mjs.map +1 -1
- package/index.d.ts +24 -9
- package/package.json +1 -1
|
@@ -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'];
|
|
@@ -142,7 +146,7 @@ function readLocaleUnsafe() {
|
|
|
142
146
|
function injectLocaleInternal() {
|
|
143
147
|
return STORE_LOCALE;
|
|
144
148
|
}
|
|
145
|
-
function
|
|
149
|
+
function proxyToGlobalSingleton(src) {
|
|
146
150
|
const originalSet = src.set;
|
|
147
151
|
src.set = (next) => {
|
|
148
152
|
originalSet(next);
|
|
@@ -250,7 +254,7 @@ class TranslationStore {
|
|
|
250
254
|
messages: this.messages(),
|
|
251
255
|
}, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : []));
|
|
252
256
|
constructor() {
|
|
253
|
-
this.locale =
|
|
257
|
+
this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
|
|
254
258
|
const paramName = this.config?.localeParamName;
|
|
255
259
|
if (paramName) {
|
|
256
260
|
const param = pathParam(paramName);
|
|
@@ -631,9 +635,9 @@ function injectFormatDisplayName() {
|
|
|
631
635
|
return formatDisplayName(value, type, defaults());
|
|
632
636
|
const unwrapped = unwrap(localeOrOpt);
|
|
633
637
|
const opt = typeof unwrapped === 'object'
|
|
634
|
-
? { ...defaults, ...unwrapped }
|
|
638
|
+
? { ...defaults(), ...unwrapped }
|
|
635
639
|
: {
|
|
636
|
-
...defaults,
|
|
640
|
+
...defaults(),
|
|
637
641
|
locale: unwrapped,
|
|
638
642
|
};
|
|
639
643
|
return formatDisplayName(value, type, opt);
|
|
@@ -967,6 +971,9 @@ function injectFormatters() {
|
|
|
967
971
|
};
|
|
968
972
|
}
|
|
969
973
|
|
|
974
|
+
/**
|
|
975
|
+
* @internal
|
|
976
|
+
*/
|
|
970
977
|
function injectResolveParamLocale(snapshot) {
|
|
971
978
|
let locale = null;
|
|
972
979
|
const paramName = injectIntlConfig()?.localeParamName;
|
|
@@ -979,7 +986,7 @@ function injectResolveParamLocale(snapshot) {
|
|
|
979
986
|
if (!locale && !alwaysInheritParams) {
|
|
980
987
|
let currentRoute = snapshot;
|
|
981
988
|
while (currentRoute && !locale) {
|
|
982
|
-
locale = currentRoute.paramMap.get(
|
|
989
|
+
locale = currentRoute.paramMap.get(paramName);
|
|
983
990
|
currentRoute = currentRoute.parent;
|
|
984
991
|
}
|
|
985
992
|
}
|
|
@@ -1046,44 +1053,85 @@ function createT(store, keyMap = new Map()) {
|
|
|
1046
1053
|
};
|
|
1047
1054
|
return fn;
|
|
1048
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
|
+
}
|
|
1049
1083
|
function registerNamespace(defaultTranslation, other) {
|
|
1050
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
|
+
]));
|
|
1051
1094
|
const injectT = () => {
|
|
1052
1095
|
const store = inject(TranslationStore);
|
|
1053
1096
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1054
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.
|
|
1055
1103
|
let defaultTranslationLoaded = false;
|
|
1056
1104
|
const resolver = async (snapshot) => {
|
|
1057
1105
|
const store = inject(TranslationStore);
|
|
1058
1106
|
const locale = injectResolveParamLocale(snapshot);
|
|
1059
1107
|
const defaultLocale = injectDefaultLocale();
|
|
1060
1108
|
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
1061
|
-
const tPromise =
|
|
1062
|
-
const promise = tPromise ??
|
|
1109
|
+
const tPromise = unwrappedOther[locale];
|
|
1110
|
+
const promise = tPromise ?? unwrappedDefault;
|
|
1063
1111
|
if (!promise && isDevMode()) {
|
|
1064
1112
|
return console.warn(`No translation found for locale: ${locale}`);
|
|
1065
1113
|
}
|
|
1066
|
-
if (promise ===
|
|
1114
|
+
if (promise === unwrappedDefault && defaultTranslationLoaded)
|
|
1067
1115
|
return;
|
|
1068
1116
|
try {
|
|
1069
1117
|
const promises = [promise()];
|
|
1070
1118
|
if (shouldPreloadDefault &&
|
|
1071
1119
|
!defaultTranslationLoaded &&
|
|
1072
|
-
promise !==
|
|
1073
|
-
promises.push(
|
|
1120
|
+
promise !== unwrappedDefault)
|
|
1121
|
+
promises.push(unwrappedDefault());
|
|
1074
1122
|
const translations = await Promise.allSettled(promises);
|
|
1075
|
-
const
|
|
1076
|
-
if (
|
|
1123
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1124
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1077
1125
|
throw new Error('Failed to load translations');
|
|
1078
|
-
const [t, defaultT] =
|
|
1126
|
+
const [t, defaultT] = fulfilled;
|
|
1079
1127
|
const ns = t?.namespace ?? defaultT?.namespace;
|
|
1080
1128
|
if (!ns)
|
|
1081
1129
|
throw new Error('No namespace found in translation');
|
|
1082
1130
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1083
1131
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1084
1132
|
store.registerOnDemandLoaders(ns, {
|
|
1085
|
-
...
|
|
1086
|
-
[defaultLocale]:
|
|
1133
|
+
...unwrappedOther,
|
|
1134
|
+
[defaultLocale]: unwrappedDefault,
|
|
1087
1135
|
});
|
|
1088
1136
|
const toRegister = {};
|
|
1089
1137
|
if (t)
|
|
@@ -1091,7 +1139,7 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1091
1139
|
if (defaultT)
|
|
1092
1140
|
toRegister[defaultLocale] = defaultT.flat;
|
|
1093
1141
|
store.register(ns, toRegister);
|
|
1094
|
-
if (promise ===
|
|
1142
|
+
if (promise === unwrappedDefault || defaultT)
|
|
1095
1143
|
defaultTranslationLoaded = true;
|
|
1096
1144
|
}
|
|
1097
1145
|
catch {
|
|
@@ -1115,10 +1163,21 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1115
1163
|
*/
|
|
1116
1164
|
function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
1117
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
|
+
]));
|
|
1118
1175
|
const injectT = () => {
|
|
1119
1176
|
const store = inject(TranslationStore);
|
|
1120
1177
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1121
1178
|
};
|
|
1179
|
+
// See `defaultTranslationLoaded` in `registerNamespace` for rationale —
|
|
1180
|
+
// intentionally racy, safe via idempotent `store.register`.
|
|
1122
1181
|
let defaultTranslationLoaded = false;
|
|
1123
1182
|
const resolver = async (snapshot) => {
|
|
1124
1183
|
const store = inject(TranslationStore);
|
|
@@ -1139,10 +1198,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1139
1198
|
promise !== defaultTranslation)
|
|
1140
1199
|
promises.push(defaultTranslation());
|
|
1141
1200
|
const translations = await Promise.allSettled(promises);
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1201
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1202
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1144
1203
|
throw new Error('Failed to load translations');
|
|
1145
|
-
const [baseT, baseDefaultT] =
|
|
1204
|
+
const [baseT, baseDefaultT] = fulfilled;
|
|
1146
1205
|
const t = baseT ? compileTranslation(baseT, ns, locale) : null;
|
|
1147
1206
|
const defaultT = baseDefaultT
|
|
1148
1207
|
? compileTranslation(baseDefaultT, ns, defaultLocale)
|
|
@@ -1150,8 +1209,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1150
1209
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1151
1210
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1152
1211
|
store.registerOnDemandLoaders(ns, {
|
|
1153
|
-
...
|
|
1154
|
-
[defaultLocale]: defaultTranslation,
|
|
1212
|
+
...compiledOther,
|
|
1213
|
+
[defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
|
|
1155
1214
|
});
|
|
1156
1215
|
const toRegister = {};
|
|
1157
1216
|
if (t)
|
|
@@ -1384,6 +1443,10 @@ function createTransformFn() {
|
|
|
1384
1443
|
const store = inject(TranslationStore);
|
|
1385
1444
|
const t = createT(store);
|
|
1386
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.
|
|
1387
1450
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1388
1451
|
_) => {
|
|
1389
1452
|
const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
|
|
@@ -1434,5 +1497,5 @@ function withParams(message) {
|
|
|
1434
1497
|
* Generated bundle index. Do not edit.
|
|
1435
1498
|
*/
|
|
1436
1499
|
|
|
1437
|
-
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 };
|
|
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 };
|
|
1438
1501
|
//# sourceMappingURL=mmstack-translate.mjs.map
|