1- import fs from 'node:fs'
2- import { addTemplate } from '@nuxt/kit'
1+ import { addTemplate , extendPages } from '@nuxt/kit'
32import { relative } from 'pathe'
43import type { NuxtPage } from '@nuxt/schema'
54import { defineVuepalFeature } from '../defineFeature'
6- import type { FileCache } from '../../classes/FileCache'
7- import type { ModuleHelper } from '../../classes/ModuleHelper'
85import { logger } from '../../helpers'
96
107function nonNullable < T > ( value : T ) : value is NonNullable < T > {
118 return value !== null && value !== undefined
129}
1310
14- /**
15- * Extracts the language mapping.
16- */
17- const extractLanguageMapping = (
18- code : string ,
19- ) : Record < string , string > | undefined => {
20- const RGX = / l a n g u a g e M a p p i n g : \s * \{ ( [ ^ } ] + ) \} /
21- const matches = code . match ( RGX )
22-
23- const match = matches ?. [ 1 ]
24-
25- if ( ! match ) {
26- return
27- }
28-
29- const jsonString = `{${ match . trim ( ) . replace ( / ' / g, '"' ) } }`
30-
31- const fn = new Function ( `return ${ jsonString } ` )
32- const mapping = fn ( )
33- if ( typeof mapping !== 'object' ) {
34- return
35- }
36-
37- for ( const key in mapping ) {
38- if ( typeof key !== 'string' ) {
39- return
40- }
41-
42- const value = mapping [ key ]
43-
44- if ( typeof value !== 'string' ) {
45- return
46- }
47- }
48-
49- return mapping
50- }
51-
5211type ExtractedPage = {
5312 filePath : string
5413 isDrupalFrontendRoute : boolean
5514 yml ?: string
5615}
5716
5817class PageCollector {
59- private cache : FileCache < ExtractedPage >
6018 private templateContents = ''
6119
62- constructor (
63- helper : ModuleHelper ,
64- private langcodes : string [ ] ,
65- ) {
66- this . cache = helper . createFileCache ( )
67- }
68-
69- private async handlePage ( page : NuxtPage ) : Promise < ExtractedPage | undefined > {
70- if ( ! page . file ) {
71- return
72- }
73-
74- if ( ! page . name ) {
75- return
76- }
20+ constructor ( private defaultLanguage : string ) { }
7721
22+ private handlePage ( page : NuxtPage ) : ExtractedPage | undefined {
7823 const name = page . name
7924
80- const cached = this . cache . get ( page . file )
81- if ( cached ) {
82- return cached
25+ if ( ! page . file ) {
26+ return
8327 }
8428
85- const contents = await fs . promises
86- . readFile ( page . file )
87- . then ( ( v ) => v . toString ( ) )
88-
8929 const extracted : ExtractedPage = {
9030 filePath : page . file ,
9131 isDrupalFrontendRoute : false ,
9232 }
9333
94- if ( contents . includes ( ' drupalFrontendRoute' ) ) {
34+ if ( page . meta ?. drupalFrontendRoute ) {
9535 extracted . isDrupalFrontendRoute = true
9636 try {
97- const languageMapping = extractLanguageMapping ( contents )
98- if ( languageMapping ) {
99- const mapping = Object . entries ( languageMapping )
100- . map ( ( [ langcode , path ] ) => {
101- return ` ${ langcode } : '${ path } '`
102- } )
103- . join ( '\n' )
104- extracted . yml = ` ${ name } :\n aliases:\n${ mapping } `
105- }
37+ const mapping = Object . entries ( {
38+ ...( page . meta . languageMapping || { } ) ,
39+ [ this . defaultLanguage ] : page . path ,
40+ } )
41+ . map ( ( [ langcode , path ] ) => {
42+ return ` ${ langcode } : '${ path } '`
43+ } )
44+ . sort ( )
45+ . join ( '\n' )
46+ extracted . yml = ` ${ name } :\n aliases:\n${ mapping } `
10647 } catch ( e ) {
10748 logger . warn (
10849 `Failed to extract language mapping in page "${ page . file } "` ,
@@ -111,19 +52,15 @@ class PageCollector {
11152 }
11253 }
11354
114- this . cache . set ( page . file , extracted )
11555 return extracted
11656 }
11757
118- public async handlePages ( pages : NuxtPage [ ] ) {
119- const mapped = await Promise . all (
120- pages . map ( ( page ) => this . handlePage ( page ) ) ,
121- ) . then ( ( result ) =>
122- result
123- . map ( ( v ) => v ?. yml )
124- . filter ( nonNullable )
125- . sort ( ) ,
126- )
58+ public handlePages ( pages : NuxtPage [ ] ) {
59+ const mapped = pages
60+ . map ( ( page ) => this . handlePage ( page ) )
61+ . map ( ( v ) => v ?. yml )
62+ . filter ( nonNullable )
63+ . sort ( )
12764
12865 this . templateContents = `keys:\n${ mapped . join ( '\n' ) } `
12966 }
@@ -135,9 +72,9 @@ class PageCollector {
13572
13673export default defineVuepalFeature < {
13774 /**
138- * The supported language codes .
75+ * The default language.
13976 */
140- langcodes : string [ ]
77+ defaultLanguage : string
14178
14279 /**
14380 * The output path of the generated YML file.
@@ -180,30 +117,53 @@ export {}
180117 }
181118
182119 const outputPath = options ?. outputPath
183- const langcodes = options ?. langcodes
184120 if ( ! outputPath ) {
185121 throw new Error ( `Missing required option "frontendRouting.outputPath".` )
186122 }
187123
188- if ( ! langcodes ?. length ) {
189- throw new Error ( `Missing required option "frontendRouting.langcodes".` )
124+ if ( ! options . defaultLanguage ) {
125+ throw new Error (
126+ `Missing required option "frontendRouting.defaultLanguage".` ,
127+ )
128+ }
129+
130+ const languageNegotiationModuleIndex =
131+ helper . nuxt . options . modules . findIndex (
132+ ( v ) => v === 'nuxt-language-negotiation' ,
133+ )
134+
135+ // Module is installed.
136+ if ( languageNegotiationModuleIndex !== - 1 ) {
137+ const vuepalIndex = helper . nuxt . options . modules . findIndex (
138+ ( v ) => v === 'vuepal' ,
139+ )
140+
141+ // Make sure that nuxt-language-negotiation runs after vuepal, or else
142+ // the routes have already been translated.
143+ if ( languageNegotiationModuleIndex < vuepalIndex ) {
144+ throw new Error (
145+ 'The "nuxt-language-negotiation" module must be put after "vuepal" in nuxt.config.ts' ,
146+ )
147+ }
190148 }
191149
192- const collector = new PageCollector ( helper , langcodes )
150+ const collector = new PageCollector ( options . defaultLanguage )
193151
194152 addTemplate ( {
195153 filename : helper . resolvers . root . resolve ( options . outputPath ) ,
196154 write : true ,
197155 getContents : ( ) => collector . getTemplateContents ( ) ,
198156 } )
199157
200- // This hook is called by Nuxt when any page changes.
201- // During development, the hook is called *after* the builder:watch
202- // event.
203- helper . nuxt . hooks . hook ( 'pages:resolved' , async ( pages ) => {
204- helper . logDebug ( 'frontendRouting: pages:resolved start' )
205- await collector . handlePages ( pages )
206- helper . logDebug ( 'frontendRouting: pages:resolved done' )
158+ helper . nuxt . options . experimental . scanPageMeta = true
159+ helper . nuxt . options . experimental . extraPageMetaExtractionKeys ||= [ ]
160+ helper . nuxt . options . experimental . extraPageMetaExtractionKeys . push (
161+ 'languageMapping' ,
162+ 'drupalFrontendRoute' ,
163+ )
164+
165+ extendPages ( ( pages ) => {
166+ collector . handlePages ( pages )
207167 } )
208168 } ,
209169} )
0 commit comments