translate.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /**
  2. * How to use it:
  3. * - cd apps/docs
  4. * - Add apps/docs/.env file, to set OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
  5. * - Use `git add .` to add all changed docs to git staged changes
  6. * - Run `npm run translate:zh` to translate all zh files to en
  7. */
  8. import * as process from 'process';
  9. import path from 'path';
  10. import fs from 'fs';
  11. import { execSync } from 'child_process';
  12. import { ChatCompletionMessageParam } from 'openai/resources/chat';
  13. import OpenAI from 'openai';
  14. // eslint-disable-next-line import/no-extraneous-dependencies
  15. import 'dotenv/config';
  16. const languages = ['zh', 'en'];
  17. const __dirname = path.dirname(new URL(import.meta.url).pathname);
  18. const ai = new OpenAI({
  19. apiKey: process.env.OPENAI_API_KEY,
  20. baseURL: process.env.OPENAI_BASE_URL,
  21. });
  22. const model = process.env.OPENAI_MODEL!;
  23. const sourceLang = process.argv[2];
  24. if (!languages.includes(sourceLang)) {
  25. console.error(`Invalid language: ${sourceLang}`);
  26. process.exit(1);
  27. }
  28. const targetLangs = languages.filter((_lang) => _lang !== sourceLang);
  29. async function translateContent(
  30. content: string,
  31. targetLang: string,
  32. previousTargetContent?: string
  33. ) {
  34. let systemPrompts = `
  35. You are a translator.
  36. You will translate the following content from ${sourceLang} to ${targetLang}.\n
  37. `;
  38. console.log('previousTargetContent', previousTargetContent);
  39. // if (previousTargetContent) {
  40. // systemPrompts += `
  41. // The target file is translated previously, here is the content:
  42. // ${previousTargetContent}
  43. // Only translate the content that is different in ${sourceLang}.\n
  44. // `;
  45. // }
  46. systemPrompts += `
  47. Constraint:
  48. - ONLY RETURN THE TRANSLATED CONTENT, NO OTHER TEXT.
  49. `;
  50. const messages: ChatCompletionMessageParam[] = [
  51. {
  52. role: 'system',
  53. content: systemPrompts,
  54. },
  55. { role: 'user', content },
  56. ];
  57. const response = await ai.chat.completions.create({
  58. model,
  59. messages,
  60. });
  61. return response.choices[0].message.content ?? '';
  62. }
  63. async function translateFiles() {
  64. // 1. Get Stage Changed Documentations for source language
  65. const gitDiffOutput = execSync('git diff --cached --name-only', {
  66. encoding: 'utf-8',
  67. });
  68. const allChangedFiles: string[] = gitDiffOutput.split('\n').filter(Boolean);
  69. console.log('Get Diff files:', allChangedFiles);
  70. const sourceLangFolder = path.join(__dirname, '../src', sourceLang);
  71. const sourceLangFolderAbs = path.join('apps/docs/src', sourceLang);
  72. // Find all *.md, *.mdx files in sourceLangFolder
  73. const diffMarkdownFiles: string[] = allChangedFiles
  74. .filter(
  75. (file) =>
  76. file.includes(sourceLangFolderAbs) && (file.endsWith('.md') || file.endsWith('.mdx'))
  77. )
  78. .map((file) => path.relative(sourceLangFolderAbs, file));
  79. console.log('Files to be translated:', diffMarkdownFiles);
  80. // 2. For each file, translate it to other language
  81. await Promise.all(
  82. diffMarkdownFiles.map(async (file) => {
  83. for (const targetLang of targetLangs) {
  84. const targetLangFolder = path.join(__dirname, '../src', targetLang);
  85. const sourceFile = path.join(sourceLangFolder, file);
  86. const targetFile = path.join(targetLangFolder, file);
  87. if (!fs.existsSync(sourceFile)) {
  88. console.error(`Source file not found: ${sourceFile}`);
  89. continue;
  90. }
  91. // 2.1. Read the file
  92. const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
  93. console.log(`Translate ${sourceFile} to ${targetFile}`);
  94. console.log(sourceContent);
  95. console.log('\n\n\n\n\n');
  96. const previousTargetContent = fs.existsSync(targetFile)
  97. ? fs.readFileSync(targetFile, 'utf-8')
  98. : undefined;
  99. // 2.2. Translate the content
  100. const targetContent = await translateContent(
  101. sourceContent,
  102. targetLang,
  103. previousTargetContent
  104. );
  105. // 2.3. Write the translated file
  106. if (!fs.existsSync(path.dirname(targetFile))) {
  107. fs.mkdirSync(path.dirname(targetFile), { recursive: true });
  108. }
  109. fs.writeFileSync(targetFile, targetContent);
  110. console.log(`Translated: ${targetFile}`);
  111. console.log(targetContent);
  112. console.log('\n\n\n\n\n');
  113. }
  114. })
  115. );
  116. }
  117. translateFiles().catch((err) => {
  118. console.error('Error generating docs:', err);
  119. });