@@ -353,4 +353,296 @@ describe('withHeadstartWPConfig - Host Check', () => {
353353 } ) ;
354354 } ) ;
355355 } ) ;
356+
357+ describe ( 'Locale/i18n Handling' , ( ) => {
358+ beforeEach ( ( ) => {
359+ // Mock file system to ensure we're using pages router (not app router)
360+ // by returning false for app directory checks
361+ // eslint-disable-next-line global-require
362+ const fs = require ( 'fs' ) ;
363+ const mockExistsSync = fs . existsSync ;
364+ mockExistsSync . mockImplementation ( ( path : string ) => {
365+ if ( path . includes ( 'headstartwp.config.js' ) || path . includes ( 'headless.config.js' ) ) {
366+ return true ;
367+ }
368+ // Return false for app directory to ensure pages router
369+ if ( path . includes ( '/app' ) || path . includes ( 'src/app' ) ) {
370+ return false ;
371+ }
372+ return false ;
373+ } ) ;
374+ } ) ;
375+
376+ it ( 'should create locale-aware rewrites when i18n is configured for pages router' , async ( ) => {
377+ const headlessConfig = {
378+ sourceUrl : 'https://wp.example.com' ,
379+ } ;
380+
381+ const nextConfig = withHeadstartWPConfig (
382+ {
383+ i18n : {
384+ locales : [ 'en' , 'es' , 'fr' ] ,
385+ defaultLocale : 'en' ,
386+ } ,
387+ } ,
388+ headlessConfig ,
389+ ) ;
390+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
391+
392+ expect ( Array . isArray ( rewrites ) ) . toBe ( true ) ;
393+ // Should have 7 paths * 4 variants (default + 3 locales) = 28 rewrites
394+ // Plus 1 sitemap xsl rewrite without locale + 3 with locales = 4
395+ // Total: 28 + 4 = 32
396+ expect ( rewrites ) . toHaveLength ( 32 ) ;
397+
398+ // Check that we have rewrites for each locale
399+ const cacheHealthcheckRewrites = ( rewrites as Rewrite [ ] ) . filter ( ( r ) =>
400+ r . source . includes ( 'cache-healthcheck' ) ,
401+ ) ;
402+ expect ( cacheHealthcheckRewrites ) . toHaveLength ( 4 ) ; // default + 3 locales
403+
404+ // Check default rewrite (no locale)
405+ const defaultRewrite = cacheHealthcheckRewrites . find (
406+ ( r ) => r . source === '/cache-healthcheck' ,
407+ ) ;
408+ expect ( defaultRewrite ) . toBeDefined ( ) ;
409+ expect ( defaultRewrite ?. destination ) . toBe ( '/api/cache-healthcheck' ) ;
410+
411+ // Check locale-specific rewrites
412+ const enRewrite = cacheHealthcheckRewrites . find (
413+ ( r ) => r . source === '/en/cache-healthcheck' ,
414+ ) ;
415+ expect ( enRewrite ) . toBeDefined ( ) ;
416+ expect ( enRewrite ?. destination ) . toBe ( '/api/cache-healthcheck' ) ;
417+
418+ const esRewrite = cacheHealthcheckRewrites . find (
419+ ( r ) => r . source === '/es/cache-healthcheck' ,
420+ ) ;
421+ expect ( esRewrite ) . toBeDefined ( ) ;
422+ expect ( esRewrite ?. destination ) . toBe ( '/api/cache-healthcheck' ) ;
423+
424+ const frRewrite = cacheHealthcheckRewrites . find (
425+ ( r ) => r . source === '/fr/cache-healthcheck' ,
426+ ) ;
427+ expect ( frRewrite ) . toBeDefined ( ) ;
428+ expect ( frRewrite ?. destination ) . toBe ( '/api/cache-healthcheck' ) ;
429+ } ) ;
430+
431+ it ( 'should not create locale-aware rewrites when i18n is not configured' , async ( ) => {
432+ const headlessConfig = {
433+ sourceUrl : 'https://wp.example.com' ,
434+ } ;
435+
436+ const nextConfig = withHeadstartWPConfig ( { } , headlessConfig ) ;
437+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
438+
439+ expect ( Array . isArray ( rewrites ) ) . toBe ( true ) ;
440+ expect ( rewrites ) . toHaveLength ( 8 ) ; // Standard 8 rewrites without locales
441+
442+ // Verify no locale prefixes in sources
443+ ( rewrites as Rewrite [ ] ) . forEach ( ( rewrite ) => {
444+ expect ( rewrite . source ) . not . toMatch ( / ^ \/ ( e n | e s | f r ) \/ / ) ;
445+ } ) ;
446+ } ) ;
447+
448+ it ( 'should create locale-aware rewrites for multisite with i18n' , async ( ) => {
449+ const headlessConfig = {
450+ sites : [
451+ {
452+ sourceUrl : 'https://wp.site1.com' ,
453+ host : 'site1.com' ,
454+ } ,
455+ {
456+ sourceUrl : 'https://wp.site2.com' ,
457+ host : 'site2.com' ,
458+ } ,
459+ ] ,
460+ } ;
461+
462+ const nextConfig = withHeadstartWPConfig (
463+ {
464+ i18n : {
465+ locales : [ 'en' , 'es' ] ,
466+ defaultLocale : 'en' ,
467+ } ,
468+ } ,
469+ headlessConfig ,
470+ ) ;
471+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
472+
473+ expect ( Array . isArray ( rewrites ) ) . toBe ( true ) ;
474+ // 2 sites * 7 paths * 3 variants (default + 2 locales) = 42
475+ // Plus 2 sites * 3 sitemap xsl rewrites (default + 2 locales) = 6
476+ // Total: 42 + 6 = 48
477+ expect ( rewrites ) . toHaveLength ( 48 ) ;
478+
479+ // Check multisite prefix with locales
480+ const site1CacheRewrites = ( rewrites as Rewrite [ ] ) . filter (
481+ ( r ) => r . source . includes ( '_sites/:site' ) && r . source . includes ( 'cache-healthcheck' ) ,
482+ ) ;
483+ expect ( site1CacheRewrites . length ) . toBeGreaterThan ( 0 ) ;
484+
485+ // Check that we have rewrites with multisite prefix and locales
486+ const multisiteEnRewrite = ( rewrites as Rewrite [ ] ) . find (
487+ ( r ) => r . source === '/_sites/:site/en/cache-healthcheck' ,
488+ ) ;
489+ expect ( multisiteEnRewrite ) . toBeDefined ( ) ;
490+
491+ const multisiteEsRewrite = ( rewrites as Rewrite [ ] ) . find (
492+ ( r ) => r . source === '/_sites/:site/es/cache-healthcheck' ,
493+ ) ;
494+ expect ( multisiteEsRewrite ) . toBeDefined ( ) ;
495+
496+ const multisiteDefaultRewrite = ( rewrites as Rewrite [ ] ) . find (
497+ ( r ) => r . source === '/_sites/:site/cache-healthcheck' ,
498+ ) ;
499+ expect ( multisiteDefaultRewrite ) . toBeDefined ( ) ;
500+ } ) ;
501+
502+ it ( 'should handle sitemap stylesheet rewrites with locales' , async ( ) => {
503+ const headlessConfig = {
504+ sourceUrl : 'https://wp.example.com' ,
505+ } ;
506+
507+ const nextConfig = withHeadstartWPConfig (
508+ {
509+ i18n : {
510+ locales : [ 'en' , 'es' ] ,
511+ defaultLocale : 'en' ,
512+ } ,
513+ } ,
514+ headlessConfig ,
515+ ) ;
516+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
517+
518+ // Find sitemap xsl rewrites - the pattern uses escaped dots
519+ const sitemapXslRewrites = ( rewrites as Rewrite [ ] ) . filter ( ( r ) =>
520+ r . source . includes ( 'main-sitemap' ) ,
521+ ) ;
522+
523+ // Should have default + 2 locales = 3 rewrites
524+ expect ( sitemapXslRewrites ) . toHaveLength ( 3 ) ;
525+
526+ // Check default - pattern uses \\. for escaped dot
527+ const defaultXsl = sitemapXslRewrites . find ( ( r ) =>
528+ r . source . includes ( ':path(.*main-sitemap' ) ,
529+ ) ;
530+ expect ( defaultXsl ) . toBeDefined ( ) ;
531+ expect ( defaultXsl ?. source ) . toBe ( '/:path(.*main-sitemap\\.xsl)' ) ;
532+
533+ // Check locale-specific
534+ const enXsl = sitemapXslRewrites . find ( ( r ) => r . source . startsWith ( '/en/' ) ) ;
535+ expect ( enXsl ) . toBeDefined ( ) ;
536+ expect ( enXsl ?. source ) . toBe ( '/en/:path(.*main-sitemap\\.xsl)' ) ;
537+
538+ const esXsl = sitemapXslRewrites . find ( ( r ) => r . source . startsWith ( '/es/' ) ) ;
539+ expect ( esXsl ) . toBeDefined ( ) ;
540+ expect ( esXsl ?. source ) . toBe ( '/es/:path(.*main-sitemap\\.xsl)' ) ;
541+ } ) ;
542+
543+ it ( 'should not apply i18n rewrites when using app router' , async ( ) => {
544+ // Mock app router by returning true for app directory
545+ // eslint-disable-next-line global-require
546+ const fs = require ( 'fs' ) ;
547+ const mockExistsSync = fs . existsSync ;
548+ mockExistsSync . mockImplementation ( ( path : string ) => {
549+ if ( path . includes ( 'headstartwp.config.js' ) || path . includes ( 'headless.config.js' ) ) {
550+ return true ;
551+ }
552+ // Return true for app directory to simulate app router
553+ if ( path . includes ( '/app' ) || path . includes ( 'src/app' ) ) {
554+ return true ;
555+ }
556+ return false ;
557+ } ) ;
558+
559+ const headlessConfig = {
560+ sourceUrl : 'https://wp.example.com' ,
561+ } ;
562+
563+ const nextConfig = withHeadstartWPConfig (
564+ {
565+ i18n : {
566+ locales : [ 'en' , 'es' ] ,
567+ defaultLocale : 'en' ,
568+ } ,
569+ } ,
570+ headlessConfig ,
571+ ) ;
572+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
573+
574+ expect ( Array . isArray ( rewrites ) ) . toBe ( true ) ;
575+ // App router should not use Next.js i18n, so should have standard 8 rewrites
576+ expect ( rewrites ) . toHaveLength ( 8 ) ;
577+
578+ // Verify no locale prefixes (app router uses its own i18n system)
579+ ( rewrites as Rewrite [ ] ) . forEach ( ( rewrite ) => {
580+ expect ( rewrite . source ) . not . toMatch ( / ^ \/ ( e n | e s ) \/ / ) ;
581+ } ) ;
582+ } ) ;
583+
584+ it ( 'should handle feed rewrites with locales' , async ( ) => {
585+ const headlessConfig = {
586+ sourceUrl : 'https://wp.example.com' ,
587+ } ;
588+
589+ const nextConfig = withHeadstartWPConfig (
590+ {
591+ i18n : {
592+ locales : [ 'en' , 'de' ] ,
593+ defaultLocale : 'en' ,
594+ } ,
595+ } ,
596+ headlessConfig ,
597+ ) ;
598+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
599+
600+ // Find feed rewrites
601+ const feedRewrites = ( rewrites as Rewrite [ ] ) . filter ( ( r ) => r . source . includes ( 'feed' ) ) ;
602+
603+ // Should have default + 2 locales = 3 rewrites
604+ expect ( feedRewrites ) . toHaveLength ( 3 ) ;
605+
606+ // Check all have correct destination
607+ feedRewrites . forEach ( ( rewrite ) => {
608+ expect ( rewrite . destination ) . toBe ( 'https://wp.example.com/feed/?rewrite_urls=1' ) ;
609+ } ) ;
610+
611+ // Check sources
612+ const defaultFeed = feedRewrites . find ( ( r ) => r . source === '/feed' ) ;
613+ expect ( defaultFeed ) . toBeDefined ( ) ;
614+
615+ const enFeed = feedRewrites . find ( ( r ) => r . source === '/en/feed' ) ;
616+ expect ( enFeed ) . toBeDefined ( ) ;
617+
618+ const deFeed = feedRewrites . find ( ( r ) => r . source === '/de/feed' ) ;
619+ expect ( deFeed ) . toBeDefined ( ) ;
620+ } ) ;
621+
622+ it ( 'should preserve host checks when i18n is configured' , async ( ) => {
623+ const headlessConfig = {
624+ sourceUrl : 'https://wp.example.com' ,
625+ host : 'example.com' ,
626+ } ;
627+
628+ const nextConfig = withHeadstartWPConfig (
629+ {
630+ i18n : {
631+ locales : [ 'en' , 'es' ] ,
632+ defaultLocale : 'en' ,
633+ } ,
634+ } ,
635+ headlessConfig ,
636+ ) ;
637+ const rewrites = ( await nextConfig . rewrites ?.( ) ) ?? [ ] ;
638+
639+ // All rewrites should have host check
640+ ( rewrites as Rewrite [ ] ) . forEach ( ( rewrite ) => {
641+ expect ( rewrite ) . toHaveProperty ( 'has' ) ;
642+ expect ( rewrite . has ) . toEqual ( [
643+ { type : 'header' , key : 'host' , value : 'example.com' } ,
644+ ] ) ;
645+ } ) ;
646+ } ) ;
647+ } ) ;
356648} ) ;
0 commit comments