@mmstack/translate 21.1.14 → 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 +85 -22
- package/fesm2022/mmstack-translate.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-translate.d.ts +24 -9
|
@@ -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);
|
|
@@ -248,7 +252,7 @@ class TranslationStore {
|
|
|
248
252
|
messages: this.messages(),
|
|
249
253
|
}, this.cache), ...(ngDevMode ? [{ debugName: "intl" }] : /* istanbul ignore next */ []));
|
|
250
254
|
constructor() {
|
|
251
|
-
this.locale =
|
|
255
|
+
this.locale = proxyToGlobalSingleton(initLocale(signal('en-US')));
|
|
252
256
|
const paramName = this.config?.localeParamName;
|
|
253
257
|
if (paramName) {
|
|
254
258
|
const param = pathParam(paramName);
|
|
@@ -629,9 +633,9 @@ function injectFormatDisplayName() {
|
|
|
629
633
|
return formatDisplayName(value, type, defaults());
|
|
630
634
|
const unwrapped = unwrap(localeOrOpt);
|
|
631
635
|
const opt = typeof unwrapped === 'object'
|
|
632
|
-
? { ...defaults, ...unwrapped }
|
|
636
|
+
? { ...defaults(), ...unwrapped }
|
|
633
637
|
: {
|
|
634
|
-
...defaults,
|
|
638
|
+
...defaults(),
|
|
635
639
|
locale: unwrapped,
|
|
636
640
|
};
|
|
637
641
|
return formatDisplayName(value, type, opt);
|
|
@@ -965,6 +969,9 @@ function injectFormatters() {
|
|
|
965
969
|
};
|
|
966
970
|
}
|
|
967
971
|
|
|
972
|
+
/**
|
|
973
|
+
* @internal
|
|
974
|
+
*/
|
|
968
975
|
function injectResolveParamLocale(snapshot) {
|
|
969
976
|
let locale = null;
|
|
970
977
|
const paramName = injectIntlConfig()?.localeParamName;
|
|
@@ -977,7 +984,7 @@ function injectResolveParamLocale(snapshot) {
|
|
|
977
984
|
if (!locale && !alwaysInheritParams) {
|
|
978
985
|
let currentRoute = snapshot;
|
|
979
986
|
while (currentRoute && !locale) {
|
|
980
|
-
locale = currentRoute.paramMap.get(
|
|
987
|
+
locale = currentRoute.paramMap.get(paramName);
|
|
981
988
|
currentRoute = currentRoute.parent;
|
|
982
989
|
}
|
|
983
990
|
}
|
|
@@ -1044,44 +1051,85 @@ function createT(store, keyMap = new Map()) {
|
|
|
1044
1051
|
};
|
|
1045
1052
|
return fn;
|
|
1046
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
|
+
}
|
|
1047
1081
|
function registerNamespace(defaultTranslation, other) {
|
|
1048
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
|
+
]));
|
|
1049
1092
|
const injectT = () => {
|
|
1050
1093
|
const store = inject(TranslationStore);
|
|
1051
1094
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1052
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.
|
|
1053
1101
|
let defaultTranslationLoaded = false;
|
|
1054
1102
|
const resolver = async (snapshot) => {
|
|
1055
1103
|
const store = inject(TranslationStore);
|
|
1056
1104
|
const locale = injectResolveParamLocale(snapshot);
|
|
1057
1105
|
const defaultLocale = injectDefaultLocale();
|
|
1058
1106
|
const shouldPreloadDefault = injectIntlConfig()?.preloadDefaultLocale ?? false;
|
|
1059
|
-
const tPromise =
|
|
1060
|
-
const promise = tPromise ??
|
|
1107
|
+
const tPromise = unwrappedOther[locale];
|
|
1108
|
+
const promise = tPromise ?? unwrappedDefault;
|
|
1061
1109
|
if (!promise && isDevMode()) {
|
|
1062
1110
|
return console.warn(`No translation found for locale: ${locale}`);
|
|
1063
1111
|
}
|
|
1064
|
-
if (promise ===
|
|
1112
|
+
if (promise === unwrappedDefault && defaultTranslationLoaded)
|
|
1065
1113
|
return;
|
|
1066
1114
|
try {
|
|
1067
1115
|
const promises = [promise()];
|
|
1068
1116
|
if (shouldPreloadDefault &&
|
|
1069
1117
|
!defaultTranslationLoaded &&
|
|
1070
|
-
promise !==
|
|
1071
|
-
promises.push(
|
|
1118
|
+
promise !== unwrappedDefault)
|
|
1119
|
+
promises.push(unwrappedDefault());
|
|
1072
1120
|
const translations = await Promise.allSettled(promises);
|
|
1073
|
-
const
|
|
1074
|
-
if (
|
|
1121
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1122
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1075
1123
|
throw new Error('Failed to load translations');
|
|
1076
|
-
const [t, defaultT] =
|
|
1124
|
+
const [t, defaultT] = fulfilled;
|
|
1077
1125
|
const ns = t?.namespace ?? defaultT?.namespace;
|
|
1078
1126
|
if (!ns)
|
|
1079
1127
|
throw new Error('No namespace found in translation');
|
|
1080
1128
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1081
1129
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1082
1130
|
store.registerOnDemandLoaders(ns, {
|
|
1083
|
-
...
|
|
1084
|
-
[defaultLocale]:
|
|
1131
|
+
...unwrappedOther,
|
|
1132
|
+
[defaultLocale]: unwrappedDefault,
|
|
1085
1133
|
});
|
|
1086
1134
|
const toRegister = {};
|
|
1087
1135
|
if (t)
|
|
@@ -1089,7 +1137,7 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1089
1137
|
if (defaultT)
|
|
1090
1138
|
toRegister[defaultLocale] = defaultT.flat;
|
|
1091
1139
|
store.register(ns, toRegister);
|
|
1092
|
-
if (promise ===
|
|
1140
|
+
if (promise === unwrappedDefault || defaultT)
|
|
1093
1141
|
defaultTranslationLoaded = true;
|
|
1094
1142
|
}
|
|
1095
1143
|
catch {
|
|
@@ -1113,10 +1161,21 @@ function registerNamespace(defaultTranslation, other) {
|
|
|
1113
1161
|
*/
|
|
1114
1162
|
function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
1115
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
|
+
]));
|
|
1116
1173
|
const injectT = () => {
|
|
1117
1174
|
const store = inject(TranslationStore);
|
|
1118
1175
|
return addSignalFn(createT(store, keyMap), store, keyMap);
|
|
1119
1176
|
};
|
|
1177
|
+
// See `defaultTranslationLoaded` in `registerNamespace` for rationale —
|
|
1178
|
+
// intentionally racy, safe via idempotent `store.register`.
|
|
1120
1179
|
let defaultTranslationLoaded = false;
|
|
1121
1180
|
const resolver = async (snapshot) => {
|
|
1122
1181
|
const store = inject(TranslationStore);
|
|
@@ -1137,10 +1196,10 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1137
1196
|
promise !== defaultTranslation)
|
|
1138
1197
|
promises.push(defaultTranslation());
|
|
1139
1198
|
const translations = await Promise.allSettled(promises);
|
|
1140
|
-
const
|
|
1141
|
-
if (
|
|
1199
|
+
const fulfilled = translations.map((t) => t.status === 'fulfilled' ? t.value : null);
|
|
1200
|
+
if (fulfilled.at(0) === null && fulfilled.at(1) === null)
|
|
1142
1201
|
throw new Error('Failed to load translations');
|
|
1143
|
-
const [baseT, baseDefaultT] =
|
|
1202
|
+
const [baseT, baseDefaultT] = fulfilled;
|
|
1144
1203
|
const t = baseT ? compileTranslation(baseT, ns, locale) : null;
|
|
1145
1204
|
const defaultT = baseDefaultT
|
|
1146
1205
|
? compileTranslation(baseDefaultT, ns, defaultLocale)
|
|
@@ -1148,8 +1207,8 @@ function registerRemoteNamespace(ns, defaultTranslation, other) {
|
|
|
1148
1207
|
if (isDevMode() && t && t.locale !== locale && t.locale)
|
|
1149
1208
|
console.warn(`Expected locale to be ${locale} but got ${t.locale}`);
|
|
1150
1209
|
store.registerOnDemandLoaders(ns, {
|
|
1151
|
-
...
|
|
1152
|
-
[defaultLocale]: defaultTranslation,
|
|
1210
|
+
...compiledOther,
|
|
1211
|
+
[defaultLocale]: compileLoader(defaultTranslation, defaultLocale),
|
|
1153
1212
|
});
|
|
1154
1213
|
const toRegister = {};
|
|
1155
1214
|
if (t)
|
|
@@ -1372,6 +1431,10 @@ function createTransformFn() {
|
|
|
1372
1431
|
const store = inject(TranslationStore);
|
|
1373
1432
|
const t = createT(store);
|
|
1374
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.
|
|
1375
1438
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1376
1439
|
_) => {
|
|
1377
1440
|
const vars = typeof variablesOrLocale === 'string' ? undefined : variablesOrLocale;
|
|
@@ -1422,5 +1485,5 @@ function withParams(message) {
|
|
|
1422
1485
|
* Generated bundle index. Do not edit.
|
|
1423
1486
|
*/
|
|
1424
1487
|
|
|
1425
|
-
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 };
|
|
1426
1489
|
//# sourceMappingURL=mmstack-translate.mjs.map
|