想让AI翻译既准确又地道?本文将教你如何利用 FastGPT 打造一个革命性的翻译工作流。
它不仅支持文本翻译,还能直接处理文档,更能通过自定义术语表确保专业术语的翻译准确性,堪称翻译神器!
直接看效果:
再来看术语表:
我只能说:
这也太适合翻译产品官网和官方文档了吧??
背景
吴恩达教授最近提出了一个创新的大语言模型(LLM)翻译方案 —— translation-agent[1],这个方案的独特之处在于引入了"自我反思"机制,具体工作流程如下:
LLM 将原文从源语言翻译为目标语言;
LLM 对翻译结果进行自我反思,提出改进建议;
根据这些建议优化翻译结果。
这个 AI 翻译流程是目前比较新的一种翻译方式,利用 LLM 对自己的翻译结果进行改进来获得较好的 AI 翻译效果。
项目中展示了可以利用对长文本进行分片,然后分别进行反思翻译处理,以突破 LLM 对 tokens 数量的限制,真正实现长文本一键高效率高质量翻译。
该项目还通过给大模型限定国家地区,已实现更精确的 AI 翻译,如美式英语、英式英语之分;同时提出一些可能能带来更好效果的优化,如对于一些 LLM 未曾训练到的术语 (或有多种翻译方式的术语) 建立术语表,进一步提升翻译的精确度等等。
而这一切都能通过 FastGPT[2] 工作流轻松实现,本文将手把手教你如何使用 FastGPT 复刻吴恩达老师的 translation-agent。
而且 FastGPT 的工作流还更进一步,既可以直接输入文本进行翻译,也可以上传文档进行翻译。
话不多说,拿起键盘,开始教学 ~
整个工作流大概分为三个板块:
判断是否上传了文档
对文本进行切分,使切分出来的文本不超出 LLM tokens 数量限制
依次按顺序对每个切分出来的文本进行翻译
FastGPT 国内版地址:https://fastgpt.cn
FastGPT 海外版地址:https://tryfastgpt.ai
判断是否上传文档
首先第一步,在【系统配置】中添加两个全局变量用来表示源语言和目标语言,同时需要开启文件上传功能。
接下来进入正式的工作流流程,先判断一下源语言与目标语言是否相同,如果是一样的,那就没必要翻译了!你让我把中文翻译成中文?开什么玩笑,Token 不要钱的啦?
如果源语言和目标语言不同,就继续往下走,判断是否上传了文档,如果上传了文档,就将文档里的内容解析出来。
OK,进入下一个流程。
切分长文本
接下来我们可以通过分片和循环,实现对长文本也即多文本块的反思翻译。
整体的逻辑是,首先对传入文本的 tokens 数量做判断,如果不超过设置的 tokens 限制,那么直接调用单文本块反思翻译,如果超过设置的 tokens 限制,那么切割为合理的大小,再分别进行对应的反思翻译处理。
至于为什么要切割分块,有两个原因:
大模型输出上下文只有 4k,无法输出超过 4k token 内容的文字。
输入分块可以减少太长的输入导致的幻觉。
下面我们接着看工作流。
不管是否上传了文档,最终都要把文本提取出来。如果直接输入了文本,那么直接将文本传递给下一个节点;如果上传了文档,那么解析完文档之后再将解析出来的文本传递给下一个节点。
下一个节点要干的事情就是对文本进行切分。
这里我们用到了 Jina AI 开源的一个强大的正则表达式,它能利用所有可能的边界线索和启发式方法来精确切分文本,来看看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const MAX_HEADING_LENGTH = 7; // 最大标题长度
const MAX_HEADING_CONTENT_LENGTH = 200; // 最大标题内容长度
const MAX_HEADING_UNDERLINE_LENGTH = 200; // 最大标题下划线长度
const MAX_HTML_HEADING_ATTRIBUTES_LENGTH = 100; // 最大HTML标题属性长度
const MAX_LIST_ITEM_LENGTH = 200; // 最大列表项长度
const MAX_NESTED_LIST_ITEMS = 6; // 最大嵌套列表项数
const MAX_LIST_INDENT_SPACES = 7; // 最大列表缩进空格数
const MAX_BLOCKQUOTE_LINE_LENGTH = 200; // 最大块引用行长度
const MAX_BLOCKQUOTE_LINES = 15; // 最大块引用行数
const MAX_CODE_BLOCK_LENGTH = 1500; // 最大代码块长度
const MAX_CODE_LANGUAGE_LENGTH = 20; // 最大代码语言长度
const MAX_INDENTED_CODE_LINES = 20; // 最大缩进代码行数
const MAX_TABLE_CELL_LENGTH = 200; // 最大表格单元格长度
const MAX_TABLE_ROWS = 20; // 最大表格行数
const MAX_HTML_TABLE_LENGTH = 2000; // 最大HTML表格长度
const MIN_HORIZONTAL_RULE_LENGTH = 3; // 最小水平分隔线长度
const MAX_SENTENCE_LENGTH = 400; // 最大句子长度
const MAX_QUOTED_TEXT_LENGTH = 300; // 最大引用文本长度
const MAX_PARENTHETICAL_CONTENT_LENGTH = 200; // 最大括号内容长度
const MAX_NESTED_PARENTHESES = 5; // 最大嵌套括号数
const MAX_MATH_INLINE_LENGTH = 100; // 最大行内数学公式长度
const MAX_MATH_BLOCK_LENGTH = 500; // 最大数学公式块长度
const MAX_PARAGRAPH_LENGTH = 1000; // 最大段落长度
const MAX_STANDALONE_LINE_LENGTH = 800; // 最大独立行长度
const MAX_HTML_TAG_ATTRIBUTES_LENGTH = 100; // 最大HTML标签属性长度
const MAX_HTML_TAG_CONTENT_LENGTH = 1000; // 最大HTML标签内容长度
const LOOKAHEAD_RANGE = 100;// 向前查找句子边界的字符数
const AVOID_AT_START = `[\\s\\]})>,']`; // 避免在开头匹配的字符
const PUNCTUATION = `[.!?…]|\\.{3}|[\\u2026\\u2047-\\u2049]|[\\p{Emoji_Presentation}\\p{Extended_Pictographic}]`; // 标点符号
const QUOTE_END = `(?:'(?=\`)|''(?=\`\`))`; // 引号结束
const SENTENCE_END = `(?:${PUNCTUATION}(?<!${AVOID_AT_START}(?=${PUNCTUATION}))|${QUOTE_END})(?=\\S|$)`; // 句子结束
const SENTENCE_BOUNDARY = `(?:${SENTENCE_END}|(?=[\\r\\n]|$))`; // 句子边界
const LOOKAHEAD_PATTERN = `(?:(?!${SENTENCE_END}).){1,${LOOKAHEAD_RANGE}}${SENTENCE_END}`; // 向前查找句子结束的模式
const NOT_PUNCTUATION_SPACE = `(?!${PUNCTUATION}\\s)`; // 非标点符号空格
const SENTENCE_PATTERN = `${NOT_PUNCTUATION_SPACE}(?:[^\\r\\n]{1,{MAX_LENGTH}}${SENTENCE_BOUNDARY}|[^\\r\\n]{1,{MAX_LENGTH}}(?=${PUNCTUATION}|${QUOTE_END})(?:${LOOKAHEAD_PATTERN})?)${AVOID_AT_START}*`; // 句子模式
const regex = new RegExp(
"(" +
// 1. Headings (Setext-style, Markdown, and HTML-style, with length constraints)
`(?:^(?:[#*=-]{1,${MAX_HEADING_LENGTH}}|\\w[^\\r\\n]{0,${MAX_HEADING_CONTENT_LENGTH}}\\r?\\n[-=]{2,${MAX_HEADING_UNDERLINE_LENGTH}}|<h[1-6][^>]{0,${MAX_HTML_HEADING_ATTRIBUTES_LENGTH}}>)[^\\r\\n]{1,${MAX_HEADING_CONTENT_LENGTH}}(?:</h[1-6]>)?(?:\\r?\\n|$))` +
"|" +
// New pattern for citations
`(?:\\[[0-9]+\\][^\\r\\n]{1,${MAX_STANDALONE_LINE_LENGTH}})` +
"|" +
// 2. List items (bulleted, numbered, lettered, or task lists, including nested, up to three levels, with length constraints)
`(?:(?:^|\\r?\\n)[ \\t]{0,3}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}` +
`(?:(?:\\r?\\n[ \\t]{2,5}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}}` +
`(?:\\r?\\n[ \\t]{4,${MAX_LIST_INDENT_SPACES}}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}})?)` +
"|" +
// 3. Block quotes (including nested quotes and citations, up to three levels, with length constraints)
`(?:(?:^>(?:>|\\s{2,}){0,2}${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_BLOCKQUOTE_LINE_LENGTH))}\\r?\\n?){1,${MAX_BLOCKQUOTE_LINES}})` +
"|" +
// 4. Code blocks (fenced, indented, or HTML pre/code tags, with length constraints)
`(?:(?:^|\\r?\\n)(?:\`\`\`|~~~)(?:\\w{0,${MAX_CODE_LANGUAGE_LENGTH}})?\\r?\\n[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:\`\`\`|~~~)\\r?\\n?` +
`|(?:(?:^|\\r?\\n)(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}(?:\\r?\\n(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}){0,${MAX_INDENTED_CODE_LINES}}\\r?\\n?)` +
`|(?:<pre>(?:<code>)?[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:</code>)?</pre>))` +
"|" +
// 5. Tables (Markdown, grid tables, and HTML tables, with length constraints)
`(?:(?:^|\\r?\\n)(?:\\|[^\\r\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\|(?:\\r?\\n\\|[-:]{1,${MAX_TABLE_CELL_LENGTH}}\\|){0,1}(?:\\r?\\n\\|[^\\r\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\|){0,${MAX_TABLE_ROWS}}` +
`|<table>[\\s\\S]{0,${MAX_HTML_TABLE_LENGTH}}?</table>))` +
"|" +
// 6. Horizontal rules (Markdown and HTML hr tag)
`(?:^(?:[-*_]){${MIN_HORIZONTAL_RULE_LENGTH},}\\s*$|<hr\\s*/?>)` +
"|" +
// 10. Standalone lines or phrases (including single-line blocks and HTML elements, with length constraints)
`(?!${AVOID_AT_START})(?:^(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}(?:</[a-zA-Z]+>)?(?:\\r?\\n|$))` +
"|" +
// 7. Sentences or phrases ending with punctuation (including ellipsis and Unicode punctuation)
`(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_SENTENCE_LENGTH))}` +
"|" +
// 8. Quoted text, parenthetical phrases, or bracketed content (with length constraints)
"(?:" +
`(?<!\\w)\"\"\"[^\"]{0,${MAX_QUOTED_TEXT_LENGTH}}\"\"\"(?!\\w)` +
`|(?<!\\w)(?:['\"\`'"])[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}\\1(?!\\w)` +
`|(?<!\\w)\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}'(?!\\w)` +
`|(?<!\\w)\`\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}''(?!\\w)` +
`|\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\)[^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\)` +
`|\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\][^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\]` +
`|\\$[^\\r\\n$]{0,${MAX_MATH_INLINE_LENGTH}}\\$` +
`|\`[^\`\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\`` +
")" +
"|" +
// 9. Paragraphs (with length constraints)
`(?!${AVOID_AT_START})(?:(?:^|\\r?\\n\\r?\\n)(?:<p>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_PARAGRAPH_LENGTH))}(?:</p>)?(?=\\r?\\n\\r?\\n|$))` +
"|" +
// 11. HTML-like tags and their content (including self-closing tags and attributes, with length constraints)
`(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}(?:>[\\s\\S]{0,${MAX_HTML_TAG_CONTENT_LENGTH}}?</[a-zA-Z]+>|\\s*/>))` +
"|" +
// 12. LaTeX-style math expressions (inline and block, with length constraints)
`(?:(?:\\$\\$[\\s\\S]{0,${MAX_MATH_BLOCK_LENGTH}}?\\$\\$)|(?:\\$[^\\$\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\\$))` +
"|" +
// 14. Fallback for any remaining content (with length constraints)
`(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}` +
")",
"gmu"
);
function main({text}){
const chunks = [];
let currentChunk = '';
const tokens = countToken(text)
const matches = text.match(regex);
if (matches) {
matches.forEach((match) => {
if (currentChunk.length + match.length <= 1000) {
currentChunk += match;
} else {
if (currentChunk) {
chunks.push(currentChunk);
}
currentChunk = match;
}
});
if (currentChunk) {
chunks.push(currentChunk);
}
}
return {chunks, tokens};
}
算了别看了,你直接用就好啦。。
格式化文本
切分完文本之后,开始对源文本进行格式化:
格式化代码如下:
function main({source_text_chunks, source_doc_text_chunks, i=0}){
let chunks = source_doc_text_chunks || source_text_chunks;
let before = chunks.slice(0, i).join("");
let current = " <TRANSLATE_THIS>" + chunks[i] + "</TRANSLATE_THIS>";
let after = chunks.slice(i + 1).join("");
let tagged_text = before + current + after;
return {
tagged_text,
chunk_to_translate: chunks[i],
}
}
最终会输出两个变量,其中 tagged_text
包含了整个文本,而 chunk_to_translate
只包含了本轮需要翻译的文本块。
接入术语表
在正式翻译之前,我们还可以将专有名词的词库作为知识库,在翻译前进行搜索。
记得要开启问题优化哦。
开始翻译
现在我们终于进入了翻译环节,这里我们使用 CoT 思维链,让 LLM 显式地、系统地生成推理链条,展示翻译的完整思考过程。
系统提示词如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Role: 资深翻译专家
## Background:
你是一位经验丰富的翻译专家,精通{{source_lang}}和{{target_lang}}互译,尤其擅长将{{source_lang}}文章译成流畅易懂的{{target_lang}}。你曾多次带领团队完成大型翻译项目,译文广受好评。
## Attention:
- 翻译过程中要始终坚持"信、达、雅"的原则,但"达"尤为重要
- 翻译的译文要符合{{target_lang}}的表达习惯,通俗易懂,连贯流畅
- 避免使用过于文绉绉的表达和晦涩难懂的典故引用
- 诗词歌词等内容需按原文换行和节奏分行,不破坏原排列格式
- 对于专有的名词或术语,按照给出的术语表进行合理替换
- 在翻译过程中,注意保留文档原有的列表项和格式标识
- 不要翻译代码块中的内容,保持原样输出
## Constraints:
- 必须严格遵循四轮翻译流程:直译、意译、反思、提升
- 译文要忠实原文,准确无误,不能遗漏或曲解原意
- 注意判断上下文,避免重复翻译
- 最终译文使用Markdown的代码块呈现,但是不用输出markdown这个单词
## Goals:
- 通过四轮翻译流程,将{{source_lang}}原文译成高质量的{{target_lang}}译文
- 译文要准确传达原文意思,语言表达力求浅显易懂,朗朗上口
- 适度使用一些熟语俗语、流行网络用语等,增强译文的亲和力
## Skills:
- 精通{{source_lang}} {{target_lang}}两种语言,具有扎实的语言功底和丰富的翻译经验
- 擅长将{{source_lang}}表达习惯转换为地道自然的{{target_lang}}
- 对当代{{target_lang}}语言的发展变化有敏锐洞察,善于把握语言流行趋势
## Workflow:
1. 第一轮直译:逐字逐句忠实原文,不遗漏任何信息(代码块内容除外)
2. 第二轮意译:在直译的基础上用通俗流畅的{{target_lang}}意译原文(代码块内容除外)
3. 第三轮反思:仔细审视译文,分点列出一份建设性的批评和有用的建议清单以改进翻译,逐句提出建议,从以下6个角度展开
(i) 准确性(纠正冗余、误译、遗漏或未翻译的文本错误),
(ii) 流畅性(应用{{target_lang}}的语法、拼写和标点规则,并确保没有不必要的重复),
(iii) 风格(确保翻译反映源文本的风格并考虑其文化背景),
(iv) 术语(严格参考给出的术语表,确保术语使用一致)
(v) 语序(合理调整语序,不要生搬{{source_lang}}中的语序,注意调整为{{target_lang}}中的合理语序)
(vi) 代码保护(确保所有代码块内容保持原样,不被翻译)
4. 第四轮提升:严格遵循第三轮提出的建议对翻译修改,定稿出一个简洁畅达、符合大众阅读习惯的译文
## OutputFormat:
- 每一轮前用【思考】说明该轮要点
- 第一轮和第二轮翻译后用【翻译】呈现译文
- 第三轮用【建议】输出建议清单,分点列出,在每一点前用*xxx*标识这条建议对应的要点,如*风格*;建议前用【思考】说明该轮要点,建议后用【建议】呈现建议
- 第四轮在\`\`\`代码块中展示最终译文内容,如\`\`\`xxx\`\`\`,不用输出markdown这个单词
## Suggestions:
- 直译时力求忠实原文,但不要过于拘泥逐字逐句
- 意译时在准确表达原意的基础上,用最朴实无华的{{target_lang}}来表达
- 反思环节重点关注译文是否符合{{target_lang}}表达习惯,是否通俗易懂,是否准确流畅,是否术语一致
- 提升环节采用反思环节的建议对意译环节的翻译进行修改,适度采用一些口语化的表达、网络流行语等,增强译文的亲和力
- 所有包含在代码块(\`\`\`)中的内容都应保持原样,不进行翻译
用户问题如下:
同时不要忘了接入词库。
还需要在 AI 模型配置中关闭【返回 AI 内容】。
这里 AI 会进行好几轮翻译,但是我们只需要最终的翻译结果,所以还需要继续接入【代码运行】节点,将最后一轮的翻译结果提取出来。
代码如下:
function main({data1}){
const result = data1.split("```").filter(item => !!item.trim())
if(result[result.length-1]) {
return {
result: result[result.length-1]
}
}
return {
result: '未截取到翻译内容'
}
}
到这里翻译基本上就结束了,但是有些模型在输出中文内容时,会夹杂英文标点符号,所以为了以防万一,我们可以再加一个【代码运行】节点来对输出内容进行格式化。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function main({target_lang, source_text}) {
let text = source_text;
if (target_lang === '简体中文' || target_lang === '繁體中文') {
// 存储代码块内容
const codeBlocks = [];
let text = source_text.replace(/```[\s\S]*?```/g, (match) => {
codeBlocks.push(match);
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
});
// 替换成对的英文引号
text = text.replace(/"(.*?)"/g, '“$1”');
// 保护 Markdown 链接格式中的括号
text = text.replace(/\[(.*?)\]\((.*?)\)/g, function(match) {
return match.replace(/\(/g, 'LEFTPAREN')
.replace(/\)/g, 'RIGHTPAREN');
});
// 替换成对的英文括号
text = text.replace(/\((.*?)\)/g, '($1)');
// 恢复被保护的 Markdown 链接括号
text = text.replace(/LEFTPAREN/g, '(')
.replace(/RIGHTPAREN/g, ')');
// 更新句号替换逻辑,增加对版本号和URL的保护
text = text.replace(/(\d+)\.(\d+)\.(\d+)/g, '$1DOT$2DOT$3') // 保护版本号 (如 16.2.1)
.replace(/(\d)\.(\d)/g, '$1DOT$2') // 临时替换小数点
.replace(/([a-zA-Z])\.([a-zA-Z])/g, '$1DOT$2') // 临时替换缩写中的句号
.replace(/([a-zA-Z])\.(\d)/g, '$1DOT$2') // 临时替换字母与数字之间的句号
.replace(/(\d)\.([a-zA-Z])/g, '$1DOT$2') // 临时替换数字与字母之间的句号
.replace(/([a-zA-Z])\./g, '$1DOT') // 临时替换字母后面的句号(如 a.)
.replace(/https?:/g, 'HTTPCOLON') // 保护 URL 中的冒号
.replace(/\./g, '。') // 替换其他句号
.replace(/DOT/g, '.') // 恢复被保护的句号
.replace(/HTTPCOLON/g, 'http:'); // 恢复 URL 中的冒号
// 替换英文逗号,但不替换数字中的逗号
text = text.replace(/(\d),(\d)/g, '$1COMMA$2') // 临时替换数字中的逗号
.replace(/,/g, ',') // 替换其他逗号
.replace(/COMMA/g, ','); // 恢复数字中的逗号
// 替换其他常见符号
const replacements = {
'!': '!',
'?': '?',
';': ';',
};
for (const [key, value] of Object.entries(replacements)) {
text = text.replace(new RegExp(`\\${key}`, 'g'), value);
}
// 在中文和英文字符之间添加空格
// 中文字符范围: \u4e00-\u9fa5
// 英文字符范围: a-zA-Z0-9
text = text.replace(/([\u4e00-\u9fa5])([a-zA-Z0-9])/g, '$1 $2')
.replace(/([a-zA-Z0-9])([\u4e00-\u9fa5])/g, '$1 $2');
// 恢复代码块
text = text.replace(/__CODE_BLOCK_(\d+)__/g, (_, index) => codeBlocks[index]);
}
return {
text
};
}
循环
单文本块翻译完成后,需要判断一下所有的文本是否都翻译完成了,如果还没翻译完,就回到循环的起点,按照之前的流程继续翻译。
代码如下:
function main({chunks, doc_chunks, currentChunk}){
let new_chunks = doc_chunks || chunks
const findIndex = new_chunks.findIndex((item) => item ===currentChunk)
return {
isEnd: new_chunks.length-1 === findIndex,
i: findIndex + 1,
}
}
如果翻译完成了,就直接回复翻译完成,整个工作流结束。
效果演示
咱们先来导入一个词库。
下载链接:https://images.tryfastgpt.ai/vocabulary.csv
导入方式很简单,在 FastGPT 中新建一个通用知识库:
然后导入表格数据集:
然后上传你的 csv 数据集,一路下一步,最后就得到了处理完成的词库。
点进去可以看到详情:
导入完成后,就可以在工作流中选择该词库了。
最终我们来测试一下翻译效果:
点击聊天框左侧的回形针图标上传附件,然后选择需要上传的文档。
测试文档地址:https://images.tryfastgpt.ai/Sealos-Devbox-quick-start.pdf
上传文档后,点击右边的发送按钮开始翻译。
术语翻译的一致性保持的非常完美:
总结
好啦!到这里我们就完整复刻了吴恩达老师的 translation-agent,而且通过 FastGPT 工作流的能力,我们不仅实现了文本翻译,还支持了文档翻译功能。整个翻译过程不仅准确,而且通过术语表的加持,专业术语的翻译也能保持高度一致性。
相信有了这个工具,你的翻译工作效率一定能上一个台阶!
如果你连一点点代码都不想写,那也没问题,只需要导入我分享的工作流就可以了。
工作流导入方式:将鼠标指针悬停在新建的工作流左上方标题处,然后点击【导入配置】