Skip to content

Commit 84e29ea

Browse files
feat: adding new prompt to translate titles
1 parent e0991b9 commit 84e29ea

File tree

4 files changed

+152
-15
lines changed

4 files changed

+152
-15
lines changed

.github/workflows/translations.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ on:
1111
required: false
1212
default: 'all'
1313
type: string
14+
force:
15+
description: 'Force retranslation of all content (ignores hash checks)'
16+
required: false
17+
default: false
18+
type: boolean
1419

1520
jobs:
1621
translate:
@@ -60,7 +65,11 @@ jobs:
6065
env:
6166
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
6267
run: |
63-
npm run translate
68+
if [ "${{ github.event.inputs.force }}" = "true" ]; then
69+
npm run translate:force
70+
else
71+
npm run translate
72+
fi
6473
EXIT_CODE=$?
6574
if [ $EXIT_CODE -eq 1 ]; then
6675
echo "No translations needed - skipping build and commit"

docs/package.json

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

docs/tools/i18n-translator/index.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ import fg from 'fast-glob';
1010
import matter from 'gray-matter';
1111
import fs from 'fs-extra';
1212
import md5 from 'md5';
13-
import { translate, translateBatch, TranslationTask } from './translate';
13+
import { translate, translateBatch, translateTitle, TranslationTask } from './translate';
1414
import os from 'os';
1515

1616
const siteDir = options.project;
1717

18+
// Check for force translate via environment variable or command line
19+
const forceTranslate = process.env.FORCE_TRANSLATE === 'true' ||
20+
process.argv.includes('--force-retranslate') ||
21+
process.argv.includes('force');
22+
23+
if (forceTranslate) {
24+
console.log('Force mode enabled - retranslating all content regardless of changes\n');
25+
}
26+
1827
// Track if any translations were made across all locales
1928
let hasChanges = false;
2029

@@ -165,7 +174,7 @@ async function translateDocs(locale: string) {
165174
const normalizedContent = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
166175
const hash = md5(normalizedContent);
167176

168-
if (fs.existsSync(targetPath)) {
177+
if (!forceTranslate && fs.existsSync(targetPath)) {
169178
const targetContent = fs.readFileSync(targetPath, 'utf-8');
170179
const { data: targetFrontmatter } = matter(targetContent);
171180

@@ -200,22 +209,39 @@ async function translateDocs(locale: string) {
200209
});
201210
process.stdout.write('\n');
202211

203-
// Process results
212+
// Process results and translate titles
204213
let totalTokens = 0;
205214
for (const result of results) {
206215
if (result.file) {
207216
const fileInfo = fileInfoMap.get(result.file);
208217
if (fileInfo) {
209-
const targetFileContent = matter.stringify(result.translatedText, {
210-
...fileInfo.frontmatter,
211-
_i18n_hash: fileInfo.hash,
212-
});
218+
// Start with original frontmatter
219+
const updatedFrontmatter = { ...fileInfo.frontmatter };
220+
221+
// Translate the title if it exists
222+
if (fileInfo.frontmatter.title && typeof fileInfo.frontmatter.title === 'string') {
223+
try {
224+
const titleResult = await translateTitle(fileInfo.frontmatter.title, locale);
225+
updatedFrontmatter.title = titleResult.translatedText;
226+
if (titleResult.usage?.total_tokens) {
227+
totalTokens += titleResult.usage.total_tokens;
228+
}
229+
} catch (error) {
230+
console.warn(` Failed to translate title for ${result.file}: ${error}`);
231+
// Keep original title on error
232+
}
233+
}
234+
235+
// Add the hash
236+
updatedFrontmatter._i18n_hash = fileInfo.hash;
237+
238+
const targetFileContent = matter.stringify(result.translatedText, updatedFrontmatter);
213239

214240
await fs.ensureDir(fileInfo.targetDir);
215241
// Ensure LF line endings for consistency across platforms
216242
const normalizedContent = targetFileContent.replace(/\r\n/g, '\n');
217243
fs.writeFileSync(fileInfo.targetPath, normalizedContent);
218-
244+
219245
if (result.usage?.total_tokens) {
220246
totalTokens += result.usage.total_tokens;
221247
}
@@ -285,7 +311,7 @@ async function translateJSON(
285311
newHashes[key] = currentHash;
286312

287313
// Check if translation is needed
288-
if (savedHashes[key] === currentHash && targetJson[key]) {
314+
if (!forceTranslate && savedHashes[key] === currentHash && targetJson[key]) {
289315
// Hash matches and translation exists, skip
290316
continue;
291317
}
@@ -424,7 +450,7 @@ async function translateCodeJSON(locale: string) {
424450
newHashes[key] = currentHash;
425451

426452
// Check if translation is needed
427-
if (savedHashes[key] === currentHash && targetJson[key]) {
453+
if (!forceTranslate && savedHashes[key] === currentHash && targetJson[key]) {
428454
// Hash matches and translation exists, skip
429455
continue;
430456
}

docs/tools/i18n-translator/translate.ts

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,95 @@ import { ProxyAgent } from 'proxy-agent';
33
import { options } from './config';
44
import pLimit from 'p-limit';
55

6+
export async function translateTitle(title: string, targetLanguage: string) {
7+
const client = new OpenAI({
8+
baseURL: options.baseUrl,
9+
apiKey: options.apiKey,
10+
organization: options.organization,
11+
httpAgent: new ProxyAgent(),
12+
});
13+
14+
const titlePrompt = `You are translating ONLY a document title (from YAML front matter) to ${targetLanguage}.
15+
16+
🎯 YOUR ONLY JOB: Decide if this title should be translated, then either translate it or return it unchanged.
17+
18+
✅ TRANSLATE these types of titles (conceptual/navigational):
19+
• "Getting Started" → translate
20+
• "Overview" → translate
21+
• "Prerequisites" → translate
22+
• "Advanced Topics" → translate
23+
• "Configuration" → translate
24+
• "Architecture" → translate
25+
• "Security" → translate
26+
• "Routing" → translate
27+
• "Data Binding" → translate
28+
• "Validation" → translate
29+
• "Project Setup" → translate
30+
• "Creating a Basic App" → translate
31+
• "Working With Data" → translate
32+
• "Error Handling" → translate
33+
• "Debugging" → translate
34+
• "Modernization Tutorial" → translate
35+
• "Building UI" → translate
36+
• "Component Basics" → translate
37+
• "Composite Components" → translate
38+
• Any title containing: Guide, Tutorial, Setup, Installation, How to, Working with, Introduction
39+
40+
🚫 DO NOT TRANSLATE these types of titles (component/code names):
41+
• "Button" → return "Button" unchanged (it's a Java class)
42+
• "TextField" → return "TextField" unchanged (it's a Java class)
43+
• "AppLayout" → return "AppLayout" unchanged (it's a Java class)
44+
• "FlexLayout" → return "FlexLayout" unchanged (it's a Java class)
45+
• "CheckBox" → return "CheckBox" unchanged (it's a Java class)
46+
• "Dialog" → return "Dialog" unchanged (it's a Java class)
47+
• "Table" → return "Table" unchanged (it's a Java class)
48+
• "Toast" → return "Toast" unchanged (it's a Java class)
49+
• "<dwc-button>" → return "<dwc-button>" unchanged (it's a web component tag)
50+
• "<dwc-anything>" → return unchanged (all web component tags)
51+
• Any single-word or CamelCase title that looks like a Java class name
52+
• Any title wrapped in angle brackets < >
53+
54+
📋 RULES:
55+
1. Return ONLY the title text (translated or unchanged)
56+
2. NO explanations, NO extra text, NO quotes
57+
3. NO YAML formatting, NO "title:" prefix
58+
4. DO NOT include sidebar_position or any other front matter fields
59+
5. If it's a component name, return it EXACTLY as given
60+
6. If it's a conceptual term, translate it naturally
61+
7. When in doubt, look for these clues:
62+
- Multiple common English words (the, and, with, to) → probably translate
63+
- CamelCase single word → probably don't translate
64+
- Contains "Guide", "Tutorial", "Setup" → definitely translate
65+
66+
⚠️ CRITICAL: You are translating ONLY the title value. The system handles all front matter fields (sidebar_position, sidebar_class_name, etc.). Return ONLY the translated title text.
67+
68+
EXAMPLES:
69+
Input: "Getting Started" → Output: "Comenzando" (for Spanish)
70+
Input: "Button" → Output: "Button"
71+
Input: "Advanced Topics" → Output: "Temas Avanzados" (for Spanish)
72+
Input: "AppLayout" → Output: "AppLayout"
73+
Input: "<dwc-button>" → Output: "<dwc-button>"
74+
Input: "Working With Data" → Output: "Trabajando con Datos" (for Spanish)
75+
76+
Return ONLY the title (translated or unchanged), nothing else.`;
77+
78+
const chatCompletion = await client.chat.completions.create({
79+
messages: [
80+
{
81+
role: 'system',
82+
content: titlePrompt,
83+
},
84+
{ role: 'user', content: title },
85+
],
86+
model: options.model,
87+
});
88+
89+
return {
90+
translatedText: chatCompletion.choices[0].message.content?.trim() ?? title,
91+
usage: chatCompletion.usage,
92+
};
93+
}
94+
695
export async function translate(content: string, targetLanguage: string, isUIString: boolean = false) {
796
const client = new OpenAI({
897
baseURL: options.baseUrl,
@@ -11,12 +100,24 @@ export async function translate(content: string, targetLanguage: string, isUIStr
11100
httpAgent: new ProxyAgent(),
12101
});
13102

14-
const systemPrompt = isUIString
15-
? `You are a UI translator. Translate the following short UI text to ${targetLanguage}.
103+
const systemPrompt = isUIString
104+
? `You are a UI translator. Translate the following short UI text to ${targetLanguage}.
16105
Keep the translation concise and appropriate for UI elements like buttons, menu items, and labels.
17106
Do not translate technical terms or brand names like: webforJ, DWC, BASIS, startforJ, HueCraft, Blog, JavaDocs.
18107
Return ONLY the translated text, nothing else.`
19-
: `You are translating technical documentation to ${targetLanguage}.
108+
: `You are translating technical documentation to ${targetLanguage}.
109+
110+
⚠️ CRITICAL: FRONT MATTER HANDLING ⚠️
111+
• The document's YAML front matter (title, sidebar_position, sidebar_class_name, slug, etc.) has been REMOVED before being sent to you
112+
• You will ONLY receive the document body/content to translate
113+
• DO NOT add, create, or include ANY front matter in your response
114+
• DO NOT output --- markers or any YAML fields
115+
• DO NOT include title:, sidebar_position:, or any other front matter fields
116+
• Return ONLY the translated document body content
117+
• Front matter fields are handled separately by the system - DO NOT modify or include them
118+
119+
Example of what you receive: Just the markdown body without front matter
120+
Example of what you return: Just the translated markdown body without front matter
20121
21122
⚠️ CRITICAL FAILURE PREVENTION ⚠️
22123
If you translate ANY of the following, the build will FAIL:

0 commit comments

Comments
 (0)