@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.11 → 3.2.0-ultramodern.111
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/dist/cjs/cli/index.js +32 -5
- package/dist/cjs/runtime/I18nLink.js +21 -22
- package/dist/cjs/runtime/context.js +41 -10
- package/dist/cjs/runtime/hooks.js +17 -10
- package/dist/cjs/runtime/i18n/backend/config.js +9 -5
- package/dist/cjs/runtime/i18n/backend/defaults.js +15 -10
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +16 -11
- package/dist/cjs/runtime/i18n/backend/index.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.common.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.node.js +13 -9
- package/dist/cjs/runtime/i18n/backend/sdk-backend.js +9 -5
- package/dist/cjs/runtime/i18n/backend/sdk-event.js +16 -11
- package/dist/cjs/runtime/i18n/detection/config.js +9 -5
- package/dist/cjs/runtime/i18n/detection/index.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.node.js +9 -5
- package/dist/cjs/runtime/i18n/index.js +9 -5
- package/dist/cjs/runtime/i18n/instance.js +17 -37
- package/dist/cjs/runtime/i18n/react-i18next.js +53 -0
- package/dist/cjs/runtime/i18n/utils.js +9 -17
- package/dist/cjs/runtime/index.js +27 -15
- package/dist/cjs/runtime/routerAdapter.js +167 -0
- package/dist/cjs/runtime/utils.js +72 -99
- package/dist/cjs/server/index.js +69 -13
- package/dist/cjs/shared/deepMerge.js +12 -8
- package/dist/cjs/shared/detection.js +9 -5
- package/dist/cjs/shared/localisedUrls.js +241 -0
- package/dist/cjs/shared/utils.js +15 -11
- package/dist/esm/cli/index.mjs +23 -0
- package/dist/esm/runtime/I18nLink.mjs +12 -17
- package/dist/esm/runtime/context.mjs +34 -7
- package/dist/esm/runtime/hooks.mjs +9 -6
- package/dist/esm/runtime/i18n/backend/defaults.mjs +1 -1
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +2 -2
- package/dist/esm/runtime/i18n/backend/middleware.node.mjs +3 -3
- package/dist/esm/runtime/i18n/instance.mjs +1 -19
- package/dist/esm/runtime/i18n/react-i18next.mjs +15 -0
- package/dist/esm/runtime/i18n/utils.mjs +0 -12
- package/dist/esm/runtime/index.mjs +19 -11
- package/dist/esm/runtime/routerAdapter.mjs +129 -0
- package/dist/esm/runtime/utils.mjs +11 -30
- package/dist/esm/server/index.mjs +53 -7
- package/dist/esm/shared/localisedUrls.mjs +191 -0
- package/dist/esm-node/cli/index.mjs +23 -0
- package/dist/esm-node/runtime/I18nLink.mjs +12 -17
- package/dist/esm-node/runtime/context.mjs +34 -7
- package/dist/esm-node/runtime/hooks.mjs +9 -6
- package/dist/esm-node/runtime/i18n/backend/defaults.mjs +1 -1
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +2 -2
- package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +3 -3
- package/dist/esm-node/runtime/i18n/instance.mjs +1 -19
- package/dist/esm-node/runtime/i18n/react-i18next.mjs +16 -0
- package/dist/esm-node/runtime/i18n/utils.mjs +0 -12
- package/dist/esm-node/runtime/index.mjs +19 -11
- package/dist/esm-node/runtime/routerAdapter.mjs +130 -0
- package/dist/esm-node/runtime/utils.mjs +11 -30
- package/dist/esm-node/server/index.mjs +53 -7
- package/dist/esm-node/shared/localisedUrls.mjs +192 -0
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/runtime/I18nLink.d.ts +15 -0
- package/dist/types/runtime/context.d.ts +3 -0
- package/dist/types/runtime/hooks.d.ts +4 -2
- package/dist/types/runtime/i18n/backend/middleware.node.d.ts +1 -1
- package/dist/types/runtime/i18n/instance.d.ts +4 -6
- package/dist/types/runtime/i18n/react-i18next.d.ts +7 -0
- package/dist/types/runtime/index.d.ts +1 -0
- package/dist/types/runtime/routerAdapter.d.ts +26 -0
- package/dist/types/runtime/types.d.ts +1 -1
- package/dist/types/runtime/utils.d.ts +2 -7
- package/dist/types/server/index.d.ts +6 -0
- package/dist/types/shared/localisedUrls.d.ts +13 -0
- package/dist/types/shared/type.d.ts +12 -0
- package/package.json +23 -27
- package/rstest.config.mts +39 -0
- package/src/cli/index.ts +44 -1
- package/src/runtime/I18nLink.tsx +13 -17
- package/src/runtime/context.tsx +45 -7
- package/src/runtime/hooks.ts +13 -4
- package/src/runtime/i18n/backend/defaults.node.ts +2 -2
- package/src/runtime/i18n/backend/defaults.ts +3 -1
- package/src/runtime/i18n/backend/middleware.node.ts +1 -1
- package/src/runtime/i18n/instance.ts +3 -30
- package/src/runtime/i18n/react-i18next.ts +25 -0
- package/src/runtime/i18n/utils.ts +4 -26
- package/src/runtime/index.tsx +23 -10
- package/src/runtime/routerAdapter.tsx +333 -0
- package/src/runtime/types.ts +1 -1
- package/src/runtime/utils.ts +22 -34
- package/src/server/index.ts +117 -10
- package/src/shared/localisedUrls.ts +393 -0
- package/src/shared/type.ts +12 -0
- package/tests/i18nUtils.test.ts +52 -0
- package/tests/localisedUrls.test.ts +312 -0
- package/tests/routerAdapter.test.tsx +452 -0
- package/dist/esm/rslib-runtime.mjs +0 -18
- package/dist/esm-node/rslib-runtime.mjs +0 -19
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
+
})();
|
|
18
|
+
(()=>{
|
|
19
|
+
__webpack_require__.r = (exports1)=>{
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
+
value: 'Module'
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
+
value: true
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
})();
|
|
28
|
+
var __webpack_exports__ = {};
|
|
29
|
+
__webpack_require__.r(__webpack_exports__);
|
|
30
|
+
const LOCALE_PARAM_NAMES = new Set([
|
|
31
|
+
'lang',
|
|
32
|
+
'locale',
|
|
33
|
+
'language'
|
|
34
|
+
]);
|
|
35
|
+
const normalisePathPattern = (path)=>{
|
|
36
|
+
const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
|
|
37
|
+
const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
|
|
38
|
+
const withoutTrailingSlash = withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
|
|
39
|
+
return withoutTrailingSlash.replace(/\[(.+?)\]/g, ':$1');
|
|
40
|
+
};
|
|
41
|
+
const normaliseRoutePath = (path)=>{
|
|
42
|
+
const normalized = normalisePathPattern(path);
|
|
43
|
+
return '/' === normalized ? '' : normalized.slice(1);
|
|
44
|
+
};
|
|
45
|
+
const getLocaleParamSegment = (segment)=>{
|
|
46
|
+
if (!segment.startsWith(':')) return null;
|
|
47
|
+
const paramName = segment.slice(1).replace(/\?$/, '');
|
|
48
|
+
return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
|
|
49
|
+
};
|
|
50
|
+
const splitPathSegments = (path)=>{
|
|
51
|
+
if (!path) return [];
|
|
52
|
+
return normalisePathPattern(path).split('/').filter(Boolean);
|
|
53
|
+
};
|
|
54
|
+
const stripLeadingLocaleParam = (path)=>{
|
|
55
|
+
const segments = splitPathSegments(path);
|
|
56
|
+
const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
|
|
57
|
+
if (!leadingLocaleParam) return path;
|
|
58
|
+
const remainingPath = segments.slice(1).join('/');
|
|
59
|
+
return remainingPath ? `/${remainingPath}` : void 0;
|
|
60
|
+
};
|
|
61
|
+
const getLeadingLocaleParam = (path)=>{
|
|
62
|
+
const segments = splitPathSegments(path);
|
|
63
|
+
return getLocaleParamSegment(segments[0] || '');
|
|
64
|
+
};
|
|
65
|
+
const resolveLocalisedUrlsConfig = (option)=>{
|
|
66
|
+
if (false === option) return {
|
|
67
|
+
enabled: false,
|
|
68
|
+
map: {}
|
|
69
|
+
};
|
|
70
|
+
if (option && 'object' == typeof option) return {
|
|
71
|
+
enabled: true,
|
|
72
|
+
map: option
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
enabled: true,
|
|
76
|
+
map: {}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const isLocalisableRoutePath = (path)=>{
|
|
80
|
+
const pathWithoutLocale = stripLeadingLocaleParam(path);
|
|
81
|
+
if (!pathWithoutLocale || '/' === pathWithoutLocale || '*' === pathWithoutLocale) return false;
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
const joinPath = (parentPath, routePath)=>{
|
|
85
|
+
if (!isLocalisableRoutePath(routePath)) return parentPath;
|
|
86
|
+
const segment = normaliseRoutePath(stripLeadingLocaleParam(routePath) || '');
|
|
87
|
+
return normalisePathPattern(`${parentPath}/${segment}`);
|
|
88
|
+
};
|
|
89
|
+
const ensureLocalisedUrlsForPath = (canonicalPath, languages, localisedUrls)=>{
|
|
90
|
+
const entry = localisedUrls[canonicalPath];
|
|
91
|
+
if (!entry) throw new Error(`localisedUrls is enabled, but route "${canonicalPath}" does not define localised URLs for languages: ${languages.join(', ')}. Add localisedUrls["${canonicalPath}"] or set localeDetection.localisedUrls to false.`);
|
|
92
|
+
const missingLanguages = languages.filter((language)=>!entry[language]);
|
|
93
|
+
if (missingLanguages.length > 0) throw new Error(`localisedUrls["${canonicalPath}"] is missing languages: ${missingLanguages.join(', ')}. Every configured language must have a localised URL.`);
|
|
94
|
+
return entry;
|
|
95
|
+
};
|
|
96
|
+
const validateLocalisedUrls = (routes, languages, localisedUrls)=>{
|
|
97
|
+
const visit = (route, parentPath)=>{
|
|
98
|
+
const canonicalPath = joinPath(parentPath, route.path);
|
|
99
|
+
if (isLocalisableRoutePath(route.path)) ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls);
|
|
100
|
+
if ('children' in route && route.children) route.children.forEach((child)=>visit(child, canonicalPath));
|
|
101
|
+
};
|
|
102
|
+
routes.forEach((route)=>visit(route, ''));
|
|
103
|
+
};
|
|
104
|
+
const getLocalisedRoutePaths = (canonicalPath, parentLocalisedPaths, languages, entry)=>{
|
|
105
|
+
const paths = languages.map((language)=>{
|
|
106
|
+
const fullPath = normalisePathPattern(entry[language]);
|
|
107
|
+
const parentPath = normalisePathPattern(parentLocalisedPaths[language] || '/');
|
|
108
|
+
if ('/' === parentPath) return normaliseRoutePath(fullPath) || void 0;
|
|
109
|
+
const parentPrefix = `${parentPath}/`;
|
|
110
|
+
if (!fullPath.startsWith(parentPrefix)) throw new Error(`localisedUrls["${canonicalPath}"].${language} must be nested under "${parentPath}" because its parent route is localised there.`);
|
|
111
|
+
return normaliseRoutePath(fullPath.slice(parentPath.length));
|
|
112
|
+
});
|
|
113
|
+
return Array.from(new Set(paths.filter(Boolean)));
|
|
114
|
+
};
|
|
115
|
+
const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPaths, languages, localisedUrls)=>{
|
|
116
|
+
const canonicalPath = joinPath(parentCanonicalPath, route.path);
|
|
117
|
+
const localisedUrlEntry = isLocalisableRoutePath(route.path) ? ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls) : void 0;
|
|
118
|
+
const routeLocalisedPaths = localisedUrlEntry ? languages.reduce((acc, language)=>{
|
|
119
|
+
acc[language] = normalisePathPattern(localisedUrlEntry[language]);
|
|
120
|
+
return acc;
|
|
121
|
+
}, {}) : parentLocalisedPaths;
|
|
122
|
+
const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
|
|
123
|
+
const baseRoute = {
|
|
124
|
+
...route,
|
|
125
|
+
...children ? {
|
|
126
|
+
children
|
|
127
|
+
} : {}
|
|
128
|
+
};
|
|
129
|
+
if (!localisedUrlEntry) return [
|
|
130
|
+
baseRoute
|
|
131
|
+
];
|
|
132
|
+
return getLocalisedRoutePaths(canonicalPath, parentLocalisedPaths, languages, localisedUrlEntry).map((localisedPath, index)=>cloneRouteWithLocalisedPath(baseRoute, localisedPath, index));
|
|
133
|
+
};
|
|
134
|
+
const legalRouteIdPart = (value)=>value.replace(/[^a-zA-Z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'index';
|
|
135
|
+
const suffixRouteIds = (route, suffix)=>{
|
|
136
|
+
const children = 'children' in route && route.children ? route.children.map((child)=>suffixRouteIds(child, suffix)) : void 0;
|
|
137
|
+
return {
|
|
138
|
+
...route,
|
|
139
|
+
...route.id ? {
|
|
140
|
+
id: `${route.id}__localised_${suffix}`
|
|
141
|
+
} : {},
|
|
142
|
+
...children ? {
|
|
143
|
+
children
|
|
144
|
+
} : {}
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
const cloneRouteWithLocalisedPath = (route, path, index)=>{
|
|
148
|
+
const leadingLocaleParam = getLeadingLocaleParam(route.path);
|
|
149
|
+
const localisedPath = leadingLocaleParam ? normaliseRoutePath(`${leadingLocaleParam}/${path}`) : path;
|
|
150
|
+
const routeWithPath = {
|
|
151
|
+
...route,
|
|
152
|
+
path: localisedPath
|
|
153
|
+
};
|
|
154
|
+
return 0 === index ? routeWithPath : suffixRouteIds(routeWithPath, legalRouteIdPart(localisedPath));
|
|
155
|
+
};
|
|
156
|
+
const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
|
|
157
|
+
const rootLocalisedPaths = languages.reduce((acc, language)=>{
|
|
158
|
+
acc[language] = '/';
|
|
159
|
+
return acc;
|
|
160
|
+
}, {});
|
|
161
|
+
validateLocalisedUrls(routes, languages, localisedUrls);
|
|
162
|
+
return routes.flatMap((route)=>transformLocalisedRoute(route, '', rootLocalisedPaths, languages, localisedUrls));
|
|
163
|
+
};
|
|
164
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
165
|
+
const getParamName = (segment)=>segment.slice(1).replace(/\?$/, '');
|
|
166
|
+
const compilePathPattern = (pattern)=>{
|
|
167
|
+
const names = [];
|
|
168
|
+
const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
|
|
169
|
+
const source = segments.map((segment)=>{
|
|
170
|
+
if (segment.startsWith(':')) {
|
|
171
|
+
names.push(getParamName(segment));
|
|
172
|
+
const paramPattern = '([^/]+)';
|
|
173
|
+
return segment.endsWith('?') ? `(?:/${paramPattern})?` : `/${paramPattern}`;
|
|
174
|
+
}
|
|
175
|
+
if ('*' === segment) {
|
|
176
|
+
names.push('*');
|
|
177
|
+
return '/(.*)';
|
|
178
|
+
}
|
|
179
|
+
return `/${escapeRegExp(segment)}`;
|
|
180
|
+
}).join('');
|
|
181
|
+
return {
|
|
182
|
+
names,
|
|
183
|
+
regexp: new RegExp(`^${source || '/'}$`)
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const matchPathPattern = (pathname, pattern)=>{
|
|
187
|
+
const { names, regexp } = compilePathPattern(pattern);
|
|
188
|
+
const match = regexp.exec(normalisePathPattern(pathname));
|
|
189
|
+
if (!match) return null;
|
|
190
|
+
return names.reduce((params, name, index)=>{
|
|
191
|
+
params[name] = decodeURIComponent(match[index + 1] || '');
|
|
192
|
+
return params;
|
|
193
|
+
}, {});
|
|
194
|
+
};
|
|
195
|
+
const buildPathFromPattern = (pattern, params)=>{
|
|
196
|
+
const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
|
|
197
|
+
const path = segments.map((segment)=>{
|
|
198
|
+
if (segment.startsWith(':')) {
|
|
199
|
+
const param = params[getParamName(segment)];
|
|
200
|
+
return param ? encodeURIComponent(param) : '';
|
|
201
|
+
}
|
|
202
|
+
if ('*' === segment) return params['*'] || '';
|
|
203
|
+
return segment;
|
|
204
|
+
}).filter(Boolean).join('/');
|
|
205
|
+
return `/${path}`;
|
|
206
|
+
};
|
|
207
|
+
const resolveLocalisedPath = (pathname, targetLanguage, languages, localisedUrls)=>{
|
|
208
|
+
const normalizedPathname = normalisePathPattern(pathname);
|
|
209
|
+
for (const localisedUrlEntry of Object.values(localisedUrls)){
|
|
210
|
+
const targetPattern = localisedUrlEntry[targetLanguage];
|
|
211
|
+
if (targetPattern) for (const language of languages){
|
|
212
|
+
const sourcePattern = localisedUrlEntry[language];
|
|
213
|
+
if (!sourcePattern) continue;
|
|
214
|
+
const params = matchPathPattern(normalizedPathname, sourcePattern);
|
|
215
|
+
if (params) return buildPathFromPattern(targetPattern, params);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return normalizedPathname;
|
|
219
|
+
};
|
|
220
|
+
__webpack_require__.d(__webpack_exports__, {}, {
|
|
221
|
+
applyLocalisedUrlsToRoutes: applyLocalisedUrlsToRoutes,
|
|
222
|
+
normalisePathPattern: normalisePathPattern,
|
|
223
|
+
resolveLocalisedPath: resolveLocalisedPath,
|
|
224
|
+
resolveLocalisedUrlsConfig: resolveLocalisedUrlsConfig,
|
|
225
|
+
validateLocalisedUrls: validateLocalisedUrls
|
|
226
|
+
});
|
|
227
|
+
exports.applyLocalisedUrlsToRoutes = __webpack_exports__.applyLocalisedUrlsToRoutes;
|
|
228
|
+
exports.normalisePathPattern = __webpack_exports__.normalisePathPattern;
|
|
229
|
+
exports.resolveLocalisedPath = __webpack_exports__.resolveLocalisedPath;
|
|
230
|
+
exports.resolveLocalisedUrlsConfig = __webpack_exports__.resolveLocalisedUrlsConfig;
|
|
231
|
+
exports.validateLocalisedUrls = __webpack_exports__.validateLocalisedUrls;
|
|
232
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
233
|
+
"applyLocalisedUrlsToRoutes",
|
|
234
|
+
"normalisePathPattern",
|
|
235
|
+
"resolveLocalisedPath",
|
|
236
|
+
"resolveLocalisedUrlsConfig",
|
|
237
|
+
"validateLocalisedUrls"
|
|
238
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
239
|
+
Object.defineProperty(exports, '__esModule', {
|
|
240
|
+
value: true
|
|
241
|
+
});
|
package/dist/cjs/shared/utils.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_require__ = {};
|
|
3
3
|
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
9
13
|
};
|
|
10
14
|
})();
|
|
11
15
|
(()=>{
|
|
@@ -23,12 +27,6 @@ var __webpack_require__ = {};
|
|
|
23
27
|
})();
|
|
24
28
|
var __webpack_exports__ = {};
|
|
25
29
|
__webpack_require__.r(__webpack_exports__);
|
|
26
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
-
getBackendOptions: ()=>getBackendOptions,
|
|
28
|
-
getEntryConfig: ()=>getEntryConfig,
|
|
29
|
-
getLocaleDetectionOptions: ()=>getLocaleDetectionOptions,
|
|
30
|
-
removeEntryConfigKey: ()=>removeEntryConfigKey
|
|
31
|
-
});
|
|
32
30
|
function getEntryConfig(entryName, config, entryKey) {
|
|
33
31
|
const entryConfigMap = config[entryKey];
|
|
34
32
|
return entryConfigMap?.[entryName];
|
|
@@ -63,6 +61,12 @@ function getBackendOptions(entryName, backend) {
|
|
|
63
61
|
if ('backendOptionsByEntry' in fullConfig) return removeEntryConfigKey(fullConfig, 'backendOptionsByEntry');
|
|
64
62
|
return backend;
|
|
65
63
|
}
|
|
64
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
65
|
+
getBackendOptions: ()=>getBackendOptions,
|
|
66
|
+
getEntryConfig: ()=>getEntryConfig,
|
|
67
|
+
getLocaleDetectionOptions: ()=>getLocaleDetectionOptions,
|
|
68
|
+
removeEntryConfigKey: ()=>removeEntryConfigKey
|
|
69
|
+
});
|
|
66
70
|
exports.getBackendOptions = __webpack_exports__.getBackendOptions;
|
|
67
71
|
exports.getEntryConfig = __webpack_exports__.getEntryConfig;
|
|
68
72
|
exports.getLocaleDetectionOptions = __webpack_exports__.getLocaleDetectionOptions;
|
package/dist/esm/cli/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { applyLocalisedUrlsToRoutes, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
|
|
4
5
|
import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
|
|
6
|
+
import "../runtime/types.mjs";
|
|
5
7
|
function hasJsonFiles(dirPath) {
|
|
6
8
|
try {
|
|
7
9
|
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) return false;
|
|
@@ -80,6 +82,27 @@ const i18nPlugin = (options = {})=>({
|
|
|
80
82
|
plugins
|
|
81
83
|
};
|
|
82
84
|
});
|
|
85
|
+
api.modifyFileSystemRoutes(({ entrypoint, routes })=>{
|
|
86
|
+
if (!localeDetection) return {
|
|
87
|
+
entrypoint,
|
|
88
|
+
routes
|
|
89
|
+
};
|
|
90
|
+
const localeDetectionOptions = getLocaleDetectionOptions(entrypoint.entryName, localeDetection);
|
|
91
|
+
const { localePathRedirect, languages = [], localisedUrls } = localeDetectionOptions;
|
|
92
|
+
if (!localePathRedirect || 0 === languages.length) return {
|
|
93
|
+
entrypoint,
|
|
94
|
+
routes
|
|
95
|
+
};
|
|
96
|
+
const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
|
|
97
|
+
if (!localisedUrlsConfig.enabled) return {
|
|
98
|
+
entrypoint,
|
|
99
|
+
routes
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
entrypoint,
|
|
103
|
+
routes: applyLocalisedUrlsToRoutes(routes, languages, localisedUrlsConfig.map)
|
|
104
|
+
};
|
|
105
|
+
});
|
|
83
106
|
api._internalServerPlugins(({ plugins })=>{
|
|
84
107
|
const { serverRoutes, metaName } = api.getAppContext();
|
|
85
108
|
const normalizedConfig = api.getNormalizedConfig();
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Link as router_Link, useInRouterContext, useParams } from "@modern-js/runtime/router";
|
|
3
2
|
import { useModernI18n } from "./context.mjs";
|
|
3
|
+
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
4
4
|
import { buildLocalizedUrl } from "./utils.mjs";
|
|
5
|
-
const useRouterHooks = ()=>{
|
|
6
|
-
const inRouter = useInRouterContext();
|
|
7
|
-
return {
|
|
8
|
-
Link: inRouter ? router_Link : null,
|
|
9
|
-
params: inRouter ? useParams() : {},
|
|
10
|
-
hasRouter: inRouter
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
5
|
const I18nLink = ({ to, children, ...props })=>{
|
|
14
|
-
const { Link, params, hasRouter } =
|
|
15
|
-
const { language, supportedLanguages } = useModernI18n();
|
|
6
|
+
const { Link, params, hasRouter } = useI18nRouterAdapter();
|
|
7
|
+
const { language, supportedLanguages, localisedUrls } = useModernI18n();
|
|
16
8
|
const currentLang = language;
|
|
17
|
-
const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages);
|
|
9
|
+
const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages, localisedUrls);
|
|
18
10
|
if ('development' === process.env.NODE_ENV && hasRouter && !params.lang) console.warn("I18nLink is being used outside of a :lang dynamic route context. This may cause unexpected behavior. Please ensure I18nLink is used within a route that has a :lang parameter.");
|
|
19
|
-
if (!hasRouter || !Link)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
if (!hasRouter || !Link) {
|
|
12
|
+
const { prefetch: _prefetch, preload: _preload, ...anchorProps } = props;
|
|
13
|
+
return /*#__PURE__*/ jsx("a", {
|
|
14
|
+
href: localizedTo,
|
|
15
|
+
...anchorProps,
|
|
16
|
+
children: children
|
|
17
|
+
});
|
|
18
|
+
}
|
|
24
19
|
return /*#__PURE__*/ jsx(Link, {
|
|
25
20
|
to: localizedTo,
|
|
26
21
|
...props,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { isBrowser } from "@modern-js/runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useMemo } from "react";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
|
|
4
4
|
import { cacheUserLanguage } from "./i18n/detection/index.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
6
|
+
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
6
7
|
const ModernI18nContext = /*#__PURE__*/ createContext(null);
|
|
7
8
|
const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nContext.Provider, {
|
|
8
9
|
value: value,
|
|
@@ -11,9 +12,33 @@ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nCo
|
|
|
11
12
|
const useModernI18n = ()=>{
|
|
12
13
|
const context = useContext(ModernI18nContext);
|
|
13
14
|
if (!context) throw new Error('useModernI18n must be used within a ModernI18nProvider');
|
|
14
|
-
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, updateLanguage } = context;
|
|
15
|
-
const { navigate, location, hasRouter } =
|
|
16
|
-
const
|
|
15
|
+
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage } = context;
|
|
16
|
+
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
17
|
+
const pathLanguage = useMemo(()=>{
|
|
18
|
+
if (!localePathRedirect || !location?.pathname) return;
|
|
19
|
+
const detected = detectLanguageFromPath(location.pathname, languages || [], localePathRedirect);
|
|
20
|
+
return detected.detected ? detected.language : void 0;
|
|
21
|
+
}, [
|
|
22
|
+
languages,
|
|
23
|
+
localePathRedirect,
|
|
24
|
+
location?.pathname
|
|
25
|
+
]);
|
|
26
|
+
const currentLanguage = pathLanguage || contextLanguage;
|
|
27
|
+
useEffect(()=>{
|
|
28
|
+
if (!pathLanguage || pathLanguage === contextLanguage) return;
|
|
29
|
+
updateLanguage?.(pathLanguage);
|
|
30
|
+
i18nInstance?.setLang?.(pathLanguage);
|
|
31
|
+
i18nInstance?.changeLanguage?.(pathLanguage);
|
|
32
|
+
if (isBrowser()) {
|
|
33
|
+
const detectionOptions = i18nInstance.options?.detection;
|
|
34
|
+
cacheUserLanguage(i18nInstance, pathLanguage, detectionOptions);
|
|
35
|
+
}
|
|
36
|
+
}, [
|
|
37
|
+
contextLanguage,
|
|
38
|
+
i18nInstance,
|
|
39
|
+
pathLanguage,
|
|
40
|
+
updateLanguage
|
|
41
|
+
]);
|
|
17
42
|
const changeLanguage = useCallback(async (newLang)=>{
|
|
18
43
|
try {
|
|
19
44
|
if (!newLang || 'string' != typeof newLang) throw new Error('Language must be a non-empty string');
|
|
@@ -30,7 +55,7 @@ const useModernI18n = ()=>{
|
|
|
30
55
|
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
31
56
|
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
32
57
|
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
33
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
|
|
58
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
34
59
|
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
35
60
|
await navigate(newUrl, {
|
|
36
61
|
replace: true
|
|
@@ -43,7 +68,7 @@ const useModernI18n = ()=>{
|
|
|
43
68
|
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
44
69
|
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
45
70
|
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
46
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
|
|
71
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
47
72
|
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
48
73
|
window.history.pushState(null, '', newUrl);
|
|
49
74
|
}
|
|
@@ -58,6 +83,7 @@ const useModernI18n = ()=>{
|
|
|
58
83
|
updateLanguage,
|
|
59
84
|
localePathRedirect,
|
|
60
85
|
ignoreRedirectRoutes,
|
|
86
|
+
localisedUrls,
|
|
61
87
|
languages,
|
|
62
88
|
hasRouter,
|
|
63
89
|
navigate,
|
|
@@ -98,6 +124,7 @@ const useModernI18n = ()=>{
|
|
|
98
124
|
changeLanguage,
|
|
99
125
|
i18nInstance,
|
|
100
126
|
supportedLanguages: languages || [],
|
|
127
|
+
localisedUrls,
|
|
101
128
|
isLanguageSupported,
|
|
102
129
|
isResourcesReady
|
|
103
130
|
};
|
|
@@ -2,7 +2,8 @@ import { isBrowser } from "@modern-js/runtime";
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { I18N_SDK_RESOURCES_LOADED_EVENT, getI18nSdkBackendId } from "./i18n/backend/sdk-event.mjs";
|
|
4
4
|
import { cacheUserLanguage } from "./i18n/detection/index.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
6
|
+
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect } from "./utils.mjs";
|
|
6
7
|
function createMinimalI18nInstance(language) {
|
|
7
8
|
const minimalInstance = {
|
|
8
9
|
language,
|
|
@@ -14,7 +15,7 @@ function createMinimalI18nInstance(language) {
|
|
|
14
15
|
};
|
|
15
16
|
return minimalInstance;
|
|
16
17
|
}
|
|
17
|
-
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang) {
|
|
18
|
+
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang) {
|
|
18
19
|
const instance = i18nInstance || createMinimalI18nInstance(lang);
|
|
19
20
|
return {
|
|
20
21
|
language: lang,
|
|
@@ -23,6 +24,7 @@ function createContextValue(lang, i18nInstance, entryName, languages, localePath
|
|
|
23
24
|
languages,
|
|
24
25
|
localePathRedirect,
|
|
25
26
|
ignoreRedirectRoutes,
|
|
27
|
+
localisedUrls,
|
|
26
28
|
updateLanguage: setLang
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -72,9 +74,9 @@ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
|
|
|
72
74
|
setForceUpdate
|
|
73
75
|
]);
|
|
74
76
|
}
|
|
75
|
-
function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes) {
|
|
77
|
+
function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls) {
|
|
76
78
|
const hasRedirectedRef = useRef(false);
|
|
77
|
-
const { navigate, location, hasRouter } =
|
|
79
|
+
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
78
80
|
useEffect(()=>{
|
|
79
81
|
if ('browser' !== process.env.MODERN_TARGET) return;
|
|
80
82
|
if (!localePathRedirect || !i18nInstance) return;
|
|
@@ -93,7 +95,7 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
|
|
|
93
95
|
const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
|
|
94
96
|
if (pathDetection.detected) return;
|
|
95
97
|
const targetLanguage = i18nInstance.language || fallbackLanguage || languages[0] || 'en';
|
|
96
|
-
const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages);
|
|
98
|
+
const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages, localisedUrls);
|
|
97
99
|
const newUrl = entryPath + newPath + currentSearch + currentHash;
|
|
98
100
|
if (newUrl !== currentPathname + currentSearch + currentHash) {
|
|
99
101
|
hasRedirectedRef.current = true;
|
|
@@ -110,7 +112,8 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
|
|
|
110
112
|
i18nInstance,
|
|
111
113
|
languages,
|
|
112
114
|
fallbackLanguage,
|
|
113
|
-
ignoreRedirectRoutes
|
|
115
|
+
ignoreRedirectRoutes,
|
|
116
|
+
localisedUrls
|
|
114
117
|
]);
|
|
115
118
|
}
|
|
116
119
|
function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang) {
|
|
@@ -4,7 +4,7 @@ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
|
|
|
4
4
|
};
|
|
5
5
|
function convertPath(path) {
|
|
6
6
|
if (!path) return path;
|
|
7
|
-
if (path.startsWith('/')) return `${window.__assetPrefix__ || ''}${path}`;
|
|
7
|
+
if (path.startsWith('/')) return "u" < typeof window ? path : `${window.__assetPrefix__ || ''}${path}`;
|
|
8
8
|
return path;
|
|
9
9
|
}
|
|
10
10
|
function convertBackendOptions(options) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
|
|
2
|
-
loadPath: './locales/{{lng}}/{{ns}}.json',
|
|
3
|
-
addPath: './locales/{{lng}}/{{ns}}.json'
|
|
2
|
+
loadPath: './config/public/locales/{{lng}}/{{ns}}.json',
|
|
3
|
+
addPath: './config/public/locales/{{lng}}/{{ns}}.json'
|
|
4
4
|
};
|
|
5
5
|
function convertPath(path) {
|
|
6
6
|
if (!path) return path;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cjs from "i18next-fs-backend/cjs";
|
|
2
2
|
import { useI18nextBackendCommon } from "./middleware.common.mjs";
|
|
3
|
-
class FsBackendWithSave extends
|
|
3
|
+
class FsBackendWithSave extends cjs {
|
|
4
4
|
save(_language, _namespace, _data) {}
|
|
5
5
|
}
|
|
6
6
|
const HttpBackendWithSave = FsBackendWithSave;
|
|
7
|
-
const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave,
|
|
7
|
+
const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, cjs, backend);
|
|
8
8
|
export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
|
|
@@ -40,14 +40,6 @@ async function createI18nextInstance() {
|
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
async function tryImportReactI18next() {
|
|
44
|
-
try {
|
|
45
|
-
const reactI18next = await import("react-i18next");
|
|
46
|
-
return reactI18next;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
43
|
function getI18nextInstanceForProvider(instance) {
|
|
52
44
|
if (isI18nWrapperInstance(instance)) {
|
|
53
45
|
const i18nextInstance = getI18nWrapperI18nextInstance(instance);
|
|
@@ -64,14 +56,4 @@ async function getI18nInstance(userInstance) {
|
|
|
64
56
|
if (i18nextInstance) return i18nextInstance;
|
|
65
57
|
throw new Error('No i18n instance found');
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
const reactI18nextModule = await tryImportReactI18next();
|
|
69
|
-
if (reactI18nextModule) return reactI18nextModule.initReactI18next;
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
async function getI18nextProvider() {
|
|
73
|
-
const reactI18nextModule = await tryImportReactI18next();
|
|
74
|
-
if (reactI18nextModule) return reactI18nextModule.I18nextProvider;
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
export { getActualI18nextInstance, getI18nInstance, getI18nWrapperI18nextInstance, getI18nextInstanceForProvider, getI18nextProvider, getInitReactI18next, isI18nInstance, isI18nWrapperInstance };
|
|
59
|
+
export { getActualI18nextInstance, getI18nInstance, getI18nWrapperI18nextInstance, getI18nextInstanceForProvider, isI18nInstance, isI18nWrapperInstance };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async function tryImportReactI18next() {
|
|
2
|
+
try {
|
|
3
|
+
return await import("react-i18next");
|
|
4
|
+
} catch (error) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
async function getReactI18nextIntegration() {
|
|
9
|
+
const reactI18nextModule = await tryImportReactI18next();
|
|
10
|
+
return {
|
|
11
|
+
I18nextProvider: reactI18nextModule?.I18nextProvider ?? null,
|
|
12
|
+
initReactI18next: reactI18nextModule?.initReactI18next ?? null
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export { getReactI18nextIntegration };
|
|
@@ -100,18 +100,6 @@ const initializeI18nInstance = async (i18nInstance, finalLanguage, fallbackLangu
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
if (mergedBackend && hasOptions(i18nInstance)) {
|
|
104
|
-
const defaultNS = initOptions.defaultNS || initOptions.ns || 'translation';
|
|
105
|
-
const ns = Array.isArray(defaultNS) ? defaultNS[0] : defaultNS;
|
|
106
|
-
let retries = 20;
|
|
107
|
-
while(retries > 0){
|
|
108
|
-
const actualInstance = getActualI18nextInstance(i18nInstance);
|
|
109
|
-
const store = actualInstance.store;
|
|
110
|
-
if (store?.data?.[finalLanguage]?.[ns]) break;
|
|
111
|
-
await new Promise((resolve)=>setTimeout(resolve, 100));
|
|
112
|
-
retries--;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
103
|
}
|
|
116
104
|
};
|
|
117
105
|
function hasOptions(instance) {
|