44 isBrowser ,
55 useRuntimeContext ,
66} from '@modern-js/runtime' ;
7+ import { merge } from '@modern-js/runtime-utils/merge' ;
78import type React from 'react' ;
8- import { useEffect , useState } from 'react' ;
9+ import { useEffect , useMemo , useRef , useState } from 'react' ;
910import type {
1011 BaseBackendOptions ,
1112 BaseLocaleDetectionOptions ,
@@ -43,13 +44,17 @@ export interface I18nPluginOptions {
4344 [ key : string ] : any ;
4445}
4546
46- const getPathname = ( context : TRuntimeContext ) => {
47+ const getPathname = ( context : TRuntimeContext ) : string => {
4748 if ( isBrowser ( ) ) {
4849 return window . location . pathname ;
4950 }
5051 return context . ssrContext ?. request ?. pathname || '/' ;
5152} ;
5253
54+ interface RuntimeContextWithI18n extends TRuntimeContext {
55+ i18nInstance ?: I18nInstance ;
56+ }
57+
5358export const i18nPlugin = ( options : I18nPluginOptions ) : RuntimePlugin => ( {
5459 name : '@modern-js/plugin-i18n' ,
5560 setup : api => {
@@ -73,6 +78,9 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
7378
7479 api . onBeforeRender ( async context => {
7580 let i18nInstance = await getI18nInstance ( userI18nInstance ) ;
81+ const { i18n : otherConfig } = api . getRuntimeConfig ( ) ;
82+ const { initOptions : otherInitOptions } = otherConfig || { } ;
83+ const initOptions = merge ( otherInitOptions || { } , userInitOptions || { } ) ;
7684 const initReactI18next = await getInitReactI18next ( ) ;
7785 I18nextProvider = await getI18nextProvider ( ) ;
7886 if ( initReactI18next ) {
@@ -90,9 +98,9 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
9098 i18nextDetector ,
9199 detection ,
92100 localePathRedirect ,
93- userInitOptions ,
101+ initOptions ,
94102 ) ;
95- const mergedBackend = mergeBackendOptions ( backend , userInitOptions ) ;
103+ const mergedBackend = mergeBackendOptions ( backend , initOptions ) ;
96104
97105 // Register Backend before init() if needed
98106 if ( backendEnabled ) {
@@ -106,7 +114,7 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
106114 localePathRedirect,
107115 i18nextDetector,
108116 detection,
109- userInitOptions,
117+ userInitOptions : initOptions ,
110118 pathname,
111119 ssrContext : context . ssrContext ,
112120 } ) ;
@@ -119,7 +127,7 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
119127 languages ,
120128 mergedDetection ,
121129 mergedBackend ,
122- userInitOptions ,
130+ initOptions ,
123131 ) ;
124132
125133 if ( ! isBrowser ( ) && i18nInstance . cloneInstance ) {
@@ -134,7 +142,7 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
134142 i18nextDetector ,
135143 detection ,
136144 localePathRedirect ,
137- userInitOptions ,
145+ initOptions ,
138146 ) ;
139147 }
140148
@@ -150,21 +158,37 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
150158
151159 api . wrapRoot ( App => {
152160 return props => {
153- const runtimeContext = useRuntimeContext ( ) ;
154- const i18nInstance = ( runtimeContext as any ) . i18nInstance ;
155- const initialLang =
156- i18nInstance ?. language || ( localeDetection ?. fallbackLanguage ?? 'en' ) ;
161+ const runtimeContext = useRuntimeContext ( ) as RuntimeContextWithI18n ;
162+ const i18nInstance = runtimeContext . i18nInstance ;
163+ const initialLang = useMemo (
164+ ( ) =>
165+ i18nInstance ?. language ||
166+ ( localeDetection ?. fallbackLanguage ?? 'en' ) ,
167+ [ i18nInstance ?. language , localeDetection ?. fallbackLanguage ] ,
168+ ) ;
157169 const [ lang , setLang ] = useState ( initialLang ) ;
158170
159- if ( i18nInstance ?. language && i18nInstance . translator ) {
160- i18nInstance . translator . language = i18nInstance . language ;
161- }
171+ // Sync translator language with i18n instance language
172+ useEffect ( ( ) => {
173+ if ( i18nInstance ?. language ) {
174+ const translator = ( i18nInstance as any ) . translator ;
175+ if ( translator ) {
176+ translator . language = i18nInstance . language ;
177+ }
178+ }
179+ } , [ i18nInstance ?. language ] ) ;
180+
181+ // Handle language detection and updates
182+ const runtimeContextRef = useRef ( runtimeContext ) ;
183+ runtimeContextRef . current = runtimeContext ;
162184
163185 useEffect ( ( ) => {
186+ if ( ! i18nInstance ) {
187+ return ;
188+ }
189+
164190 if ( localePathRedirect ) {
165- const currentPathname = getPathname (
166- runtimeContext as TRuntimeContext ,
167- ) ;
191+ const currentPathname = getPathname ( runtimeContextRef . current ) ;
168192 const pathDetection = detectLanguageFromPath (
169193 currentPathname ,
170194 languages ,
@@ -174,8 +198,8 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
174198 const currentLang = pathDetection . language ;
175199 if ( currentLang !== lang ) {
176200 setLang ( currentLang ) ;
177- i18nInstance ? .setLang ?.( currentLang ) ;
178- i18nInstance ? .changeLanguage ?.( currentLang ) ;
201+ i18nInstance . setLang ?.( currentLang ) ;
202+ i18nInstance . changeLanguage ?.( currentLang ) ;
179203 if ( isBrowser ( ) ) {
180204 const detectionOptions = i18nInstance . options ?. detection ;
181205 cacheUserLanguage (
@@ -192,24 +216,49 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
192216 setLang ( instanceLang ) ;
193217 }
194218 }
195- } , [ ] ) ;
219+ } , [ i18nInstance , localePathRedirect , languages , lang ] ) ;
196220
197- const contextValue = {
198- language : lang ,
221+ const contextValue = useMemo ( ( ) => {
222+ if ( ! i18nInstance ) {
223+ // Return a minimal context value when i18nInstance is not available
224+ return {
225+ language : lang ,
226+ i18nInstance : { } as I18nInstance ,
227+ entryName,
228+ languages,
229+ localePathRedirect,
230+ ignoreRedirectRoutes,
231+ updateLanguage : setLang ,
232+ } ;
233+ }
234+ return {
235+ language : lang ,
236+ i18nInstance,
237+ entryName,
238+ languages,
239+ localePathRedirect,
240+ ignoreRedirectRoutes,
241+ updateLanguage : setLang ,
242+ } ;
243+ } , [
244+ lang ,
199245 i18nInstance ,
200246 entryName ,
201247 languages ,
202248 localePathRedirect ,
203249 ignoreRedirectRoutes ,
204- updateLanguage : setLang ,
205- } ;
250+ ] ) ;
206251
207252 const appContent = (
208253 < ModernI18nProvider value = { contextValue } >
209254 < App { ...props } />
210255 </ ModernI18nProvider >
211256 ) ;
212257
258+ if ( ! i18nInstance ) {
259+ return appContent ;
260+ }
261+
213262 return I18nextProvider ? (
214263 < I18nextProvider i18n = { i18nInstance } > { appContent } </ I18nextProvider >
215264 ) : (
0 commit comments