@bleedingdev/modern-js-plugin-i18n 3.4.0-ultramodern.0 → 3.4.0-ultramodern.10

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 (35) hide show
  1. package/dist/cjs/cli/index.js +2 -1
  2. package/dist/cjs/runtime/context.js +8 -0
  3. package/dist/cjs/runtime/core.js +217 -0
  4. package/dist/cjs/runtime/i18n/backend/middleware.node.js +22 -4
  5. package/dist/cjs/runtime/index.js +44 -173
  6. package/dist/cjs/runtime/no-react-i18next.js +76 -0
  7. package/dist/esm/cli/index.mjs +2 -1
  8. package/dist/esm/runtime/context.mjs +8 -0
  9. package/dist/esm/runtime/core.mjs +152 -0
  10. package/dist/esm/runtime/i18n/backend/middleware.node.mjs +19 -4
  11. package/dist/esm/runtime/index.mjs +5 -140
  12. package/dist/esm/runtime/no-react-i18next.mjs +6 -0
  13. package/dist/esm-node/cli/index.mjs +2 -1
  14. package/dist/esm-node/runtime/context.mjs +8 -0
  15. package/dist/esm-node/runtime/core.mjs +153 -0
  16. package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +19 -4
  17. package/dist/esm-node/runtime/index.mjs +5 -140
  18. package/dist/esm-node/runtime/no-react-i18next.mjs +7 -0
  19. package/dist/types/runtime/context.d.ts +1 -0
  20. package/dist/types/runtime/core.d.ts +30 -0
  21. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +4 -1
  22. package/dist/types/runtime/index.d.ts +2 -24
  23. package/dist/types/runtime/no-react-i18next.d.ts +3 -0
  24. package/package.json +21 -11
  25. package/rstest.config.mts +1 -0
  26. package/src/cli/index.ts +6 -1
  27. package/src/runtime/context.tsx +13 -0
  28. package/src/runtime/core.tsx +360 -0
  29. package/src/runtime/i18n/backend/middleware.node.ts +31 -1
  30. package/src/runtime/index.tsx +4 -316
  31. package/src/runtime/no-react-i18next.tsx +7 -0
  32. package/tests/i18nUtils.test.ts +34 -0
  33. package/tests/localisedUrls.test.ts +35 -0
  34. package/tests/reactI18nextRuntimeBoundary.test.ts +36 -0
  35. package/tests/routerAdapter.test.tsx +58 -1
@@ -0,0 +1,152 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { RuntimeContext, isBrowser } from "@modern-js/runtime";
3
+ import { Helmet } from "@modern-js/runtime/head";
4
+ import { merge } from "@modern-js/runtime-utils/merge";
5
+ import { useContext, useEffect, useMemo, useRef, useState } from "react";
6
+ import { ModernI18nProvider, useModernI18n } from "./context.mjs";
7
+ import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
8
+ import { getI18nInstance } from "./i18n/index.mjs";
9
+ import { mergeBackendOptions } from "./i18n/backend/index.mjs";
10
+ import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
11
+ import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
12
+ import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
13
+ import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
14
+ import "./types.mjs";
15
+ let i18nLifecycleHelpersPromise;
16
+ function loadI18nLifecycleHelpers() {
17
+ i18nLifecycleHelpersPromise ??= Promise.all([
18
+ import("./i18n/backend/middleware.mjs"),
19
+ import("./i18n/utils.mjs")
20
+ ]).then(([backendMiddleware, utils])=>({
21
+ useI18nextBackend: backendMiddleware.useI18nextBackend,
22
+ changeI18nLanguage: utils.changeI18nLanguage,
23
+ ensureLanguageMatch: utils.ensureLanguageMatch,
24
+ initializeI18nInstance: utils.initializeI18nInstance,
25
+ setupClonedInstance: utils.setupClonedInstance
26
+ }));
27
+ return i18nLifecycleHelpersPromise;
28
+ }
29
+ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
30
+ name: '@modern-js/plugin-i18n',
31
+ setup: (api)=>{
32
+ const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true } = options;
33
+ const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes, localisedUrls } = localeDetection || {};
34
+ const { enabled: backendEnabled = false } = backend || {};
35
+ let latestI18nInstance;
36
+ let I18nextProvider;
37
+ const resolveReactI18nextIntegration = async ()=>{
38
+ if (!reactI18next) return null;
39
+ return loadReactI18nextIntegration?.() ?? null;
40
+ };
41
+ api.onBeforeRender(async (context)=>{
42
+ const { useI18nextBackend, changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } = await loadI18nLifecycleHelpers();
43
+ let i18nInstance = await getI18nInstance(userI18nInstance);
44
+ const { i18n: otherConfig } = api.getRuntimeConfig();
45
+ const { initOptions: otherInitOptions } = otherConfig || {};
46
+ const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
47
+ const reactI18nextIntegration = await resolveReactI18nextIntegration();
48
+ I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
49
+ if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
50
+ const pathname = getPathname(context);
51
+ if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
52
+ const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
53
+ const mergedBackend = mergeBackendOptions(backend, userInitOptions);
54
+ const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
55
+ if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
56
+ const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
57
+ languages,
58
+ fallbackLanguage,
59
+ localePathRedirect,
60
+ i18nextDetector,
61
+ detection,
62
+ userInitOptions,
63
+ mergedBackend,
64
+ pathname,
65
+ ssrContext: context.ssrContext
66
+ });
67
+ await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
68
+ if (!isBrowser() && i18nInstance.cloneInstance) {
69
+ i18nInstance = i18nInstance.cloneInstance();
70
+ await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
71
+ }
72
+ if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
73
+ if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
74
+ context.i18nInstance = i18nInstance;
75
+ latestI18nInstance = i18nInstance;
76
+ context.changeLanguage = async (newLang)=>{
77
+ await changeI18nLanguage(i18nInstance, newLang, {
78
+ detectionOptions: mergedDetection
79
+ });
80
+ };
81
+ });
82
+ api.wrapRoot((App)=>(props)=>{
83
+ const runtimeContext = useContext(RuntimeContext);
84
+ const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
85
+ const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
86
+ i18nInstance?.language,
87
+ localeDetection?.fallbackLanguage
88
+ ]);
89
+ const [lang, setLang] = useState(initialLang);
90
+ const [forceUpdate, setForceUpdate] = useState(0);
91
+ const prevLangRef = useRef(lang);
92
+ const runtimeContextRef = useRef(runtimeContext);
93
+ runtimeContextRef.current = runtimeContext;
94
+ useEffect(()=>{
95
+ if (i18nInstance?.language) {
96
+ const translator = i18nInstance.translator;
97
+ if (translator) translator.language = i18nInstance.language;
98
+ }
99
+ }, [
100
+ i18nInstance?.language
101
+ ]);
102
+ useEffect(()=>{
103
+ prevLangRef.current = lang;
104
+ }, [
105
+ lang
106
+ ]);
107
+ useSdkResourcesLoader(i18nInstance, setForceUpdate);
108
+ useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
109
+ useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
110
+ const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
111
+ lang,
112
+ i18nInstance,
113
+ entryName,
114
+ languages,
115
+ localePathRedirect,
116
+ ignoreRedirectRoutes,
117
+ localisedUrls,
118
+ forceUpdate
119
+ ]);
120
+ const children = props.children;
121
+ const appContent = /*#__PURE__*/ jsxs(Fragment, {
122
+ children: [
123
+ Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
124
+ htmlAttributes: {
125
+ lang
126
+ }
127
+ }),
128
+ /*#__PURE__*/ jsx(ModernI18nProvider, {
129
+ value: contextValue,
130
+ children: App ? /*#__PURE__*/ jsx(App, {
131
+ ...props,
132
+ children: children
133
+ }) : children
134
+ })
135
+ ]
136
+ });
137
+ if (!i18nInstance) return appContent;
138
+ if (I18nextProvider) {
139
+ const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
140
+ return /*#__PURE__*/ jsx(I18nextProvider, {
141
+ i18n: i18nextInstanceForProvider,
142
+ children: appContent
143
+ });
144
+ }
145
+ return appContent;
146
+ });
147
+ }
148
+ });
149
+ export { I18nLink } from "./I18nLink.mjs";
150
+ export { Link } from "./Link.mjs";
151
+ export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
152
+ export { buildLocalizedUrl, createI18nPlugin, splitUrlTarget, useModernI18n };
@@ -1,8 +1,23 @@
1
- import cjs from "i18next-fs-backend/cjs";
1
+ import i18next_fs_backend from "i18next-fs-backend";
2
2
  import { useI18nextBackendCommon } from "./middleware.common.mjs";
3
- class FsBackendWithSave extends cjs {
3
+ const resolveFsBackendConstructor = (backendModule)=>{
4
+ const nestedDefault = backendModule?.default?.default;
5
+ const nestedModuleExports = backendModule?.default?.['module.exports'];
6
+ const candidates = [
7
+ backendModule,
8
+ backendModule?.default,
9
+ backendModule?.['module.exports'],
10
+ nestedDefault,
11
+ nestedModuleExports
12
+ ];
13
+ const Backend = candidates.find((candidate)=>'function' == typeof candidate);
14
+ if (!Backend) throw new Error('Failed to resolve i18next-fs-backend constructor for the i18n Node backend.');
15
+ return Backend;
16
+ };
17
+ const middleware_node_Backend = resolveFsBackendConstructor(i18next_fs_backend);
18
+ class FsBackendWithSave extends middleware_node_Backend {
4
19
  save(_language, _namespace, _data) {}
5
20
  }
6
21
  const HttpBackendWithSave = FsBackendWithSave;
7
- const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, cjs, backend);
8
- export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
22
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, middleware_node_Backend, backend);
23
+ export { FsBackendWithSave, HttpBackendWithSave, resolveFsBackendConstructor, useI18nextBackend };
@@ -1,142 +1,7 @@
1
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { RuntimeContext, isBrowser } from "@modern-js/runtime";
3
- import { Helmet } from "@modern-js/runtime/head";
4
- import { merge } from "@modern-js/runtime-utils/merge";
5
- import { useContext, useEffect, useMemo, useRef, useState } from "react";
6
- import { ModernI18nProvider, useModernI18n } from "./context.mjs";
7
- import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
8
- import { getI18nInstance } from "./i18n/index.mjs";
9
- import { mergeBackendOptions } from "./i18n/backend/index.mjs";
10
- import { useI18nextBackend } from "./i18n/backend/middleware.mjs";
11
- import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
12
- import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
13
- import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
14
- import { changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } from "./i18n/utils.mjs";
15
- import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
16
- import "./types.mjs";
17
- const i18nPlugin = (options)=>({
18
- name: '@modern-js/plugin-i18n',
19
- setup: (api)=>{
20
- const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true } = options;
21
- const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes, localisedUrls } = localeDetection || {};
22
- const { enabled: backendEnabled = false } = backend || {};
23
- let latestI18nInstance;
24
- let I18nextProvider;
25
- const loadReactI18nextIntegration = async ()=>{
26
- if (!reactI18next) return null;
27
- const { getReactI18nextIntegration } = await import("./i18n/react-i18next.mjs");
28
- return getReactI18nextIntegration();
29
- };
30
- api.onBeforeRender(async (context)=>{
31
- let i18nInstance = await getI18nInstance(userI18nInstance);
32
- const { i18n: otherConfig } = api.getRuntimeConfig();
33
- const { initOptions: otherInitOptions } = otherConfig || {};
34
- const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
35
- const reactI18nextIntegration = await loadReactI18nextIntegration();
36
- I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
37
- if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
38
- const pathname = getPathname(context);
39
- if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
40
- const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
41
- const mergedBackend = mergeBackendOptions(backend, userInitOptions);
42
- const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
43
- if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
44
- const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
45
- languages,
46
- fallbackLanguage,
47
- localePathRedirect,
48
- i18nextDetector,
49
- detection,
50
- userInitOptions,
51
- mergedBackend,
52
- pathname,
53
- ssrContext: context.ssrContext
54
- });
55
- await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
56
- if (!isBrowser() && i18nInstance.cloneInstance) {
57
- i18nInstance = i18nInstance.cloneInstance();
58
- await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
59
- }
60
- if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
61
- if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
62
- context.i18nInstance = i18nInstance;
63
- latestI18nInstance = i18nInstance;
64
- context.changeLanguage = async (newLang)=>{
65
- await changeI18nLanguage(i18nInstance, newLang, {
66
- detectionOptions: mergedDetection
67
- });
68
- };
69
- });
70
- api.wrapRoot((App)=>(props)=>{
71
- const runtimeContext = useContext(RuntimeContext);
72
- const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
73
- const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
74
- i18nInstance?.language,
75
- localeDetection?.fallbackLanguage
76
- ]);
77
- const [lang, setLang] = useState(initialLang);
78
- const [forceUpdate, setForceUpdate] = useState(0);
79
- const prevLangRef = useRef(lang);
80
- const runtimeContextRef = useRef(runtimeContext);
81
- runtimeContextRef.current = runtimeContext;
82
- useEffect(()=>{
83
- if (i18nInstance?.language) {
84
- const translator = i18nInstance.translator;
85
- if (translator) translator.language = i18nInstance.language;
86
- }
87
- }, [
88
- i18nInstance?.language
89
- ]);
90
- useEffect(()=>{
91
- prevLangRef.current = lang;
92
- }, [
93
- lang
94
- ]);
95
- useSdkResourcesLoader(i18nInstance, setForceUpdate);
96
- useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
97
- useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
98
- const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
99
- lang,
100
- i18nInstance,
101
- entryName,
102
- languages,
103
- localePathRedirect,
104
- ignoreRedirectRoutes,
105
- localisedUrls,
106
- forceUpdate
107
- ]);
108
- const children = props.children;
109
- const appContent = /*#__PURE__*/ jsxs(Fragment, {
110
- children: [
111
- Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
112
- htmlAttributes: {
113
- lang
114
- }
115
- }),
116
- /*#__PURE__*/ jsx(ModernI18nProvider, {
117
- value: contextValue,
118
- children: App ? /*#__PURE__*/ jsx(App, {
119
- ...props,
120
- children: children
121
- }) : children
122
- })
123
- ]
124
- });
125
- if (!i18nInstance) return appContent;
126
- if (I18nextProvider) {
127
- const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
128
- return /*#__PURE__*/ jsx(I18nextProvider, {
129
- i18n: i18nextInstanceForProvider,
130
- children: appContent
131
- });
132
- }
133
- return appContent;
134
- });
135
- }
136
- });
1
+ import { createI18nPlugin } from "./core.mjs";
2
+ import { getReactI18nextIntegration } from "./i18n/react-i18next.mjs";
3
+ export * from "./core.mjs";
4
+ const i18nPlugin = createI18nPlugin(getReactI18nextIntegration);
137
5
  const runtime = i18nPlugin;
138
- export { I18nLink } from "./I18nLink.mjs";
139
- export { Link } from "./Link.mjs";
140
- export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
141
6
  export default runtime;
142
- export { buildLocalizedUrl, i18nPlugin, splitUrlTarget, useModernI18n };
7
+ export { i18nPlugin };
@@ -0,0 +1,6 @@
1
+ import { createI18nPlugin } from "./core.mjs";
2
+ export * from "./core.mjs";
3
+ const i18nPlugin = createI18nPlugin();
4
+ const no_react_i18next = i18nPlugin;
5
+ export default no_react_i18next;
6
+ export { i18nPlugin };
@@ -32,9 +32,10 @@ const i18nPlugin = (options = {})=>({
32
32
  backend: backendOptions,
33
33
  ...extendedConfig
34
34
  };
35
+ const runtimePluginPath = customPlugin?.runtime?.path || (false === config.reactI18next ? `@${metaName}/plugin-i18n/runtime/no-react-i18next` : `@${metaName}/plugin-i18n/runtime`);
35
36
  plugins.push({
36
37
  name: customPlugin?.runtime?.name || 'i18n',
37
- path: customPlugin?.runtime?.path || `@${metaName}/plugin-i18n/runtime`,
38
+ path: runtimePluginPath,
38
39
  config
39
40
  });
40
41
  return {
@@ -90,6 +90,13 @@ const useModernI18n = ()=>{
90
90
  navigate,
91
91
  location
92
92
  ]);
93
+ const t = useCallback((key, ...args)=>{
94
+ if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t is required');
95
+ return i18nInstance.t(key, ...args);
96
+ }, [
97
+ currentLanguage,
98
+ i18nInstance
99
+ ]);
93
100
  const isLanguageSupported = useCallback((lang)=>languages?.includes(lang) || false, [
94
101
  languages
95
102
  ]);
@@ -123,6 +130,7 @@ const useModernI18n = ()=>{
123
130
  return {
124
131
  language: currentLanguage,
125
132
  changeLanguage,
133
+ t,
126
134
  i18nInstance,
127
135
  supportedLanguages: languages || [],
128
136
  localisedUrls,
@@ -0,0 +1,153 @@
1
+ import "node:module";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { RuntimeContext, isBrowser } from "@modern-js/runtime";
4
+ import { Helmet } from "@modern-js/runtime/head";
5
+ import { merge } from "@modern-js/runtime-utils/merge";
6
+ import { useContext, useEffect, useMemo, useRef, useState } from "react";
7
+ import { ModernI18nProvider, useModernI18n } from "./context.mjs";
8
+ import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
9
+ import { getI18nInstance } from "./i18n/index.mjs";
10
+ import { mergeBackendOptions } from "./i18n/backend/index.mjs";
11
+ import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
12
+ import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
13
+ import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
14
+ import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
15
+ import "./types.mjs";
16
+ let i18nLifecycleHelpersPromise;
17
+ function loadI18nLifecycleHelpers() {
18
+ i18nLifecycleHelpersPromise ??= Promise.all([
19
+ import("./i18n/backend/middleware.mjs"),
20
+ import("./i18n/utils.mjs")
21
+ ]).then(([backendMiddleware, utils])=>({
22
+ useI18nextBackend: backendMiddleware.useI18nextBackend,
23
+ changeI18nLanguage: utils.changeI18nLanguage,
24
+ ensureLanguageMatch: utils.ensureLanguageMatch,
25
+ initializeI18nInstance: utils.initializeI18nInstance,
26
+ setupClonedInstance: utils.setupClonedInstance
27
+ }));
28
+ return i18nLifecycleHelpersPromise;
29
+ }
30
+ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
31
+ name: '@modern-js/plugin-i18n',
32
+ setup: (api)=>{
33
+ const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true } = options;
34
+ const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes, localisedUrls } = localeDetection || {};
35
+ const { enabled: backendEnabled = false } = backend || {};
36
+ let latestI18nInstance;
37
+ let I18nextProvider;
38
+ const resolveReactI18nextIntegration = async ()=>{
39
+ if (!reactI18next) return null;
40
+ return loadReactI18nextIntegration?.() ?? null;
41
+ };
42
+ api.onBeforeRender(async (context)=>{
43
+ const { useI18nextBackend, changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } = await loadI18nLifecycleHelpers();
44
+ let i18nInstance = await getI18nInstance(userI18nInstance);
45
+ const { i18n: otherConfig } = api.getRuntimeConfig();
46
+ const { initOptions: otherInitOptions } = otherConfig || {};
47
+ const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
48
+ const reactI18nextIntegration = await resolveReactI18nextIntegration();
49
+ I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
50
+ if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
51
+ const pathname = getPathname(context);
52
+ if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
53
+ const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
54
+ const mergedBackend = mergeBackendOptions(backend, userInitOptions);
55
+ const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
56
+ if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
57
+ const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
58
+ languages,
59
+ fallbackLanguage,
60
+ localePathRedirect,
61
+ i18nextDetector,
62
+ detection,
63
+ userInitOptions,
64
+ mergedBackend,
65
+ pathname,
66
+ ssrContext: context.ssrContext
67
+ });
68
+ await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
69
+ if (!isBrowser() && i18nInstance.cloneInstance) {
70
+ i18nInstance = i18nInstance.cloneInstance();
71
+ await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
72
+ }
73
+ if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
74
+ if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
75
+ context.i18nInstance = i18nInstance;
76
+ latestI18nInstance = i18nInstance;
77
+ context.changeLanguage = async (newLang)=>{
78
+ await changeI18nLanguage(i18nInstance, newLang, {
79
+ detectionOptions: mergedDetection
80
+ });
81
+ };
82
+ });
83
+ api.wrapRoot((App)=>(props)=>{
84
+ const runtimeContext = useContext(RuntimeContext);
85
+ const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
86
+ const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
87
+ i18nInstance?.language,
88
+ localeDetection?.fallbackLanguage
89
+ ]);
90
+ const [lang, setLang] = useState(initialLang);
91
+ const [forceUpdate, setForceUpdate] = useState(0);
92
+ const prevLangRef = useRef(lang);
93
+ const runtimeContextRef = useRef(runtimeContext);
94
+ runtimeContextRef.current = runtimeContext;
95
+ useEffect(()=>{
96
+ if (i18nInstance?.language) {
97
+ const translator = i18nInstance.translator;
98
+ if (translator) translator.language = i18nInstance.language;
99
+ }
100
+ }, [
101
+ i18nInstance?.language
102
+ ]);
103
+ useEffect(()=>{
104
+ prevLangRef.current = lang;
105
+ }, [
106
+ lang
107
+ ]);
108
+ useSdkResourcesLoader(i18nInstance, setForceUpdate);
109
+ useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
110
+ useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
111
+ const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
112
+ lang,
113
+ i18nInstance,
114
+ entryName,
115
+ languages,
116
+ localePathRedirect,
117
+ ignoreRedirectRoutes,
118
+ localisedUrls,
119
+ forceUpdate
120
+ ]);
121
+ const children = props.children;
122
+ const appContent = /*#__PURE__*/ jsxs(Fragment, {
123
+ children: [
124
+ Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
125
+ htmlAttributes: {
126
+ lang
127
+ }
128
+ }),
129
+ /*#__PURE__*/ jsx(ModernI18nProvider, {
130
+ value: contextValue,
131
+ children: App ? /*#__PURE__*/ jsx(App, {
132
+ ...props,
133
+ children: children
134
+ }) : children
135
+ })
136
+ ]
137
+ });
138
+ if (!i18nInstance) return appContent;
139
+ if (I18nextProvider) {
140
+ const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
141
+ return /*#__PURE__*/ jsx(I18nextProvider, {
142
+ i18n: i18nextInstanceForProvider,
143
+ children: appContent
144
+ });
145
+ }
146
+ return appContent;
147
+ });
148
+ }
149
+ });
150
+ export { I18nLink } from "./I18nLink.mjs";
151
+ export { Link } from "./Link.mjs";
152
+ export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
153
+ export { buildLocalizedUrl, createI18nPlugin, splitUrlTarget, useModernI18n };
@@ -1,9 +1,24 @@
1
1
  import "node:module";
2
- import cjs from "i18next-fs-backend/cjs";
2
+ import i18next_fs_backend from "i18next-fs-backend";
3
3
  import { useI18nextBackendCommon } from "./middleware.common.mjs";
4
- class FsBackendWithSave extends cjs {
4
+ const resolveFsBackendConstructor = (backendModule)=>{
5
+ const nestedDefault = backendModule?.default?.default;
6
+ const nestedModuleExports = backendModule?.default?.['module.exports'];
7
+ const candidates = [
8
+ backendModule,
9
+ backendModule?.default,
10
+ backendModule?.['module.exports'],
11
+ nestedDefault,
12
+ nestedModuleExports
13
+ ];
14
+ const Backend = candidates.find((candidate)=>'function' == typeof candidate);
15
+ if (!Backend) throw new Error('Failed to resolve i18next-fs-backend constructor for the i18n Node backend.');
16
+ return Backend;
17
+ };
18
+ const middleware_node_Backend = resolveFsBackendConstructor(i18next_fs_backend);
19
+ class FsBackendWithSave extends middleware_node_Backend {
5
20
  save(_language, _namespace, _data) {}
6
21
  }
7
22
  const HttpBackendWithSave = FsBackendWithSave;
8
- const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, cjs, backend);
9
- export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
23
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, middleware_node_Backend, backend);
24
+ export { FsBackendWithSave, HttpBackendWithSave, resolveFsBackendConstructor, useI18nextBackend };