diff --git a/markdown-translator/README.md b/markdown-translator/README.md index 1ce3f29..a5cc625 100644 --- a/markdown-translator/README.md +++ b/markdown-translator/README.md @@ -7,4 +7,8 @@ export \ GOOGLE_APPLICATION_CREDENTIALS=key.json export PROJECT_ID=PROJECT_ID export GLOSSARY_ID=GLOSSARY_ID +export OPENAI_RESPONSES_API_URL=https://YOUR_PROVIDER_URL +export OPENAI_API_KEY=YOUR_API_KEY +# optional, defaults to gpt-5.4 +export OPENAI_MODEL=gpt-5.4 ``` diff --git a/markdown-translator/src/aiTranslatorZH.js b/markdown-translator/src/aiTranslatorZH.js index 55207e8..e945c91 100644 --- a/markdown-translator/src/aiTranslatorZH.js +++ b/markdown-translator/src/aiTranslatorZH.js @@ -1,12 +1,8 @@ import * as fs from "fs"; import { get_encoding } from "tiktoken"; import { writeFileSync } from "./lib.js"; -import { executeLangLinkTranslator } from "./langlinkClient.js"; import { gcpTranslator } from "./gcpTranslator.js"; -// LangLink 配置 -const LANGLINK_APP_ID = "d57cc1a9-2b2a-45c7-9119-ac798285b2ab"; - // Token 限制配置 const OUTPUT_TOKEN_LIMIT = 60000; const TIKTOKEN_ENCODING = "cl100k_base"; @@ -15,6 +11,12 @@ const TIKTOKEN_ENCODING = "cl100k_base"; const TOKEN_RATIO = 1.8; const INPUT_TOKEN_LIMIT = Math.floor(OUTPUT_TOKEN_LIMIT / TOKEN_RATIO); +const getOpenAIConfig = () => ({ + apiUrl: process.env.OPENAI_RESPONSES_API_URL, + apiKey: process.env.OPENAI_API_KEY, + model: process.env.OPENAI_MODEL || "gpt-5.4", +}); + /** * 计算文本的token数量 * @param {string} text - 要计算的文本 @@ -28,20 +30,99 @@ const countTokens = (text) => { return count; }; +const buildTranslationPrompt = (content, glossary) => `You are a professional technical document translator, specializing in translating English technical documents into accurate and professional Chinese. + +Please translate the following English technical document into Chinese, preserving the original Markdown format and structure: + +English Content: +${content} + +Please follow these requirements strictly: + +Markdown and Code Preservation Rules: +- Absolutely preserve all Markdown structures (headings, lists, tables, links, emphasis, etc.). +- Preserve ALL code blocks exactly as-is: + - Do not modify code. + - Do not summarize, shorten, or replace it with comments like “代码保持不变”. + - Do not add or remove any characters inside code blocks. + - Treat everything between triple backticks \`\`\` as literal text to copy verbatim. +- Keep all filenames, paths, SQL, Java, Go, JSON, YAML, and shell commands unchanged. + +Translation Rules: +- Use precise, professional Chinese technical terminology. +- Maintain logical flow and readability. +- Keep all link URLs unchanged. +- Do not translate text wrapped in bold syntax. +- Translate “you” as “你”, not “您”. +- Insert spaces between Chinese and English text, and between Chinese text and Arabic numerals. +- Translate only the natural-language content. Do not add explanations or extra text. + +Strictly forbidden: +- Do not omit code blocks. +- Do not replace code with placeholders. +- Do not rewrite or format code. +- Do not hallucinate missing content. +- Do not simplify long code samples. + +Glossary Rules: +If you find any of the following keys in the text, do not translate them; simply replace the matching key with the corresponding value: +${JSON.stringify(glossary, null, 2)}`; + +const extractResponseText = (responseBody) => { + if (typeof responseBody.output_text === "string" && responseBody.output_text) { + return responseBody.output_text; + } + + if (!Array.isArray(responseBody.output)) { + throw new Error(`Unexpected OpenAI response shape: ${JSON.stringify(responseBody)}`); + } + + const text = responseBody.output + .flatMap((item) => item.content || []) + .filter((item) => item.type === "output_text") + .map((item) => item.text || "") + .join(""); + + if (!text) { + throw new Error(`OpenAI response does not contain output text: ${JSON.stringify(responseBody)}`); + } + + return text; +}; + /** - * 使用LangLink进行翻译 + * 使用 OpenAI Responses API 进行翻译 * @param {string} content - 要翻译的内容 - * @param {Array} glossary - 词汇表 + * @param {Record} glossary - 词汇表 * @returns {Promise} 翻译结果 */ const translateWithLangLink = async (content, glossary) => { + const { apiUrl, apiKey, model } = getOpenAIConfig(); + + if (!apiUrl || !apiKey || !model) { + throw new Error("Missing required env vars: OPENAI_RESPONSES_API_URL, OPENAI_API_KEY"); + } + try { - const result = await executeLangLinkTranslator( - LANGLINK_APP_ID, - content, - glossary - ); - return result; + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "api-key": apiKey, + }, + body: JSON.stringify({ + model, + input: buildTranslationPrompt(content, glossary), + max_output_tokens: OUTPUT_TOKEN_LIMIT, + }), + }); + + const responseBody = await response.json(); + if (!response.ok) { + throw new Error(JSON.stringify(responseBody)); + } + + return extractResponseText(responseBody); } catch (error) { console.error("Translation error:", error); throw error;