translate.ts 4.3 KB

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