Skip to content

Commit bc18a45

Browse files
feat: adding behavior to translate only titles:
1 parent 84e29ea commit bc18a45

File tree

2 files changed

+125
-32
lines changed

2 files changed

+125
-32
lines changed

docs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"write-translations": "docusaurus write-translations",
1414
"write-heading-ids": "docusaurus write-heading-ids",
1515
"translate": "tsx tools/i18n-translator/index.ts",
16-
"translate:force": "tsx tools/i18n-translator/index.ts force"
16+
"translate:force": "tsx tools/i18n-translator/index.ts force",
17+
"translate:titles": "tsx tools/i18n-translator/index.ts titles-only"
1718
},
1819
"dependencies": {
1920
"@docusaurus/core": "^3.9.1",

docs/tools/i18n-translator/index.ts

Lines changed: 123 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2327
if (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
2836
let 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

256348
async function translateJSON(
257349
locale: string,

0 commit comments

Comments
 (0)