flowgram.ai/apps/docs/scripts/translate.ts
2025-05-23 06:55:34 +00:00

150 lines
4.2 KiB
TypeScript

/**
* How to use it:
* - cd apps/docs
* - Add apps/docs/.env file, to set OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
* - Use `git add .` to add all changed docs to git staged changes
* - Run `npm run translate:zh` to translate all zh files to en
*/
import * as process from 'process';
import path from 'path';
import fs from 'fs';
import { execSync } from 'child_process';
import { ChatCompletionMessageParam } from 'openai/resources/chat';
import OpenAI from 'openai';
// eslint-disable-next-line import/no-extraneous-dependencies
import 'dotenv/config';
const languages = ['zh', 'en'];
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const ai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
const model = process.env.OPENAI_MODEL!;
const sourceLang = process.argv[2];
if (!languages.includes(sourceLang)) {
console.error(`Invalid language: ${sourceLang}`);
process.exit(1);
}
const targetLangs = languages.filter((_lang) => _lang !== sourceLang);
async function translateContent(
content: string,
targetLang: string,
previousTargetContent?: string
) {
let systemPrompts = `
You are a translator.
You will translate the following content from ${sourceLang} to ${targetLang}.\n
`;
console.log('previousTargetContent', previousTargetContent);
// if (previousTargetContent) {
// systemPrompts += `
// The target file is translated previously, here is the content:
// ${previousTargetContent}
// Only translate the content that is different in ${sourceLang}.\n
// `;
// }
systemPrompts += `
Constraint:
- ONLY RETURN THE TRANSLATED CONTENT, NO OTHER TEXT.
`;
const messages: ChatCompletionMessageParam[] = [
{
role: 'system',
content: systemPrompts,
},
{ role: 'user', content },
];
const response = await ai.chat.completions.create({
model,
messages,
});
return response.choices[0].message.content ?? '';
}
async function translateFiles() {
// 1. Get Stage Changed Documentations for source language
const gitDiffOutput = execSync('git diff --cached --name-only', {
encoding: 'utf-8',
});
const allChangedFiles: string[] = gitDiffOutput.split('\n').filter(Boolean);
console.log('Get Diff files:', allChangedFiles);
const sourceLangFolder = path.join(__dirname, '../src', sourceLang);
const sourceLangFolderAbs = path.join('apps/docs/src', sourceLang);
// Find all *.md, *.mdx files in sourceLangFolder
const diffMarkdownFiles: string[] = allChangedFiles
.filter(
(file) =>
file.includes(sourceLangFolderAbs) && (file.endsWith('.md') || file.endsWith('.mdx'))
)
.map((file) => path.relative(sourceLangFolderAbs, file));
console.log('Files to be translated:', diffMarkdownFiles);
// 2. For each file, translate it to other language
await Promise.all(
diffMarkdownFiles.map(async (file) => {
for (const targetLang of targetLangs) {
const targetLangFolder = path.join(__dirname, '../src', targetLang);
const sourceFile = path.join(sourceLangFolder, file);
const targetFile = path.join(targetLangFolder, file);
if (!fs.existsSync(sourceFile)) {
console.error(`Source file not found: ${sourceFile}`);
continue;
}
// 2.1. Read the file
const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
console.log(`Translate ${sourceFile} to ${targetFile}`);
console.log(sourceContent);
console.log('\n\n\n\n\n');
const previousTargetContent = fs.existsSync(targetFile)
? fs.readFileSync(targetFile, 'utf-8')
: undefined;
// 2.2. Translate the content
const targetContent = await translateContent(
sourceContent,
targetLang,
previousTargetContent
);
// 2.3. Write the translated file
if (!fs.existsSync(path.dirname(targetFile))) {
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
}
fs.writeFileSync(targetFile, targetContent);
console.log(`Translated: ${targetFile}`);
console.log(targetContent);
console.log('\n\n\n\n\n');
}
})
);
}
translateFiles().catch((err) => {
console.error('Error generating docs:', err);
});