Skip to content

Commit d03c20a

Browse files
authored
Release (#1577)
1 parent 2aa652f commit d03c20a

File tree

3 files changed

+176
-43
lines changed

3 files changed

+176
-43
lines changed

.github/workflows/build-docusaurus.yml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,4 @@ jobs:
4141
name: build
4242
path: docusaurus/build.tar
4343
retention-days: 90
44-
- name: Update i18n files
45-
if: ${{ !github.event.pull_request.head.repo.fork }}
46-
run: |
47-
cd docusaurus
48-
npm run write-translations -- --locale de
49-
npm run write-translations -- --locale es
50-
npm run write-translations -- --locale fr
51-
npm run write-translations -- --locale ar
52-
npm run write-translations -- --locale pt
53-
npm run write-translations -- --locale th
54-
npm run write-translations -- --locale pl
55-
npm run write-translations -- --locale ja
56-
57-
git config user.name github-actions
58-
git config user.email github-actions@github.com
59-
git add --all .
60-
git diff --exit-code && git commit -am "update i18n" || true
61-
git push
6244

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,68 @@
11
name: PR Build Docusaurus
2-
3-
on:
2+
on:
43
pull_request:
5-
branches: ["master", "main"]
4+
types: [labeled]
5+
paths:
6+
- 'docs/**/*.md'
67

78
permissions:
89
checks: write
9-
contents: write
10+
contents: write
1011

1112
jobs:
1213
build-docusaurus:
14+
if: github.event.label.name == 'BUILD PERMISSION'
15+
timeout-minutes: 1440
1316
runs-on: self-hosted
1417
steps:
15-
- name: Check out Git repository
18+
- name: Checkout PR branch
1619
uses: actions/checkout@v4
1720
with:
18-
path: 'docusaurus'
21+
ref: ${{ github.head_ref }}
1922
fetch-depth: 0
20-
ref: ${{github.event.pull_request.head.ref}}
21-
repository: ${{github.event.pull_request.head.repo.full_name}}
22-
- name: Set up Node.js
23+
24+
- name: Setup Node.js
2325
uses: actions/setup-node@v4
2426
with:
25-
node-version: 20
26-
- name: Install Docusaurus
27+
node-version: '20'
28+
29+
- name: Install dependencies
30+
run: npm install
31+
working-directory: ${{ github.workspace }}
32+
33+
- name: Translation process
34+
working-directory: ${{ github.workspace }}
35+
env:
36+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
37+
TRANSLATION_PROMPT: ${{ secrets.TRANSLATION_PROMPT }}
38+
TRANSLATION_GLOSSARY: ${{ secrets.TRANSLATION_GLOSSARY }}
39+
run: node translate.js
40+
- name: Commit translations
2741
run: |
28-
cd docusaurus
29-
npm install
42+
git config --local user.name "github-actions"
43+
git config --local user.email "github-actions@github.com"
44+
git add -A i18n/
45+
if ! git diff --cached --quiet; then
46+
git commit -m "Auto-translate docs"
47+
git push origin HEAD:${{ github.head_ref }}
48+
else
49+
echo "No translation changes to commit"
50+
fi
51+
3052
- name: Build Docusaurus
3153
run: |
32-
cd docusaurus
54+
npm run write-translations -- --locale de
55+
npm run write-translations -- --locale es
56+
npm run write-translations -- --locale fr
57+
npm run write-translations -- --locale ar
58+
npm run write-translations -- --locale pt
59+
npm run write-translations -- --locale th
60+
npm run write-translations -- --locale pl
61+
npm run write-translations -- --locale ja
62+
3363
rm -rf build
3464
npm run docusaurus clear
3565
rm -f build.tar
3666
npm run build
3767
tar cf build.tar -C build --transform 's~^\./~~' .
38-
- name: Update i18n files
39-
if: ${{ !github.event.pull_request.head.repo.fork }}
40-
run: |
41-
cd docusaurus
42-
npm run write-translations -- --locale de
43-
git config user.name github-actions
44-
git config user.email github-actions@github.com
45-
git add --all .
46-
git diff --exit-code && git commit -am "update i18n" || true
47-
git push
48-
68+
working-directory: ${{ github.workspace }}

translate.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { execSync } = require('child_process');
4+
const { OpenAI } = require('openai');
5+
6+
const openai = new OpenAI({
7+
apiKey: process.env.OPENAI_API_KEY
8+
});
9+
10+
const SOURCE_DIR = 'docs';
11+
const TARGET_LANGUAGES = ['de', 'fr', 'es', 'ar', 'pt', 'th', 'pl', 'ja'];
12+
13+
let totalPromptTokens = 0;
14+
let totalCompletionTokens = 0;
15+
let totalTokens = 0;
16+
17+
function buildGlossary(targetLang) {
18+
const glossaryRaw = process.env.TRANSLATION_GLOSSARY;
19+
if (!glossaryRaw) return "";
20+
21+
try {
22+
const glossaryObj = JSON.parse(glossaryRaw);
23+
if (!glossaryObj[targetLang]) return "";
24+
25+
const entries = Object.entries(glossaryObj[targetLang])
26+
.map(([src, tgt]) => `- Translate "${src}" as "${tgt}"`)
27+
.join("\n");
28+
29+
return `\n\nGlossary rules for ${targetLang}:\n${entries}`;
30+
} catch (err) {
31+
console.error("Glossary parsing failed:", err);
32+
return "";
33+
}
34+
}
35+
36+
function buildSystemPrompt(targetLang) {
37+
const fromSecret = process.env.TRANSLATION_PROMPT;
38+
39+
if (!fromSecret || !fromSecret.trim()) {
40+
throw new Error("TRANSLATION_PROMPT secret is missing or empty!");
41+
}
42+
return fromSecret.replaceAll("${targetLang}", targetLang) + buildGlossary(targetLang);
43+
}
44+
45+
function getChangedMarkdownFiles() {
46+
try {
47+
const output = execSync('git diff --name-only HEAD~1 HEAD', { encoding: 'utf8' });
48+
return output
49+
.split('\n')
50+
.filter(file => file.startsWith(SOURCE_DIR) && file.endsWith('.md') && fs.existsSync(file));
51+
} catch (error) {
52+
return getAllMarkdownFiles();
53+
}
54+
}
55+
56+
function getAllMarkdownFiles() {
57+
const output = execSync(
58+
`find ${SOURCE_DIR} -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*"`,
59+
{ encoding: 'utf8' }
60+
);
61+
return output.split('\n').filter(file => file.trim() !== '');
62+
}
63+
64+
async function translateContent(content, targetLang) {
65+
const systemPrompt = buildSystemPrompt(targetLang);
66+
67+
// console.log(`\n--- DEBUG: Prompt for ${targetLang} ---`);
68+
// console.log(systemPrompt);
69+
70+
const response = await openai.chat.completions.create({
71+
model: 'gpt-4.1-mini',
72+
messages: [
73+
{
74+
role: 'system',
75+
content: systemPrompt
76+
},
77+
{
78+
role: 'user',
79+
content: content
80+
}
81+
],
82+
temperature: 0.1
83+
});
84+
85+
if (response.usage) {
86+
totalPromptTokens += response.usage.prompt_tokens || 0;
87+
totalCompletionTokens += response.usage.completion_tokens || 0;
88+
totalTokens += response.usage.total_tokens || 0;
89+
}
90+
91+
return response.choices[0].message.content;
92+
}
93+
94+
async function main() {
95+
const changedFiles = getChangedMarkdownFiles();
96+
console.log(`Found ${changedFiles.length} changed markdown files`);
97+
98+
for (const file of changedFiles) {
99+
const content = fs.readFileSync(file, 'utf8');
100+
const relPath = path.relative(SOURCE_DIR, file);
101+
102+
for (const lang of TARGET_LANGUAGES) {
103+
console.log(`Translating ${file} to ${lang}...`);
104+
105+
try {
106+
const translatedContent = await translateContent(content, lang);
107+
108+
const outputFile = path.join(
109+
'i18n',
110+
lang,
111+
'docusaurus-plugin-content-docs/current',
112+
relPath
113+
);
114+
115+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
116+
fs.writeFileSync(outputFile, translatedContent);
117+
118+
await new Promise(resolve => setTimeout(resolve, 1000));
119+
} catch (error) {
120+
console.error(`Error translating ${file} to ${lang}:`, error);
121+
}
122+
}
123+
}
124+
125+
console.log('\n--- Translation Token Usage ---');
126+
console.log(`Prompt tokens: ${totalPromptTokens}`);
127+
console.log(`Completion tokens: ${totalCompletionTokens}`);
128+
console.log(`Total tokens: ${totalTokens}`);
129+
}
130+
131+
main().catch(console.error);

0 commit comments

Comments
 (0)