@@ -165,15 +165,64 @@ function TranslationProvider__Prod({
165165 const [ cookieConfig ] = useState ( customCookieConfig || defaultCookieConfig ) ;
166166 // TODO (AleksandrSl 01/12/2025): Correctly provide default locale.
167167 const [ locale , setLocaleState ] = useState ( initialLocale ?? "en" ) ;
168+ const [ translations , setTranslations ] =
169+ useState < Record < string , string > > ( initialTranslations ) ;
170+ const [ isLoading , setIsLoading ] = useState ( false ) ;
168171
169172 logger . debug (
170173 `TranslationProvider initialized with locale: ${ locale } ` ,
171174 initialTranslations ,
172175 ) ;
173176
174177 /**
175- * Change locale - triggers server re-render via router.refresh()
176- * Following next-intl pattern: locale changes reload the page with new translations from server
178+ * Load translations from public/translations/{locale}.json
179+ * Lazy loads on-demand for SPAs
180+ */
181+ const loadTranslations = useCallback (
182+ async ( targetLocale : string ) => {
183+ // If we already have initialTranslations (Next.js SSR), don't fetch
184+ if ( Object . keys ( initialTranslations ) . length > 0 ) {
185+ return ;
186+ }
187+
188+ setIsLoading ( true ) ;
189+ try {
190+ const response = await fetch ( `/translations/${ targetLocale } .json` ) ;
191+ if ( ! response . ok ) {
192+ throw new Error (
193+ `Failed to load translations for ${ targetLocale } : ${ response . statusText } ` ,
194+ ) ;
195+ }
196+
197+ const data = await response . json ( ) ;
198+ // Translation files have format: { version, locale, entries: {...} }
199+ setTranslations ( data . entries || data ) ;
200+ logger . debug (
201+ `Loaded translations for ${ targetLocale } :` ,
202+ Object . keys ( data . entries || data ) . length ,
203+ ) ;
204+ } catch ( error ) {
205+ logger . error ( `Failed to load translations for ${ targetLocale } :` , error ) ;
206+ // Fallback to empty translations
207+ setTranslations ( { } ) ;
208+ } finally {
209+ setIsLoading ( false ) ;
210+ }
211+ } ,
212+ [ initialTranslations ] ,
213+ ) ;
214+
215+ // Load translations on mount if not provided via initialTranslations
216+ useEffect ( ( ) => {
217+ if ( Object . keys ( initialTranslations ) . length === 0 ) {
218+ loadTranslations ( locale ) ;
219+ }
220+ } , [ ] ) ; // Only run on mount
221+
222+ /**
223+ * Change locale
224+ * - For Next.js SSR: triggers server re-render via router.refresh()
225+ * - For SPAs: lazy loads translations from /translations/{locale}.json
177226 */
178227 const setLocale = useCallback (
179228 async ( newLocale : string ) => {
@@ -183,23 +232,26 @@ function TranslationProvider__Prod({
183232 // 2. Update local state for immediate UI feedback
184233 setLocaleState ( newLocale ) ;
185234
186- // 3. Trigger server re-render - Server Components will fetch new translations
187- // and pass them as initialTranslations prop, causing this component to re-render
235+ // 3a. Next.js pattern: Trigger server re-render
188236 if ( router ) {
189237 router . refresh ( ) ;
190238 }
239+ // 3b. SPA pattern: Lazy load translations
240+ else {
241+ await loadTranslations ( newLocale ) ;
242+ }
191243 } ,
192- [ cookieConfig , router ] ,
244+ [ cookieConfig , router , loadTranslations ] ,
193245 ) ;
194246
195247 return (
196248 < TranslationContext . Provider
197249 value = { {
198250 locale,
199251 setLocale,
200- translations : initialTranslations ,
252+ translations,
201253 registerHashes : ( ) => { } , // No-op in production
202- isLoading : false , // No loading state - translations come from server
254+ isLoading,
203255 sourceLocale,
204256 } }
205257 >
0 commit comments