@bleedingdev/modern-js-i18n-integration 0.0.0 → 3.9.0-ultramodern.5

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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/cli.js +97 -0
  3. package/dist/cjs/federation/boundary.js +100 -0
  4. package/dist/cjs/federation/registration.js +43 -0
  5. package/dist/cjs/languageSynchronization.js +44 -0
  6. package/dist/cjs/navigation.js +220 -0
  7. package/dist/cjs/options.js +18 -0
  8. package/dist/cjs/runtime-no-react-i18next.js +46 -0
  9. package/dist/cjs/runtime.js +46 -0
  10. package/dist/cjs/runtimeComposition.js +60 -0
  11. package/dist/cjs/server.js +50 -0
  12. package/dist/cjs/urlStrategy.js +51 -0
  13. package/dist/esm/cli.mjs +57 -0
  14. package/dist/esm/federation/boundary.mjs +62 -0
  15. package/dist/esm/federation/registration.mjs +5 -0
  16. package/dist/esm/languageSynchronization.mjs +6 -0
  17. package/dist/esm/navigation.mjs +182 -0
  18. package/dist/esm/options.mjs +0 -0
  19. package/dist/esm/runtime-no-react-i18next.mjs +6 -0
  20. package/dist/esm/runtime.mjs +6 -0
  21. package/dist/esm/runtimeComposition.mjs +22 -0
  22. package/dist/esm/server.mjs +10 -0
  23. package/dist/esm/urlStrategy.mjs +13 -0
  24. package/dist/esm-node/cli.mjs +58 -0
  25. package/dist/esm-node/federation/boundary.mjs +63 -0
  26. package/dist/esm-node/federation/registration.mjs +6 -0
  27. package/dist/esm-node/languageSynchronization.mjs +7 -0
  28. package/dist/esm-node/navigation.mjs +183 -0
  29. package/dist/esm-node/options.mjs +1 -0
  30. package/dist/esm-node/runtime-no-react-i18next.mjs +7 -0
  31. package/dist/esm-node/runtime.mjs +7 -0
  32. package/dist/esm-node/runtimeComposition.mjs +23 -0
  33. package/dist/esm-node/server.mjs +11 -0
  34. package/dist/esm-node/urlStrategy.mjs +14 -0
  35. package/dist/types/cli.d.ts +4 -0
  36. package/dist/types/federation/boundary.d.ts +9 -0
  37. package/dist/types/federation/registration.d.ts +2 -0
  38. package/dist/types/languageSynchronization.d.ts +3 -0
  39. package/dist/types/navigation.d.ts +3 -0
  40. package/dist/types/options.d.ts +13 -0
  41. package/dist/types/runtime-no-react-i18next.d.ts +2 -0
  42. package/dist/types/runtime.d.ts +2 -0
  43. package/dist/types/runtimeComposition.d.ts +4 -0
  44. package/dist/types/server.d.ts +3 -0
  45. package/dist/types/urlStrategy.d.ts +4 -0
  46. package/package.json +139 -5
  47. package/src/cli.ts +72 -0
  48. package/src/federation/boundary.tsx +129 -0
  49. package/src/federation/registration.ts +8 -0
  50. package/src/languageSynchronization.tsx +23 -0
  51. package/src/navigation.tsx +399 -0
  52. package/src/options.ts +18 -0
  53. package/src/runtime-no-react-i18next.ts +5 -0
  54. package/src/runtime.ts +5 -0
  55. package/src/runtimeComposition.ts +33 -0
  56. package/src/server.ts +27 -0
  57. package/src/urlStrategy.ts +19 -0
  58. 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,2 @@
1
+ import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
2
+ export declare function registerFederatedI18nBoundary(api: RuntimePluginAPI<{}>): void;
@@ -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,3 @@
1
+ import type React from 'react';
2
+ /** Supplies the actual selected router; native links still render its Link. */
3
+ export declare const I18nRouterNavigationProvider: ({ children, }: React.PropsWithChildren) => React.JSX.Element;
@@ -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,2 @@
1
+ export declare const i18nPlugin: (options?: import("./options.js").I18nPluginOptions) => import("@modern-js/runtime").RuntimePlugin;
2
+ export default i18nPlugin;
@@ -0,0 +1,2 @@
1
+ export declare const i18nPlugin: (options?: import("./options.js").I18nPluginOptions) => import("@modern-js/runtime").RuntimePlugin;
2
+ export default i18nPlugin;
@@ -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,3 @@
1
+ import type { I18nServerPluginOptions } from './options.js';
2
+ export declare const i18nServerPlugin: (options: I18nServerPluginOptions) => import("@modern-js/server-core").ServerPlugin;
3
+ export default i18nServerPlugin;
@@ -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
- "version": "0.0.0",
4
- "description": "Package-name registration for the forthcoming UltraModern.js release. This setup version contains no runtime implementation.",
5
- "license": "MIT",
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.5",
10
15
  "files": [
11
- "README.md"
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.5",
110
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.5",
111
+ "@modern-js/plugin-i18n": "npm:@bleedingdev/modern-js-plugin-i18n@3.9.0-ultramodern.5",
112
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.5",
113
+ "@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.9.0-ultramodern.5",
114
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.5"
115
+ },
116
+ "peerDependencies": {
117
+ "@modern-js/app-tools": "3.9.0-ultramodern.5",
118
+ "@modern-js/runtime": "3.9.0-ultramodern.5",
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.5",
125
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.5",
126
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.5",
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;