@@ -9,10 +9,10 @@ import type { BaseLocaleDetectionOptions } from '../utils/config';
99import { ModernI18nProvider } from './context' ;
1010import type { I18nInitOptions , I18nInstance } from './i18n' ;
1111import { getI18nInstance } from './i18n' ;
12- import { useI18nextLanguageDetector } from './i18n/detection' ;
12+ import { detectLanguage , useI18nextLanguageDetector } from './i18n/detection' ;
1313import { mergeDetectionOptions } from './i18n/detection/config' ;
1414import { getI18nextProvider , getInitReactI18next } from './i18n/instance' ;
15- import { getEntryPath , getLanguageFromPath } from './utils' ;
15+ import { getEntryPath } from './utils' ;
1616
1717export interface I18nPluginOptions {
1818 entryName ?: string ;
@@ -32,72 +32,138 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
3232 localeDetection,
3333 } = options ;
3434 const {
35- localePathRedirect,
36- i18nextDetector,
35+ localePathRedirect = false ,
36+ i18nextDetector = true ,
3737 languages = [ ] ,
3838 fallbackLanguage = 'en' ,
3939 } = localeDetection || { } ;
4040 let I18nextProvider : React . FunctionComponent < any > | null ;
4141
4242 // Helper function to detect language from path
43- const detectLanguageFromPath = ( pathname : string ) => {
44- if ( localePathRedirect ) {
45- const relativePath = pathname . replace ( getEntryPath ( entryName ) , '' ) ;
46- const detectedLang = getLanguageFromPath (
47- relativePath ,
48- languages ,
49- fallbackLanguage ,
50- ) ;
51- // If no language is detected from path, use fallback language
52- return detectedLang || fallbackLanguage ;
43+ // Returns: { detected: true, language: string } if language found in path
44+ // { detected: false } if no language found in path
45+ const detectLanguageFromPath = (
46+ pathname : string ,
47+ ) : {
48+ detected : boolean ;
49+ language ?: string ;
50+ } => {
51+ if ( ! localePathRedirect ) {
52+ return { detected : false } ;
53+ }
54+
55+ const relativePath = pathname . replace ( getEntryPath ( entryName ) , '' ) ;
56+ const segments = relativePath . split ( '/' ) . filter ( Boolean ) ;
57+ const firstSegment = segments [ 0 ] ;
58+
59+ // Check if first segment is a valid language
60+ if ( firstSegment && languages . includes ( firstSegment ) ) {
61+ return { detected : true , language : firstSegment } ;
5362 }
54- return fallbackLanguage ;
63+
64+ return { detected : false } ;
5565 } ;
5666
5767 api . onBeforeRender ( async context => {
5868 let i18nInstance = await getI18nInstance ( userI18nInstance ) ;
5969 const initReactI18next = await getInitReactI18next ( ) ;
6070 I18nextProvider = await getI18nextProvider ( ) ;
71+
6172 if ( initReactI18next ) {
6273 i18nInstance . use ( initReactI18next ) ;
6374 }
75+
76+ // Get pathname from appropriate source
77+ const getPathname = ( ) => {
78+ if ( isBrowser ( ) ) {
79+ return window . location . pathname ;
80+ }
81+ return context . ssrContext ?. request ?. pathname || '/' ;
82+ } ;
83+
84+ // Step 1: Detect language with priority: path > i18next detector > fallback
85+ let detectedLanguage : string | undefined ;
86+
87+ // Priority 1: Path detection (if enabled)
88+ if ( localePathRedirect ) {
89+ const pathname = getPathname ( ) ;
90+ const pathDetection = detectLanguageFromPath ( pathname ) ;
91+ if ( pathDetection . detected && pathDetection . language ) {
92+ detectedLanguage = pathDetection . language ;
93+ }
94+ }
95+
96+ // Step 2: Register detector and prepare detection options if enabled
6497 if ( i18nextDetector ) {
6598 useI18nextLanguageDetector ( i18nInstance ) ;
6699 }
67- // Always detect language from path for consistency between SSR and client
68- let initialLanguage = fallbackLanguage ;
69- if ( localePathRedirect ) {
100+
101+ // Merge detection options and exclude 'path' if localePathRedirect is enabled
102+ // to avoid conflict with manual path detection
103+ const mergedDetection = i18nextDetector
104+ ? mergeDetectionOptions ( userInitOptions ?. detection )
105+ : userInitOptions ?. detection ;
106+ if ( localePathRedirect && mergedDetection ?. order ) {
107+ mergedDetection . order = mergedDetection . order . filter (
108+ ( item : string ) => item !== 'path' ,
109+ ) ;
110+ }
111+
112+ // Step 3: Use i18next detector if path didn't detect (Priority 2)
113+ if ( ! detectedLanguage && i18nextDetector ) {
114+ // Initialize with fallback language first to enable detector
115+ const initOptions : I18nInitOptions = {
116+ fallbackLng : fallbackLanguage ,
117+ supportedLngs : languages ,
118+ ...( userInitOptions || { } ) ,
119+ detection : mergedDetection ,
120+ } ;
121+ await i18nInstance . init ( initOptions ) ;
122+
123+ // Use detector to detect language
124+ let detectorLang : string | undefined ;
70125 if ( isBrowser ( ) ) {
71- // In browser, get from window.location
72- initialLanguage = detectLanguageFromPath ( window . location . pathname ) ;
126+ detectorLang = detectLanguage ( i18nInstance ) ;
73127 } else {
74- // In SSR, get from request context
75- const pathname = context . ssrContext ?. request ?. pathname || '/' ;
76- initialLanguage = detectLanguageFromPath ( pathname ) ;
128+ const request = context . ssrContext ?. request ;
129+ if ( request ) {
130+ detectorLang = detectLanguage ( i18nInstance , request as any ) ;
131+ }
132+ }
133+
134+ // Validate detected language
135+ if ( detectorLang ) {
136+ if ( languages . length === 0 || languages . includes ( detectorLang ) ) {
137+ detectedLanguage = detectorLang ;
138+ }
77139 }
78140 }
79- if ( ! i18nInstance . isInitialized ) {
80- // Merge detection options: default options + user options
81- const mergedDetection = i18nextDetector
82- ? mergeDetectionOptions ( userInitOptions ?. detection )
83- : userInitOptions ?. detection ;
84141
142+ // Priority 3: Use fallback language
143+ const finalLanguage = detectedLanguage || fallbackLanguage ;
144+
145+ // Step 4: Initialize i18n if not already initialized
146+ if ( ! i18nInstance . isInitialized ) {
85147 const initOptions : I18nInitOptions = {
86- lng : initialLanguage ,
148+ lng : finalLanguage ,
87149 fallbackLng : fallbackLanguage ,
88150 supportedLngs : languages ,
89151 ...( userInitOptions || { } ) ,
90152 detection : mergedDetection ,
91153 } ;
92154 await i18nInstance . init ( initOptions ) ;
155+ } else {
156+ // Update language if different
157+ if ( i18nInstance . language !== finalLanguage ) {
158+ await i18nInstance . changeLanguage ( finalLanguage ) ;
159+ }
93160 }
161+
162+ // Clone instance for SSR if needed
94163 if ( ! isBrowser ( ) && i18nInstance . cloneInstance ) {
95164 i18nInstance = i18nInstance . cloneInstance ( ) ;
96165 }
97- if ( localePathRedirect && i18nInstance . language !== initialLanguage ) {
98- // If instance is already initialized but language doesn't match the path, update it
99- await i18nInstance . changeLanguage ( initialLanguage ) ;
100- }
166+
101167 context . i18nInstance = i18nInstance ;
102168 } ) ;
103169
@@ -107,7 +173,7 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
107173 const i18nInstance = ( runtimeContext as any ) . i18nInstance ;
108174 const [ lang , setLang ] = useState ( i18nInstance . language ) ;
109175
110- if ( ! isBrowser ) {
176+ if ( ! isBrowser ( ) ) {
111177 ( i18nInstance as any ) . translator . language = i18nInstance . language ;
112178 }
113179
@@ -125,11 +191,14 @@ export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({
125191 useEffect ( ( ) => {
126192 if ( localePathRedirect ) {
127193 const currentPathname = getCurrentPathname ( ) ;
128- const currentLang = detectLanguageFromPath ( currentPathname ) ;
129- if ( currentLang !== lang ) {
130- setLang ( currentLang ) ;
131- // Update i18n instance language
132- i18nInstance . changeLanguage ( currentLang ) ;
194+ const pathDetection = detectLanguageFromPath ( currentPathname ) ;
195+ if ( pathDetection . detected && pathDetection . language ) {
196+ const currentLang = pathDetection . language ;
197+ if ( currentLang !== lang ) {
198+ setLang ( currentLang ) ;
199+ // Update i18n instance language
200+ i18nInstance . changeLanguage ( currentLang ) ;
201+ }
133202 }
134203 }
135204 } , [ ] ) ;
0 commit comments