import fs from 'node:fs' import path from 'node:path' import process from 'node:process' import cpy from 'cpy' import dotenv from 'dotenv' import { glob } from 'glob' import { removeDir } from './utils.js' dotenv.config() // 配置常量 const MAX_RETRIES = 3 const RETRY_DELAY = 1000 // 1秒 const MAX_CHARS_PER_REQUEST = 128000 const BASE_PATH = 'packages/artplayer-vitepress/docs' const EN_PATH = 'packages/artplayer-vitepress/docs/en' const COPY_DIRS = ['advanced', 'component', 'plugin', 'start'] function splitMarkdown(text, maxChars) { const parts = [] let currentPart = '' let inCodeBlock = false let inSpecialBlock = false const lines = text.split('\n') for (let i = 0; i < lines.length; i++) { const line = lines[i] // 检测代码块 if (line.trim().startsWith('```')) { inCodeBlock = !inCodeBlock } // 检测特殊块 if (line.trim().startsWith(':::')) { inSpecialBlock = !inSpecialBlock } // 添加当前行到当前部分 currentPart += `${line}\n` // 检测是否达到字符限制 if (!inCodeBlock && !inSpecialBlock && currentPart.length > maxChars && i < lines.length - 1) { parts.push(currentPart) currentPart = '' } } // 添加最后一部分 if (currentPart !== '') { parts.push(currentPart) } return parts } async function fetchWithRetry(url, options, retries = MAX_RETRIES) { try { const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } return await response.json() } catch (error) { if (retries > 0) { console.log(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`) await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)) return fetchWithRetry(url, options, retries - 1) } throw error } } async function translateContent(content, targetLanguage) { try { const TRANSLATE_URL = 'https://api.deepseek.com/chat/completions' const response = await fetchWithRetry(TRANSLATE_URL, { headers: { Authorization: `Bearer ${process.env.DEEPL_API_KEY}`, }, method: 'POST', body: JSON.stringify({ stream: false, model: 'deepseek-chat', messages: [ { role: 'user', content: `The following text is written in VitePress's extended Markdown syntax, Please translate it into ${targetLanguage}, and keep the Markdown format and don't add your explanation:\n\n${content}`, }, ], }), }) return response.choices[0].message.content } catch (error) { console.error(`Translation failed: ${error.message}`) return content // 返回原文作为回退 } } class ProgressTracker { constructor(totalParts) { this.totalParts = totalParts this.translatedPartsCount = 0 } update() { this.translatedPartsCount++ const progressPercentage = ((this.translatedPartsCount / this.totalParts) * 100).toFixed(2) console.log(`Overall progress: ${progressPercentage}% (${this.translatedPartsCount}/${this.totalParts})`) } } async function translateMarkdownFiles(filePaths, targetLanguage, maxCharsPerRequest) { // 先计算总共有多少部分需要翻译 let totalParts = 0 for (const filePath of filePaths) { const content = fs.readFileSync(filePath, 'utf-8') totalParts += splitMarkdown(content, maxCharsPerRequest).length } console.log(`Starting translation of ${filePaths.length} files, total ${totalParts} parts.`) const progressTracker = new ProgressTracker(totalParts) for (const filePath of filePaths) { console.log(`Translating: ${path.relative(EN_PATH, filePath)}`) const content = fs.readFileSync(filePath, 'utf-8') const parts = splitMarkdown(content, maxCharsPerRequest) const translatedParts = [] for (const part of parts) { const translatedPart = await translateContent(part, targetLanguage) translatedParts.push(translatedPart) progressTracker.update() } const translatedContent = translatedParts.join('\n') fs.writeFileSync(filePath, translatedContent) } } async function setupDirectoryStructure() { // 清理并创建目录结构 removeDir(EN_PATH) await cpy(path.resolve(BASE_PATH, 'index.md'), path.resolve(EN_PATH)) // 并行复制所有目录 await Promise.all( COPY_DIRS.map(dir => cpy(path.resolve(BASE_PATH, dir), path.resolve(EN_PATH, dir), { flat: true })), ) } (async () => { try { await setupDirectoryStructure() const mdsPaths = glob.sync(path.resolve(EN_PATH, '**/*.md')) await translateMarkdownFiles(mdsPaths, 'English', MAX_CHARS_PER_REQUEST) console.log('Translation completed successfully!') } catch (error) { console.error('Translation failed:', error) process.exit(1) } })()