@@ -20,10 +20,18 @@ const forceTranslate = process.env.FORCE_TRANSLATE === 'true' ||
2020 process . argv . includes ( '--force-retranslate' ) ||
2121 process . argv . includes ( 'force' ) ;
2222
23+ // Check for titles-only mode
24+ const titlesOnly = process . argv . includes ( '--titles-only' ) ||
25+ process . argv . includes ( 'titles-only' ) ;
26+
2327if ( forceTranslate ) {
2428 console . log ( 'Force mode enabled - retranslating all content regardless of changes\n' ) ;
2529}
2630
31+ if ( titlesOnly ) {
32+ console . log ( 'Titles-only mode enabled - translating only frontmatter titles\n' ) ;
33+ }
34+
2735// Track if any translations were made across all locales
2836let hasChanges = false ;
2937
@@ -92,40 +100,46 @@ async function main() {
92100 for ( const locale of targetLocales ) {
93101 console . group ( `Translating ${ locale } :` ) ;
94102
95- await translateDocs ( locale ) ;
96- await translateJSON (
97- locale ,
98- path . resolve (
99- siteDir ,
100- './i18n' ,
103+ if ( titlesOnly ) {
104+ // Only translate titles in existing translated files
105+ await translateDocsTitlesOnly ( locale ) ;
106+ } else {
107+ // Normal translation flow
108+ await translateDocs ( locale ) ;
109+ await translateJSON (
101110 locale ,
102- './docusaurus-plugin-content-docs/current.json'
103- ) ,
104- [ 'sidebar.documentationSidebar.category.' ]
105- ) ;
106- await translateJSON (
107- locale ,
108- path . resolve (
109- siteDir ,
110- './i18n' ,
111+ path . resolve (
112+ siteDir ,
113+ './i18n' ,
114+ locale ,
115+ './docusaurus-plugin-content-docs/current.json'
116+ ) ,
117+ [ 'sidebar.documentationSidebar.category.' ]
118+ ) ;
119+ await translateJSON (
111120 locale ,
112- './docusaurus-theme-classic/footer.json'
113- ) ,
114- [ 'link.title.' , 'link.item.label.' ]
115- ) ;
116- await translateJSON (
117- locale ,
118- path . resolve (
119- siteDir ,
120- './i18n' ,
121+ path . resolve (
122+ siteDir ,
123+ './i18n' ,
124+ locale ,
125+ './docusaurus-theme-classic/footer.json'
126+ ) ,
127+ [ 'link.title.' , 'link.item.label.' ]
128+ ) ;
129+ await translateJSON (
121130 locale ,
122- './docusaurus-theme-classic/navbar.json'
123- ) ,
124- [ 'item.label.' ]
125- ) ;
126-
127- // Translate the main code.json file containing all translation strings
128- await translateCodeJSON ( locale ) ;
131+ path . resolve (
132+ siteDir ,
133+ './i18n' ,
134+ locale ,
135+ './docusaurus-theme-classic/navbar.json'
136+ ) ,
137+ [ 'item.label.' ]
138+ ) ;
139+
140+ // Translate the main code.json file containing all translation strings
141+ await translateCodeJSON ( locale ) ;
142+ }
129143
130144 console . groupEnd ( ) ;
131145 }
@@ -252,6 +266,84 @@ async function translateDocs(locale: string) {
252266 console . log ( ` Completed translation of ${ results . length } files. Total token usage: ${ totalTokens } ` ) ;
253267}
254268
269+ async function translateDocsTitlesOnly ( locale : string ) {
270+ const docsDir = path . resolve ( siteDir , './docs' ) ;
271+ const translatedDocsDir = path . resolve (
272+ siteDir ,
273+ './i18n' ,
274+ locale ,
275+ './docusaurus-plugin-content-docs/current'
276+ ) ;
277+
278+ // Find all translated files
279+ const translatedFiles = await fg ( [ '**/*.md' , '**/*.mdx' ] , {
280+ cwd : translatedDocsDir ,
281+ onlyFiles : true ,
282+ } ) ;
283+
284+ let updatedCount = 0 ;
285+ let totalTokens = 0 ;
286+
287+ console . log ( ` Processing ${ translatedFiles . length } files for title translation...` ) ;
288+
289+ for ( const file of translatedFiles ) {
290+ const translatedPath = path . resolve ( translatedDocsDir , file ) ;
291+ const sourcePath = path . resolve ( docsDir , file ) ;
292+
293+ // Check if source file exists
294+ if ( ! fs . existsSync ( sourcePath ) ) {
295+ console . warn ( ` Warning: Source file not found for ${ file } , skipping` ) ;
296+ continue ;
297+ }
298+
299+ // Read both source and translated files
300+ const sourceContent = fs . readFileSync ( sourcePath , 'utf-8' ) ;
301+ const { data : sourceFrontmatter } = matter ( sourceContent ) ;
302+
303+ const translatedContent = fs . readFileSync ( translatedPath , 'utf-8' ) ;
304+ const { data : translatedFrontmatter , content : translatedBody } = matter ( translatedContent ) ;
305+
306+ // Check if source has a title and it needs translation
307+ if ( ! sourceFrontmatter . title || typeof sourceFrontmatter . title !== 'string' ) {
308+ continue ;
309+ }
310+
311+ // Check if the title has already been translated (not the same as source)
312+ if ( translatedFrontmatter . title && translatedFrontmatter . title !== sourceFrontmatter . title ) {
313+ // Title already translated, skip
314+ continue ;
315+ }
316+
317+ // Translate the title
318+ try {
319+ console . log ( ` Translating title for: ${ file } ` ) ;
320+ const titleResult = await translateTitle ( sourceFrontmatter . title , locale ) ;
321+
322+ // Update frontmatter with new title
323+ const updatedFrontmatter = { ...translatedFrontmatter , title : titleResult . translatedText } ;
324+
325+ // Write back the file with updated title but same content and hash
326+ const updatedFileContent = matter . stringify ( translatedBody , updatedFrontmatter ) ;
327+ const normalizedContent = updatedFileContent . replace ( / \r \n / g, '\n' ) ;
328+ fs . writeFileSync ( translatedPath , normalizedContent ) ;
329+
330+ updatedCount ++ ;
331+ if ( titleResult . usage ?. total_tokens ) {
332+ totalTokens += titleResult . usage . total_tokens ;
333+ }
334+ } catch ( error ) {
335+ console . warn ( ` Failed to translate title for ${ file } : ${ error } ` ) ;
336+ }
337+ }
338+
339+ if ( updatedCount > 0 ) {
340+ hasChanges = true ;
341+ console . log ( ` Completed title translation for ${ updatedCount } files. Total token usage: ${ totalTokens } ` ) ;
342+ } else {
343+ console . log ( ` No titles needed translation.` ) ;
344+ }
345+ }
346+
255347
256348async function translateJSON (
257349 locale : string ,
0 commit comments