博客背景
在运营技术博客时,内容的覆盖面是一个重要的考虑因素。特别是,仅用韩语撰写的内容覆盖面有限,限制了与全球开发人员的知识共享。事实上,根据开发人员的人口统计数据,韩国是排名最靠后的 15 个国家之一,这进一步强调了多语言支持的必要性。
전문 개발자 수 기준 상위 15개국(参见:)。
对自动化的需求
现代人工智能翻译和传统翻译工具可以提供相当准确的翻译。但是,每次撰写新文章时都要手动翻译和上传,效率很低。特别是如果您不仅要提供英文版,还要提供中文版和日文版,而这些地方有许多开发人员在工作,那么手动操作就不太现实了。
为了解决这个问题,我们使用 DeepL API 实现了自动翻译脚本。
开发环境配置
安装必要的软件包,以便在 Next.js 环境中运行 Node.js 脚本。
pnpm install deepl-node dotenv tsx
起初,我尝试使用ts-node
,但它与 Next.js 环境存在配置冲突。相反,我们使用tsx
库建立了一个独立的执行环境。
项目结构
首先,我的项目结构大致如下。
.
├── src/
│ └── app/
│ └── posts/
│ └── [slug]/
│ └── page.tsx
├── posts/
│ └── post1.mdx
└── package.json
然后,我创建了这样一个脚本
import fs from "fs/promises";
import path from "path";
import * as deepl from "deepl-node";
import matter from "gray-matter";
import dotenv from "dotenv";
dotenv.config();
const DEEPL_API_KEY = process.env.DEEPL_API_KEY!;
const translator = new deepl.Translator(DEEPL_API_KEY);
const SOURCE_DIR = "src/posts";
const TARGET_DIR = "src/posts/en";
interface PostContent {
content: string;
data: {
title: string;
description: string;
[key: string]: string;
};
}
async function translatePost(content: {
data: { [p: string]: string };
content: string;
}): Promise<PostContent> {
const translatedTitle = await translator.translateText(
content.data.title,
"ko",
"en-US",
);
const translatedDescription = await translator.translateText(
content.data.description,
"ko",
"en-US",
);
const translatedContent = await translator.translateText(
content.content,
"ko",
"en-US",
);
return {
content: translatedContent.text,
data: {
...content.data,
title: translatedTitle.text,
description: translatedDescription?.text,
originalLang: "ko",
},
};
}
async function processFile(filename: string) {
try {
const sourcePath = path.join(SOURCE_DIR, filename);
const targetPath = path.join(TARGET_DIR, filename);
// 파일 존재 여부 확인
try {
await fs.access(sourcePath);
} catch (error) {
throw new Error(`파일을 찾을 수 없습니다: ${filename}`);
}
// MDX 파일 읽기
const fileContent = await fs.readFile(sourcePath, "utf-8");
const { data, content } = matter(fileContent);
// 번역 실행
console.log(`${filename} 번역 중...`);
const translated = await translatePost({ data, content });
// 번역된 MDX 파일 생성
const translatedFileContent = matter.stringify(
translated.content,
translated.data,
);
await fs.mkdir(TARGET_DIR, { recursive: true });
await fs.writeFile(targetPath, translatedFileContent);
console.log(`${filename} 번역 완료!`);
} catch (error) {
console.error(`Error:`, error);
process.exit(1);
}
}
// 명령줄 인자에서 파일명 가져오기
const filename = process.argv[2];
if (!filename) {
process.exit(1);
}
// 파일 확장자 확인
if (!filename.endsWith(".mdx")) {
console.error("Error: MDX 파일만 지원됩니다.");
process.exit(1);
}
processFile(filename);
将脚本也添加到package.json
中。
{
"scripts": {
"translate": "tsx scripts/translate-posts.ts"
}
}
运行结果
现在,在终端中键入命令
就会创建一个已翻译的 mdx 文件。
问题
在最初的实施过程中,我们将 MDX 文件中的文本直接发送到 DeepL API,但发现了以下问题
- 破坏 Markdown 语法
- 不必要地翻译代码块
- 图像标签和链接结构失真
- 原创
- 日语翻译
如何解决
我一直在想该怎么办,于是想出了以下办法。HTML handling首先,我在 DeepL API 文档中看到了这部分内容,似乎以 HTML 发送文本可以很好地处理表单,而不会破坏表单。
因此,为了解决上述问题,我们引入了以下改进流程。
- 将 MDX 转换为 HTML
- 发送 DeepL API,并保留 HTML 标记选项
- 翻译 HTML → MDX 重新转换
const convertMDXToHtml = async (markdown: string) => {
try {
const html = await unified()
.use(remarkParse)
.use(remarkHtml)
.process(markdown);
return html.toString();
} catch (err) {
console.error("MD => HTML 변환 중 오류가 발생했습니다: ");
return "error";
}
};
const convertHtmlToMDX = async (html: string) => {
try {
const markdown = await unified()
.use(rehypeParse)
.use(rehypeRemark)
.use(remarkStringify)
.process(html);
return markdown.toString();
} catch (err) {
console.error("HTML => MD 변환 중 오류가 발생했습니다: ");
return "error";
}
};
async function translatePost(
content: { data: { [p: string]: string }; content: string },
targetLang: TargetLanguageCode,
): Promise<PostContent> {
// 중략...
// md => html
const html = await convertMDXToHtml(content.content);
// html => translated html
const translatedContent = await translator.translateText(
html,
"ko",
targetLang,
{
tagHandling: "html",
},
);
// translated html => md
const mdx = await convertHtmlToMDX(translatedContent.text);
return {
content: mdx,
// ...
};
}
现在表单可以正确输入了!
结束语
通过这次自动化实施,我们顺利完成了多语言版博文的部署过程。我将继续验证翻译后的文章是否按照我的预期进行了翻译,但现在,我期待着创建多语言的静态文件,并看看我所希望的搜索引擎优化改进是否真的带来了更多的流量!