@bleedingdev/modern-js-i18n-integration 0.0.0 → 3.9.0-ultramodern.6
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/LICENSE +21 -0
- package/dist/cjs/cli.js +97 -0
- package/dist/cjs/federation/boundary.js +100 -0
- package/dist/cjs/federation/registration.js +43 -0
- package/dist/cjs/languageSynchronization.js +44 -0
- package/dist/cjs/navigation.js +220 -0
- package/dist/cjs/options.js +18 -0
- package/dist/cjs/runtime-no-react-i18next.js +46 -0
- package/dist/cjs/runtime.js +46 -0
- package/dist/cjs/runtimeComposition.js +60 -0
- package/dist/cjs/server.js +50 -0
- package/dist/cjs/urlStrategy.js +51 -0
- package/dist/esm/cli.mjs +57 -0
- package/dist/esm/federation/boundary.mjs +62 -0
- package/dist/esm/federation/registration.mjs +5 -0
- package/dist/esm/languageSynchronization.mjs +6 -0
- package/dist/esm/navigation.mjs +182 -0
- package/dist/esm/options.mjs +0 -0
- package/dist/esm/runtime-no-react-i18next.mjs +6 -0
- package/dist/esm/runtime.mjs +6 -0
- package/dist/esm/runtimeComposition.mjs +22 -0
- package/dist/esm/server.mjs +10 -0
- package/dist/esm/urlStrategy.mjs +13 -0
- package/dist/esm-node/cli.mjs +58 -0
- package/dist/esm-node/federation/boundary.mjs +63 -0
- package/dist/esm-node/federation/registration.mjs +6 -0
- package/dist/esm-node/languageSynchronization.mjs +7 -0
- package/dist/esm-node/navigation.mjs +183 -0
- package/dist/esm-node/options.mjs +1 -0
- package/dist/esm-node/runtime-no-react-i18next.mjs +7 -0
- package/dist/esm-node/runtime.mjs +7 -0
- package/dist/esm-node/runtimeComposition.mjs +23 -0
- package/dist/esm-node/server.mjs +11 -0
- package/dist/esm-node/urlStrategy.mjs +14 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/federation/boundary.d.ts +9 -0
- package/dist/types/federation/registration.d.ts +2 -0
- package/dist/types/languageSynchronization.d.ts +3 -0
- package/dist/types/navigation.d.ts +3 -0
- package/dist/types/options.d.ts +13 -0
- package/dist/types/runtime-no-react-i18next.d.ts +2 -0
- package/dist/types/runtime.d.ts +2 -0
- package/dist/types/runtimeComposition.d.ts +4 -0
- package/dist/types/server.d.ts +3 -0
- package/dist/types/urlStrategy.d.ts +4 -0
- package/package.json +139 -5
- package/src/cli.ts +72 -0
- package/src/federation/boundary.tsx +129 -0
- package/src/federation/registration.ts +8 -0
- package/src/languageSynchronization.tsx +23 -0
- package/src/navigation.tsx +399 -0
- package/src/options.ts +18 -0
- package/src/runtime-no-react-i18next.ts +5 -0
- package/src/runtime.ts +5 -0
- package/src/runtimeComposition.ts +33 -0
- package/src/server.ts +27 -0
- package/src/urlStrategy.ts +19 -0
- package/README.md +0 -3
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ModernI18nContext, ModernI18nProvider, ReactI18nextProviderContext, getActualI18nextInstance } from "@modern-js/plugin-i18n/runtime/consumer";
|
|
4
|
+
import { useContext, useMemo } from "react";
|
|
5
|
+
const FederatedI18nBoundary = ({ children, defaultNamespace, fallbackLanguage, resources, supportedLanguages })=>{
|
|
6
|
+
const parent = useContext(ModernI18nContext);
|
|
7
|
+
const I18nextProvider = useContext(ReactI18nextProviderContext);
|
|
8
|
+
if (!parent) throw new Error('FederatedI18nBoundary must be used within ModernI18nProvider');
|
|
9
|
+
const languages = supportedLanguages ?? parent.languages ?? Object.keys(resources);
|
|
10
|
+
const scopedInstance = useMemo(()=>{
|
|
11
|
+
const parentInstance = getActualI18nextInstance(parent.i18nInstance);
|
|
12
|
+
const clone = parentInstance.cloneInstance?.({
|
|
13
|
+
defaultNS: defaultNamespace,
|
|
14
|
+
fallbackLng: fallbackLanguage ?? languages[0] ?? parent.language,
|
|
15
|
+
forkResourceStore: true,
|
|
16
|
+
initImmediate: false,
|
|
17
|
+
lng: parent.language,
|
|
18
|
+
ns: [
|
|
19
|
+
defaultNamespace
|
|
20
|
+
],
|
|
21
|
+
resources,
|
|
22
|
+
supportedLngs: languages
|
|
23
|
+
});
|
|
24
|
+
if (!clone) throw new Error('FederatedI18nBoundary requires an i18n instance with cloneInstance support');
|
|
25
|
+
const resourceStore = clone.store;
|
|
26
|
+
const parentResourceStore = parentInstance.store;
|
|
27
|
+
const sharesNestedResourceState = Object.entries(resourceStore?.data ?? {}).some(([language, namespaces])=>{
|
|
28
|
+
const parentNamespaces = parentResourceStore?.data?.[language];
|
|
29
|
+
return namespaces === parentNamespaces || Object.entries(namespaces).some(([namespace, resource])=>null !== resource && 'object' == typeof resource && resource === parentNamespaces?.[namespace]);
|
|
30
|
+
});
|
|
31
|
+
if (clone === parentInstance || resourceStore === parentResourceStore || resourceStore?.data === parentResourceStore?.data || sharesNestedResourceState) throw new Error('FederatedI18nBoundary cloneInstance did not isolate the host resource store');
|
|
32
|
+
if (!resourceStore?.addResourceBundle || !clone.removeResourceBundle || !resourceStore.data) throw new Error('FederatedI18nBoundary requires an isolated mutable i18n resource store');
|
|
33
|
+
for (const [language, namespaces] of Object.entries(resourceStore.data))for (const namespace of Object.keys(namespaces))clone.removeResourceBundle(language, namespace);
|
|
34
|
+
for (const [language, namespaces] of Object.entries(resources))for (const [namespace, resource] of Object.entries(namespaces))resourceStore.addResourceBundle(language, namespace, resource, true, true);
|
|
35
|
+
return clone;
|
|
36
|
+
}, [
|
|
37
|
+
defaultNamespace,
|
|
38
|
+
fallbackLanguage,
|
|
39
|
+
languages,
|
|
40
|
+
parent.i18nInstance,
|
|
41
|
+
parent.language,
|
|
42
|
+
resources
|
|
43
|
+
]);
|
|
44
|
+
const value = useMemo(()=>({
|
|
45
|
+
...parent,
|
|
46
|
+
i18nInstance: scopedInstance,
|
|
47
|
+
language: parent.language,
|
|
48
|
+
languages
|
|
49
|
+
}), [
|
|
50
|
+
languages,
|
|
51
|
+
parent,
|
|
52
|
+
scopedInstance
|
|
53
|
+
]);
|
|
54
|
+
const scopedContent = /*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
55
|
+
value: value,
|
|
56
|
+
children: children
|
|
57
|
+
});
|
|
58
|
+
return I18nextProvider ? /*#__PURE__*/ jsx(I18nextProvider, {
|
|
59
|
+
i18n: scopedInstance,
|
|
60
|
+
children: scopedContent
|
|
61
|
+
}) : scopedContent;
|
|
62
|
+
};
|
|
63
|
+
export { FederatedI18nBoundary };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { FederatedI18nBoundary } from "./boundary.mjs";
|
|
3
|
+
function registerFederatedI18nBoundary(api) {
|
|
4
|
+
api.resolveComponent((component, { name })=>'i18n.FederatedI18nBoundary' === name ? FederatedI18nBoundary : component);
|
|
5
|
+
}
|
|
6
|
+
export { registerFederatedI18nBoundary };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { useLanguageSync } from "@modern-js/i18n-runtime-extensions";
|
|
3
|
+
const I18nLanguageSynchronization = ({ i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang, children })=>{
|
|
4
|
+
const synchronizeLanguage = useLanguageSync(i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang);
|
|
5
|
+
return children(synchronizeLanguage);
|
|
6
|
+
};
|
|
7
|
+
export { I18nLanguageSynchronization };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { I18nNavigationProvider, useNativeI18nRouterAdapter } from "@modern-js/plugin-i18n/runtime/no-react-i18next";
|
|
4
|
+
import { RuntimeContext, isBrowser } from "@modern-js/runtime";
|
|
5
|
+
import { InternalRuntimeContext } from "@modern-js/runtime/context";
|
|
6
|
+
import { getRouterRuntimeState } from "@modern-js/runtime-extensions/router-state";
|
|
7
|
+
import { useCallback, useContext, useSyncExternalStore } from "react";
|
|
8
|
+
const normalizeUrlPart = (value, prefix)=>{
|
|
9
|
+
if ('string' != typeof value || !value) return '';
|
|
10
|
+
return value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
11
|
+
};
|
|
12
|
+
const normalizeLocation = (location)=>{
|
|
13
|
+
if (!location || 'object' != typeof location) return null;
|
|
14
|
+
const locationValue = location;
|
|
15
|
+
if ('string' != typeof locationValue.pathname) return null;
|
|
16
|
+
return {
|
|
17
|
+
pathname: locationValue.pathname,
|
|
18
|
+
search: normalizeUrlPart('string' == typeof locationValue.search ? locationValue.search : locationValue.searchStr, '?'),
|
|
19
|
+
hash: normalizeUrlPart(locationValue.hash, '#')
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const getWindowLocation = ()=>{
|
|
23
|
+
if (!isBrowser()) return null;
|
|
24
|
+
return {
|
|
25
|
+
pathname: window.location.pathname,
|
|
26
|
+
search: window.location.search,
|
|
27
|
+
hash: window.location.hash
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
|
|
31
|
+
const framework = getRouterRuntimeState(internalContext)?.framework || getRouterRuntimeState(runtimeContext)?.framework;
|
|
32
|
+
if (framework) return framework;
|
|
33
|
+
if (internalContext.router?.useRouter || runtimeContext.router?.useRouter) return 'tanstack';
|
|
34
|
+
if (internalContext.router?.useLocation || internalContext.router?.useHref || runtimeContext.router?.useLocation || runtimeContext.router?.useHref) return 'react-router';
|
|
35
|
+
if (inReactRouter) return 'react-router';
|
|
36
|
+
};
|
|
37
|
+
const getRouterInstance = (internalContext, contextRouter)=>{
|
|
38
|
+
if (contextRouter) return contextRouter;
|
|
39
|
+
const router = getRouterRuntimeState(internalContext)?.instance;
|
|
40
|
+
if (!router || 'object' != typeof router) return null;
|
|
41
|
+
return router;
|
|
42
|
+
};
|
|
43
|
+
const getRouterStateLocation = (internalContext, contextRouter)=>{
|
|
44
|
+
const router = getRouterInstance(internalContext, contextRouter);
|
|
45
|
+
return normalizeLocation(router?.stores?.location?.get?.()) || normalizeLocation(router?.state?.location);
|
|
46
|
+
};
|
|
47
|
+
const getRouterParams = (internalContext, contextRouter)=>{
|
|
48
|
+
const router = getRouterInstance(internalContext, contextRouter);
|
|
49
|
+
const matches = router?.stores?.matches?.get?.() || router?.state?.matches;
|
|
50
|
+
if (!Array.isArray(matches)) return {};
|
|
51
|
+
return matches.reduce((params, match)=>{
|
|
52
|
+
if (match?.params) Object.assign(params, match.params);
|
|
53
|
+
return params;
|
|
54
|
+
}, {});
|
|
55
|
+
};
|
|
56
|
+
const getRouterSnapshot = (internalContext, contextRouter)=>{
|
|
57
|
+
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
58
|
+
const params = getRouterParams(internalContext, contextRouter);
|
|
59
|
+
return JSON.stringify([
|
|
60
|
+
location?.pathname ?? '',
|
|
61
|
+
location?.search ?? '',
|
|
62
|
+
location?.hash ?? '',
|
|
63
|
+
Object.entries(params).sort(([left], [right])=>left.localeCompare(right))
|
|
64
|
+
]);
|
|
65
|
+
};
|
|
66
|
+
const useIntegratedRouterAdapter = ()=>{
|
|
67
|
+
const runtimeContext = useContext(RuntimeContext);
|
|
68
|
+
const internalContext = useContext(InternalRuntimeContext);
|
|
69
|
+
const routerApi = internalContext.router || runtimeContext.router;
|
|
70
|
+
const native = useNativeI18nRouterAdapter();
|
|
71
|
+
const inReactRouter = native.hasRouter;
|
|
72
|
+
const reactRouterNavigate = native.navigate;
|
|
73
|
+
const reactRouterLocation = inReactRouter ? native.location : null;
|
|
74
|
+
const reactRouterParams = native.params;
|
|
75
|
+
const framework = getRouterFramework(runtimeContext, internalContext, inReactRouter);
|
|
76
|
+
const contextUseRouter = inReactRouter || 'tanstack' !== framework ? void 0 : internalContext.router?.useRouter || runtimeContext.router?.useRouter;
|
|
77
|
+
const contextRouter = contextUseRouter ? contextUseRouter({
|
|
78
|
+
warn: false
|
|
79
|
+
}) : null;
|
|
80
|
+
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
81
|
+
const subscribeToRouter = useCallback((update)=>{
|
|
82
|
+
const router = getRouterInstance(internalContext, contextRouter);
|
|
83
|
+
if (!router) return ()=>void 0;
|
|
84
|
+
const unsubscribers = [];
|
|
85
|
+
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
86
|
+
const subscribe = router.subscribe;
|
|
87
|
+
const unsubscribe = subscribe.call(router, update);
|
|
88
|
+
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
89
|
+
}
|
|
90
|
+
if ('tanstack' === framework && 'function' == typeof router.stores?.location?.subscribe) {
|
|
91
|
+
const unsubscribe = router.stores.location.subscribe(update);
|
|
92
|
+
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
93
|
+
}
|
|
94
|
+
if ('tanstack' === framework && 'function' == typeof router.subscribe) {
|
|
95
|
+
const subscribe = router.subscribe;
|
|
96
|
+
for (const eventType of [
|
|
97
|
+
'onBeforeNavigate',
|
|
98
|
+
'onBeforeLoad'
|
|
99
|
+
]){
|
|
100
|
+
const unsubscribe = subscribe.call(router, eventType, update);
|
|
101
|
+
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return ()=>{
|
|
105
|
+
for (const unsubscribe of unsubscribers)unsubscribe();
|
|
106
|
+
};
|
|
107
|
+
}, [
|
|
108
|
+
contextRouter,
|
|
109
|
+
framework,
|
|
110
|
+
inReactRouter,
|
|
111
|
+
internalContext
|
|
112
|
+
]);
|
|
113
|
+
const getSnapshot = useCallback(()=>getRouterSnapshot(internalContext, contextRouter), [
|
|
114
|
+
contextRouter,
|
|
115
|
+
internalContext
|
|
116
|
+
]);
|
|
117
|
+
useSyncExternalStore(subscribeToRouter, getSnapshot, getSnapshot);
|
|
118
|
+
const navigate = useCallback((href, options)=>{
|
|
119
|
+
const router = getRouterInstance(internalContext, contextRouter);
|
|
120
|
+
const activeFramework = getRouterFramework(runtimeContext, internalContext, inReactRouter);
|
|
121
|
+
if ('tanstack' === activeFramework) {
|
|
122
|
+
if ('function' == typeof router?.navigate) return router.navigate({
|
|
123
|
+
to: href,
|
|
124
|
+
replace: options?.replace,
|
|
125
|
+
...options?.state === void 0 ? {} : {
|
|
126
|
+
state: options.state
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
throw new Error('TanStack router instance is not available.');
|
|
130
|
+
}
|
|
131
|
+
if (reactRouterNavigate) return reactRouterNavigate(href, options);
|
|
132
|
+
if ('react-router' === activeFramework) {
|
|
133
|
+
if ('function' == typeof router?.navigate) return router.navigate(href, options);
|
|
134
|
+
throw new Error('React Router instance is not available.');
|
|
135
|
+
}
|
|
136
|
+
}, [
|
|
137
|
+
contextRouter,
|
|
138
|
+
internalContext,
|
|
139
|
+
inReactRouter,
|
|
140
|
+
reactRouterNavigate,
|
|
141
|
+
runtimeContext
|
|
142
|
+
]);
|
|
143
|
+
const location = (reactRouterLocation ? normalizeLocation(reactRouterLocation) : getRouterStateLocation(internalContext, contextRouter)) || getWindowLocation();
|
|
144
|
+
const params = inReactRouter ? reactRouterParams : getRouterParams(internalContext, contextRouter);
|
|
145
|
+
const Link = 'tanstack' === framework ? internalContext.router?.Link || runtimeContext.router?.Link || null : 'react-router' === framework || inReactRouter ? routerApi?.Link ?? null : null;
|
|
146
|
+
return {
|
|
147
|
+
framework,
|
|
148
|
+
hasRouter,
|
|
149
|
+
location,
|
|
150
|
+
navigate: hasRouter ? navigate : null,
|
|
151
|
+
Link,
|
|
152
|
+
params,
|
|
153
|
+
...'tanstack' === framework ? {
|
|
154
|
+
createLinkProps: createTanstackLinkProps
|
|
155
|
+
} : {}
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const createTanstackLinkProps = (target)=>{
|
|
159
|
+
const preload = void 0 !== target.preload ? target.preload : 'none' === target.prefetch ? false : target.prefetch;
|
|
160
|
+
return {
|
|
161
|
+
to: target.pathname,
|
|
162
|
+
...target.search ? {
|
|
163
|
+
search: target.search
|
|
164
|
+
} : {},
|
|
165
|
+
...target.hash ? {
|
|
166
|
+
hash: target.hash
|
|
167
|
+
} : {},
|
|
168
|
+
...void 0 === target.hashScrollIntoView ? {} : {
|
|
169
|
+
hashScrollIntoView: target.hashScrollIntoView
|
|
170
|
+
},
|
|
171
|
+
...void 0 === preload ? {} : {
|
|
172
|
+
preload
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const I18nRouterNavigationProvider = ({ children })=>{
|
|
177
|
+
const value = useIntegratedRouterAdapter();
|
|
178
|
+
return /*#__PURE__*/ jsx(I18nNavigationProvider, {
|
|
179
|
+
value: value,
|
|
180
|
+
children: children
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
export { I18nRouterNavigationProvider };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "node:module";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { i18nPlugin } from "@modern-js/plugin-i18n/runtime/no-react-i18next";
|
|
3
|
+
import { composeI18nPlugin } from "./runtimeComposition.mjs";
|
|
4
|
+
const runtime_no_react_i18next_i18nPlugin = composeI18nPlugin(i18nPlugin);
|
|
5
|
+
const runtime_no_react_i18next = runtime_no_react_i18next_i18nPlugin;
|
|
6
|
+
export default runtime_no_react_i18next;
|
|
7
|
+
export { runtime_no_react_i18next_i18nPlugin as i18nPlugin };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { i18nPlugin } from "@modern-js/plugin-i18n/runtime";
|
|
3
|
+
import { composeI18nPlugin } from "./runtimeComposition.mjs";
|
|
4
|
+
const runtime_i18nPlugin = composeI18nPlugin(i18nPlugin);
|
|
5
|
+
const runtime = runtime_i18nPlugin;
|
|
6
|
+
export default runtime;
|
|
7
|
+
export { runtime_i18nPlugin as i18nPlugin };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { registerFederatedI18nBoundary } from "./federation/registration.mjs";
|
|
3
|
+
import { I18nLanguageSynchronization } from "./languageSynchronization.mjs";
|
|
4
|
+
import { I18nRouterNavigationProvider } from "./navigation.mjs";
|
|
5
|
+
import { createI18nUrlStrategy } from "./urlStrategy.mjs";
|
|
6
|
+
const composeI18nPlugin = (nativeI18nPlugin)=>(options = {})=>{
|
|
7
|
+
const { localisedUrls, localeDetectionByEntry: _entryOptions, ...nativeLocaleDetection } = options.localeDetection ?? {};
|
|
8
|
+
const native = nativeI18nPlugin({
|
|
9
|
+
...options,
|
|
10
|
+
localeDetection: nativeLocaleDetection,
|
|
11
|
+
urlStrategy: options.urlStrategy ?? createI18nUrlStrategy(localisedUrls),
|
|
12
|
+
NavigationProvider: options.NavigationProvider ?? I18nRouterNavigationProvider,
|
|
13
|
+
LanguageSynchronization: options.LanguageSynchronization ?? I18nLanguageSynchronization
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
...native,
|
|
17
|
+
setup (api) {
|
|
18
|
+
registerFederatedI18nBoundary(api);
|
|
19
|
+
return native.setup?.(api);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export { composeI18nPlugin };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { getLocaleDetectionOptions } from "@modern-js/plugin-i18n/config";
|
|
3
|
+
import { i18nServerPlugin } from "@modern-js/plugin-i18n/server";
|
|
4
|
+
import { createI18nUrlStrategy } from "./urlStrategy.mjs";
|
|
5
|
+
const server_i18nServerPlugin = (options)=>i18nServerPlugin({
|
|
6
|
+
...options,
|
|
7
|
+
resolveUrlStrategy: options.resolveUrlStrategy ?? ((entryName)=>createI18nUrlStrategy(options.localeDetection ? getLocaleDetectionOptions(entryName, options.localeDetection).localisedUrls : void 0))
|
|
8
|
+
});
|
|
9
|
+
const server = server_i18nServerPlugin;
|
|
10
|
+
export default server;
|
|
11
|
+
export { server_i18nServerPlugin as i18nServerPlugin };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { canonicalTargetPathname, isDefaultLocaleRedirectSkipPath, localiseTargetPathname } from "@modern-js/i18n-runtime-extensions";
|
|
3
|
+
const createI18nUrlStrategy = (localisedUrls)=>({
|
|
4
|
+
localizePathname: (pathname, language, languages)=>localiseTargetPathname(pathname, language, [
|
|
5
|
+
...languages
|
|
6
|
+
], localisedUrls),
|
|
7
|
+
canonicalPathname: (pathname, languages)=>canonicalTargetPathname(pathname, [
|
|
8
|
+
...languages
|
|
9
|
+
], localisedUrls),
|
|
10
|
+
shouldSkipRedirect: (pathname, languages)=>isDefaultLocaleRedirectSkipPath(pathname, [
|
|
11
|
+
...languages
|
|
12
|
+
])
|
|
13
|
+
});
|
|
14
|
+
export { createI18nUrlStrategy };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AppTools, CliPlugin } from '@modern-js/app-tools';
|
|
2
|
+
/** Compose fork policy with the native plugin's own descriptor generation. */
|
|
3
|
+
export declare const ultramodernI18nIntegrationPlugin: () => CliPlugin<AppTools>;
|
|
4
|
+
export default ultramodernI18nIntegrationPlugin;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type FederatedI18nBoundaryProps } from '@modern-js/plugin-i18n/runtime/consumer';
|
|
2
|
+
import { type FC } from 'react';
|
|
3
|
+
export type { FederatedI18nBoundaryProps } from '@modern-js/plugin-i18n/runtime/consumer';
|
|
4
|
+
/**
|
|
5
|
+
* Keeps a federated surface's translation resources inside its delivery unit.
|
|
6
|
+
* The host supplies only the active language; the remote owns and versions the
|
|
7
|
+
* resources used below this boundary.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { I18nLanguageSynchronizationProps } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
|
|
2
|
+
/** Bind the shared controller to this native provider's instance and state. */
|
|
3
|
+
export declare const I18nLanguageSynchronization: ({ i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang, children, }: I18nLanguageSynchronizationProps) => import("react").ReactNode;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
|
|
2
|
+
import type { I18nPluginOptions as NativeRuntimeOptions } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
|
|
3
|
+
import type { I18nPluginOptions as NativeServerOptions } from '@modern-js/plugin-i18n/server';
|
|
4
|
+
export type IntegratedLocaleDetectionOptions = NonNullable<NativeRuntimeOptions['localeDetection']> & {
|
|
5
|
+
localisedUrls?: LocalisedUrlsOption;
|
|
6
|
+
localeDetectionByEntry?: Record<string, IntegratedLocaleDetectionOptions>;
|
|
7
|
+
};
|
|
8
|
+
export type I18nPluginOptions = NativeRuntimeOptions & {
|
|
9
|
+
localeDetection?: IntegratedLocaleDetectionOptions;
|
|
10
|
+
};
|
|
11
|
+
export type I18nServerPluginOptions = NativeServerOptions & {
|
|
12
|
+
localeDetection: IntegratedLocaleDetectionOptions;
|
|
13
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { I18nPluginOptions as NativeI18nPluginOptions } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
|
|
2
|
+
import type { RuntimePlugin } from '@modern-js/runtime';
|
|
3
|
+
import type { I18nPluginOptions } from './options.js';
|
|
4
|
+
export declare const composeI18nPlugin: (nativeI18nPlugin: (options: NativeI18nPluginOptions) => RuntimePlugin) => (options?: I18nPluginOptions) => RuntimePlugin;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
|
|
2
|
+
import type { I18nUrlStrategy } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
|
|
3
|
+
/** Each integration owns its map; no process-wide configuration is installed. */
|
|
4
|
+
export declare const createI18nUrlStrategy: (localisedUrls?: LocalisedUrlsOption) => I18nUrlStrategy;
|
package/package.json
CHANGED
|
@@ -1,16 +1,150 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bleedingdev/modern-js-i18n-integration",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "UltraModern.js localized routing and federated resource integration through native Modern.js i18n plugins.",
|
|
4
|
+
"homepage": "https://github.com/BleedingDev/ultramodern.js#readme",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/BleedingDev/ultramodern.js/issues"
|
|
7
|
+
},
|
|
6
8
|
"repository": {
|
|
7
9
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/BleedingDev/ultramodern.js.git"
|
|
10
|
+
"url": "git+https://github.com/BleedingDev/ultramodern.js.git",
|
|
11
|
+
"directory": "packages/runtime/i18n-integration"
|
|
9
12
|
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"version": "3.9.0-ultramodern.6",
|
|
10
15
|
"files": [
|
|
11
|
-
"
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
12
18
|
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=26.7.0"
|
|
21
|
+
},
|
|
22
|
+
"types": "./dist/types/cli.d.ts",
|
|
23
|
+
"main": "./dist/cjs/cli.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/types/cli.d.ts",
|
|
27
|
+
"node": {
|
|
28
|
+
"import": "./dist/esm-node/cli.mjs",
|
|
29
|
+
"require": "./dist/cjs/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"default": "./dist/cjs/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"./cli": {
|
|
34
|
+
"types": "./dist/types/cli.d.ts",
|
|
35
|
+
"node": {
|
|
36
|
+
"import": "./dist/esm-node/cli.mjs",
|
|
37
|
+
"require": "./dist/cjs/cli.js"
|
|
38
|
+
},
|
|
39
|
+
"default": "./dist/cjs/cli.js"
|
|
40
|
+
},
|
|
41
|
+
"./runtime": {
|
|
42
|
+
"types": "./dist/types/runtime.d.ts",
|
|
43
|
+
"node": {
|
|
44
|
+
"import": "./dist/esm-node/runtime.mjs",
|
|
45
|
+
"require": "./dist/cjs/runtime.js"
|
|
46
|
+
},
|
|
47
|
+
"default": "./dist/esm/runtime.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./runtime/no-react-i18next": {
|
|
50
|
+
"types": "./dist/types/runtime-no-react-i18next.d.ts",
|
|
51
|
+
"node": {
|
|
52
|
+
"import": "./dist/esm-node/runtime-no-react-i18next.mjs",
|
|
53
|
+
"require": "./dist/cjs/runtime-no-react-i18next.js"
|
|
54
|
+
},
|
|
55
|
+
"default": "./dist/esm/runtime-no-react-i18next.mjs"
|
|
56
|
+
},
|
|
57
|
+
"./server": {
|
|
58
|
+
"types": "./dist/types/server.d.ts",
|
|
59
|
+
"node": {
|
|
60
|
+
"import": "./dist/esm-node/server.mjs",
|
|
61
|
+
"require": "./dist/cjs/server.js"
|
|
62
|
+
},
|
|
63
|
+
"default": "./dist/cjs/server.js"
|
|
64
|
+
},
|
|
65
|
+
"./federation": {
|
|
66
|
+
"types": "./dist/types/federation/boundary.d.ts",
|
|
67
|
+
"node": {
|
|
68
|
+
"import": "./dist/esm-node/federation/boundary.mjs",
|
|
69
|
+
"require": "./dist/cjs/federation/boundary.js"
|
|
70
|
+
},
|
|
71
|
+
"default": "./dist/esm/federation/boundary.mjs"
|
|
72
|
+
},
|
|
73
|
+
"./package.json": "./package.json",
|
|
74
|
+
"./options": {
|
|
75
|
+
"types": "./dist/types/options.d.ts",
|
|
76
|
+
"node": {
|
|
77
|
+
"import": "./dist/esm-node/options.mjs",
|
|
78
|
+
"require": "./dist/cjs/options.js"
|
|
79
|
+
},
|
|
80
|
+
"default": "./dist/esm/options.mjs"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"typesVersions": {
|
|
84
|
+
"*": {
|
|
85
|
+
".": [
|
|
86
|
+
"./dist/types/cli.d.ts"
|
|
87
|
+
],
|
|
88
|
+
"cli": [
|
|
89
|
+
"./dist/types/cli.d.ts"
|
|
90
|
+
],
|
|
91
|
+
"runtime": [
|
|
92
|
+
"./dist/types/runtime.d.ts"
|
|
93
|
+
],
|
|
94
|
+
"runtime/no-react-i18next": [
|
|
95
|
+
"./dist/types/runtime-no-react-i18next.d.ts"
|
|
96
|
+
],
|
|
97
|
+
"server": [
|
|
98
|
+
"./dist/types/server.d.ts"
|
|
99
|
+
],
|
|
100
|
+
"federation": [
|
|
101
|
+
"./dist/types/federation/boundary.d.ts"
|
|
102
|
+
],
|
|
103
|
+
"options": [
|
|
104
|
+
"./dist/types/options.d.ts"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"dependencies": {
|
|
109
|
+
"@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.6",
|
|
110
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.6",
|
|
111
|
+
"@modern-js/plugin-i18n": "npm:@bleedingdev/modern-js-plugin-i18n@3.9.0-ultramodern.6",
|
|
112
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.6",
|
|
113
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.9.0-ultramodern.6",
|
|
114
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.6"
|
|
115
|
+
},
|
|
116
|
+
"peerDependencies": {
|
|
117
|
+
"@modern-js/app-tools": "3.9.0-ultramodern.6",
|
|
118
|
+
"@modern-js/runtime": "3.9.0-ultramodern.6",
|
|
119
|
+
"react": "^19.2.8"
|
|
120
|
+
},
|
|
121
|
+
"peerDependenciesMeta": {},
|
|
122
|
+
"optionalDependencies": {},
|
|
123
|
+
"devDependencies": {
|
|
124
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.6",
|
|
125
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.6",
|
|
126
|
+
"@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.6",
|
|
127
|
+
"@rslib/core": "1.0.0",
|
|
128
|
+
"@rstest/core": "0.11.12",
|
|
129
|
+
"@scripts/rstest-config": "2.66.0",
|
|
130
|
+
"@types/node": "^26.4.1",
|
|
131
|
+
"@types/react": "^19.2.18",
|
|
132
|
+
"@types/react-dom": "^19.2.7",
|
|
133
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
134
|
+
"esbuild": "^0.28.2",
|
|
135
|
+
"i18next": "26.4.2",
|
|
136
|
+
"react": "^19.2.8",
|
|
137
|
+
"react-dom": "^19.2.8",
|
|
138
|
+
"react-i18next": "17.0.13",
|
|
139
|
+
"typescript": "^7.0.2"
|
|
140
|
+
},
|
|
141
|
+
"sideEffects": false,
|
|
13
142
|
"publishConfig": {
|
|
14
143
|
"access": "public"
|
|
144
|
+
},
|
|
145
|
+
"scripts": {
|
|
146
|
+
"dev": "rslib build --watch",
|
|
147
|
+
"build": "rslib build",
|
|
148
|
+
"test": "rstest"
|
|
15
149
|
}
|
|
16
150
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AppTools, CliPlugin } from '@modern-js/app-tools';
|
|
2
|
+
import {
|
|
3
|
+
applyLocalisedUrlsToRoutes,
|
|
4
|
+
resolveLocalisedUrlsConfig,
|
|
5
|
+
} from '@modern-js/i18n-runtime-extensions';
|
|
6
|
+
import type { NestedRouteForCli, PageRoute } from '@modern-js/types';
|
|
7
|
+
import type { I18nPluginOptions } from './options';
|
|
8
|
+
|
|
9
|
+
const runtimePaths = new Map([
|
|
10
|
+
['@modern-js/plugin-i18n/runtime', '@modern-js/i18n-integration/runtime'],
|
|
11
|
+
[
|
|
12
|
+
'@modern-js/plugin-i18n/runtime/no-react-i18next',
|
|
13
|
+
'@modern-js/i18n-integration/runtime/no-react-i18next',
|
|
14
|
+
],
|
|
15
|
+
]);
|
|
16
|
+
const integrationRuntimePaths = new Set(runtimePaths.values());
|
|
17
|
+
|
|
18
|
+
/** Compose fork policy with the native plugin's own descriptor generation. */
|
|
19
|
+
export const ultramodernI18nIntegrationPlugin = (): CliPlugin<AppTools> => ({
|
|
20
|
+
name: '@modern-js/i18n-integration',
|
|
21
|
+
pre: ['@modern-js/plugin-i18n'],
|
|
22
|
+
setup(api) {
|
|
23
|
+
api._internalRuntimePlugins(({ entrypoint, plugins }) => ({
|
|
24
|
+
entrypoint,
|
|
25
|
+
plugins: plugins.map(plugin => {
|
|
26
|
+
const path = runtimePaths.get(plugin.path);
|
|
27
|
+
return path ? { ...plugin, path } : plugin;
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
api._internalServerPlugins(({ plugins }) => ({
|
|
32
|
+
plugins: plugins.map(plugin =>
|
|
33
|
+
plugin.name === '@modern-js/plugin-i18n/server'
|
|
34
|
+
? { ...plugin, name: '@modern-js/i18n-integration/server' }
|
|
35
|
+
: plugin,
|
|
36
|
+
),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
api.modifyFileSystemRoutes(async ({ entrypoint, routes }) => {
|
|
40
|
+
// Route generation may precede runtime module generation. Resolve the
|
|
41
|
+
// native descriptor with a fresh local output list; this hook never calls
|
|
42
|
+
// route generation and cannot append to a later emitted descriptor list.
|
|
43
|
+
const { plugins } = await api.getHooks()._internalRuntimePlugins.call({
|
|
44
|
+
entrypoint,
|
|
45
|
+
plugins: [],
|
|
46
|
+
});
|
|
47
|
+
const descriptor = plugins.find(plugin =>
|
|
48
|
+
integrationRuntimePaths.has(plugin.path),
|
|
49
|
+
);
|
|
50
|
+
const config = descriptor?.config as I18nPluginOptions | undefined;
|
|
51
|
+
const {
|
|
52
|
+
localePathRedirect,
|
|
53
|
+
languages = [],
|
|
54
|
+
localisedUrls,
|
|
55
|
+
} = config?.localeDetection ?? {};
|
|
56
|
+
const resolved = resolveLocalisedUrlsConfig(localisedUrls);
|
|
57
|
+
if (!localePathRedirect || !languages.length || !resolved.enabled) {
|
|
58
|
+
return { entrypoint, routes };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
entrypoint,
|
|
62
|
+
routes: applyLocalisedUrlsToRoutes(
|
|
63
|
+
routes as (NestedRouteForCli | PageRoute)[],
|
|
64
|
+
languages,
|
|
65
|
+
resolved.map,
|
|
66
|
+
) as (NestedRouteForCli | PageRoute)[],
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export default ultramodernI18nIntegrationPlugin;
|