Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions markdown-translator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
105 changes: 93 additions & 12 deletions markdown-translator/src/aiTranslatorZH.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 - 要计算的文本
Expand All @@ -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<string, string>} glossary - 词汇表
* @returns {Promise<string>} 翻译结果
*/
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;
Expand Down