在这篇文章中,我分享了一个作为概念验证(POC)开发的 Python 脚本,用于使用 OpenAI 的 GPT-4 语言模型自动翻译我博客的文章。该脚本专门用于处理 Markdown 文件,便于对文章进行多语言管理。翻译可通过页面顶部的语言选择器访问。
项目起步:将人工智能与自动化融入我的博客
这个自动化翻译博客文章的项目源自我对人工智能日益增长的兴趣。受到我对 OpenAI GPT-4 和 Mistral AI API 的初步实验的启发,我被将这些技术落实到一个实用项目中的想法所吸引,为我的博客提供切实的价值。这不仅仅是掌握 AI 工具的探索,也是一种将自动化与创新结合以丰富我的数字空间的愿望。
该项目演变成了一次冒险,在这过程中 AI 不再只是写作的主题,而是开发中的一个积极伙伴。用 AI 简单高效地翻译我的文章,同时探索其自动化能力,带来了令人着迷的前景。这是一次跨越语言障碍的机会,让我的内容对更广泛的读者可及,同时在不断发展的人工智能领域中前行。
挑战
主要挑战是创建一个能够精确翻译并保持文章原始格式的脚本,尤其是代码块、链接和图片。另一个挑战是确保脚本能方便地适配以支持不同语言。 它还需要能够考虑到以下结构:
├── content
│ ├── about
│ │ └── a-propos-du-blog-jls42.md
│ ├── mentions
│ │ └── mentions-legales.md
│ ├── posts
│ │ ├── blog
│ │ │ └── nouveau-theme-logo.md
│ │ ├── ia
│ │ │ ├── poc-mistral-ai-mixtral.md
│ │ │ ├── poc-openai-api-gpt4.md
│ │ │ └── stable-difusion-aws-ec2.md
│ │ ├── infrastructure
│ │ │ └── infrastruture-as-code-serverless-ha-jls42-org.md
│ │ └── raspberry-pi
│ │ ├── glusterfs_distribue_replique_sur_raspberry_pi_via_ansible.md
│ │ ├── initialisation-auto-de-raspbian-sur-raspberry-pi.md
│ │ ├── installation-de-docker-sur-raspberry-pi-via-ansible.md
│ │ └── installation-de-kubernetes-sur-raspberry-pi-via-ansible.md
解决方案:一个创新脚本
我设计了一个 Python 脚本,依赖 OpenAI 的 GPT-4 API 来翻译文本,同时保留非文本元素。通过一系列处理规则和占位符的使用,脚本可以识别并排除代码块及其他不可翻译的元素,从而确保翻译内容忠实于原文。
关键功能
- 使用 GPT-4 的精确翻译:脚本使用 OpenAI 的 GPT-4 模型将文本从法语翻译为英语,确保保留原文的质量和细微差别。
- 保留格式:代码块、URL 和图片路径会被识别并在翻译过程中保持不变,保证原始格式不被破坏。
- 多语言灵活性:脚本设计易于适配不同的源语言和目标语言,可应用于多种多语言场景。
- 支持 Markdown 文件:能够翻译用 Markdown 写成的文档,保留其特定的结构与格式。
- 目录翻译自动化:自动翻译在指定目录及其子目录中找到的 Markdown 文件,便于大批量内容管理。
- 加入翻译说明:自动在翻译后文档末尾添加翻译说明,标明用于翻译的 GPT 模型。
- 简单配置与个性化:默认参数可自定义,包括 API 密钥、GPT 模型、源/目标语言和文件目录,提供高度灵活性。
- 性能报告:脚本会反馈翻译每个文件所需的时间,便于监控性能。
脚本代码
代码也可在此处获取: AI 驱动的 Markdown 翻译器
#!/usr/bin/env python3
import os
import argparse
import time
from openai import OpenAI
import re
# Initialisation de la configuration avec les valeurs par défaut
DEFAULT_API_KEY = 'votre-clé-api-par-défaut'
DEFAULT_MODEL = "gpt-4-1106-preview"
DEFAULT_SOURCE_LANG = 'fr'
DEFAULT_TARGET_LANG = 'en'
DEFAULT_SOURCE_DIR = 'content/posts'
DEFAULT_TARGET_DIR = 'traductions_en'
MODEL_TOKEN_LIMITS = {
"gpt-4-1106-preview": 4096,
"gpt-4-vision-preview": 4096,
"gpt-4": 8192,
"gpt-4-32k": 32768,
"gpt-4-0613": 8192,
"gpt-4-32k-0613": 32768
}
# Fonction de traduction
def translate_with_openai(text, client, args):
"""
Traduit le texte donné du langage source au langage cible en utilisant l'API OpenAI.
Args:
text (str) : Le texte à traduire.
client : L'objet client OpenAI.
args : Les arguments contenant les informations sur le langage source, le langage cible et le modèle.
Returns:
str : Le texte traduit.
"""
# Détecter et stocker les blocs de code
code_blocks = re.findall(r'(^```[a-zA-Z]*\n.*?\n^```)', text, flags=re.MULTILINE | re.DOTALL)
placeholders = [f"#CODEBLOCK{index}#" for index, _ in enumerate(code_blocks)]
# Remplacer les blocs de code par des placeholders
for placeholder, code_block in zip(placeholders, code_blocks):
text = text.replace(code_block, placeholder)
# Création du message pour l'API
messages = [
{"role": "system", "content": f"Translate the following text from {args.source_lang} to {args.target_lang}, ensuring that elements such as URLs, image paths, and code blocks (delimited by ```) are not translated. Leave these elements unchanged."},
{"role": "user", "content": text}
]
# Envoi de la demande de traduction
response = client.chat.completions.create(
model=args.model,
messages=messages
)
# Obtenir le texte traduit et remplacer les placeholders par les blocs de code originaux
translated_text = response.choices[0].message.content.strip()
for placeholder, code_block in zip(placeholders, code_blocks):
translated_text = translated_text.replace(placeholder, code_block)
return translated_text
def add_translation_note(client, args):
"""
Ajoute une note de traduction à un document.
Args:
client : Le client de traduction.
args : Arguments supplémentaires.
Returns:
La note de traduction formatée.
"""
# Note de traduction en français
translation_note_fr = "Ce document a été traduit de la version française du blog par le modèle "
# Traduire la note en langue cible
translated_note = translate_with_openai(translation_note_fr + args.model, client, args)
# Formatage de la note de traduction
return f"\n\n**{translated_note}**\n\n"
# Traitement des fichiers Markdown
def translate_markdown_file(file_path, output_path, client, args):
"""
Traduit le contenu d'un fichier markdown en utilisant l'API de traduction OpenAI et écrit le contenu traduit dans un nouveau fichier.
Args:
file_path (str): Chemin vers le fichier markdown d'entrée.
output_path (str): Chemin vers le fichier de sortie où le contenu traduit sera écrit.
client: Client de traduction OpenAI.
args: Arguments supplémentaires pour le processus de traduction.
Returns:
None
"""
print(f"Traitement du fichier : {file_path}")
start_time = time.time()
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
translated_content = translate_with_openai(content, client, args)
# Ajouter la note de traduction à la fin du contenu traduit
translation_note = add_translation_note(client, args)
translated_content_with_note = translated_content + translation_note
with open(output_path, 'w', encoding='utf-8') as f:
f.write(translated_content_with_note)
end_time = time.time()
print(f"Traduction terminée en {end_time - start_time:.2f} secondes.")
def translate_directory(input_dir, output_dir, client, args):
"""
Traduit tous les fichiers markdown dans le répertoire d'entrée et ses sous-répertoires.
Args:
input_dir (str): Chemin vers le répertoire d'entrée.
output_dir (str): Chemin vers le répertoire de sortie.
client: Objet client de traduction.
args: Arguments supplémentaires pour la traduction.
Returns:
None
"""
for root, dirs, files in os.walk(input_dir, topdown=True):
# Exclure les dossiers qui commencent par "traductions_"
dirs[:] = [d for d in dirs if not d.startswith("traductions_")]
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
base, _ = os.path.splitext(file)
# Ajouter le nom du modèle utilisé dans le nom du fichier de sortie
output_file = f"{base}-{args.model}-{args.target_lang}.md"
relative_path = os.path.relpath(root, input_dir)
output_path = os.path.join(output_dir, relative_path, output_file)
os.makedirs(os.path.dirname(output_path), exist_ok=True)
if not os.path.exists(output_path):
translate_markdown_file(file_path, output_path, client, args)
print(f"Fichier '{file}' traité.")
def main():
"""
Fonction principale pour traduire les fichiers Markdown.
Args:
--source_dir (str): Répertoire source contenant les fichiers Markdown.
--target_dir (str): Répertoire cible pour sauvegarder les traductions.
--model (str): Modèle GPT à utiliser.
--target_lang (str): Langue cible pour la traduction.
--source_lang (str): Langue source pour la traduction.
"""
parser = argparse.ArgumentParser(description="Traduit les fichiers Markdown.")
parser.add_argument('--source_dir', type=str, default=DEFAULT_SOURCE_DIR, help='Répertoire source contenant les fichiers Markdown')
parser.add_argument('--target_dir', type=str, default=DEFAULT_TARGET_DIR, help='Répertoire cible pour sauvegarder les traductions')
parser.add_argument('--model', type=str, default=DEFAULT_MODEL, help='Modèle GPT à utiliser')
parser.add_argument('--target_lang', type=str, default=DEFAULT_TARGET_LANG, help='Langue cible pour la traduction')
parser.add_argument('--source_lang', type=str, default=DEFAULT_SOURCE_LANG, help='Langue source pour la traduction')
args = parser.parse_args()
openai_api_key = os.getenv('OPENAI_API_KEY', DEFAULT_API_KEY)
with OpenAI(api_key=openai_api_key) as client:
translate_directory(args.source_dir, args.target_dir, client, args)
if __name__ == "__main__":
main()
脚本详解
模块导入
首先,我们有一些必要的模块导入,例如 os、argparse、time 和 re。这些模块用于对文件系统执行操作、解析命令行参数、测量执行时间以及执行文本查找与替换操作。
常量
接着,我们定义了一些常量,例如 DEFAULT_API_KEY、DEFAULT_MODEL、DEFAULT_SOURCE_LANG、DEFAULT_TARGET_LANG、DEFAULT_SOURCE_DIR 和 DEFAULT_TARGET_DIR。这些常量代表脚本中使用的默认值,但可以通过指定命令行参数来修改它们。
函数 translate_with_openai
然后是函数 translate_with_openai。该函数以文本、OpenAI 客户端对象和参数作为输入。它使用 OpenAI API 将文本从源语言翻译为目标语言。其工作流程如下:
- 函数使用正则表达式检测并存储文本中的代码块。这些代码块由三重反引号(
). Les blocs de code sont stockés dans une liste appeléecode_blocks`)分隔。 - 接着,函数将代码块替换为文本中的占位符。占位符的形式为
#CODEBLOCK{index}#,其中index是对应代码块在列表code_blocks中的索引。 - 函数为 OpenAI API 创建一个消息。该消息包含两部分:一条系统消息,指示 API 将文本从源语言翻译为目标语言并保持诸如 URL、图片路径和代码块等元素不被翻译;以及一条包含要翻译文本的用户消息。
- 函数使用方法
client.chat.completions.create()向 API 发送翻译请求,并指定要使用的模型和消息。 - API 的响应包含翻译后的文本。函数获取翻译文本并将占位符替换回原始代码块。
- 最后,函数返回翻译后的文本。
函数 add_translation_note
接下来是函数 add_translation_note。该函数向文档添加一条翻译说明。它以 OpenAI 客户端对象和参数作为输入。其工作流程如下:
- 函数使用变量
translation_note_fr创建一条法语的翻译说明。 - 然后,函数使用
translate_with_openai将该翻译说明通过 OpenAI API 翻译为目标语言。传递给translate_with_openai的参数包括法语翻译说明和其他参数。 - 函数通过添加格式字符来格式化翻译后的说明。
- 最后,函数返回格式化后的翻译说明。
函数 translate_markdown_file
接着是函数 translate_markdown_file。该函数以输入 Markdown 文件的路径、输出文件的路径、OpenAI 客户端对象和参数为输入。它使用 OpenAI 翻译 API 翻译 Markdown 文件的内容并将翻译结果写入输出文件。
该脚本不仅提升了我博客文章的可访问性,还为内容多语言创作的自动化打开了新的可能性。这是朝着更广泛、更包容地传播知识迈出的重要一步。
使用体验与处理时间
使用示例
# Création des répertoires cibles
jls42@Boo:~/blog/jls42$ mkdir content/traductions_en content/traductions_es
###############################################
# Demande de traduction à l'IA vers l'anglais #
###############################################
jls42@Boo:~/blog/jls42$ python3 translate.py --source_dir content/ --target_dir content/traductions_en
Traitement du fichier : content/posts/ia/stable-difusion-aws-ec2.md
Traduction terminée en 21.57 secondes.
Fichier 'stable-difusion-aws-ec2.md' traité.
Traitement du fichier : content/posts/ia/poc-openai-api-gpt4.md
Traduction terminée en 34.87 secondes.
Fichier 'poc-openai-api-gpt4.md' traité.
Traitement du fichier : content/posts/ia/poc-mistral-ai-mixtral.md
Traduction terminée en 62.47 secondes.
Fichier 'poc-mistral-ai-mixtral.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-kubernetes-sur-raspberry-pi-via-ansible.md
Traduction terminée en 46.37 secondes.
Fichier 'installation-de-kubernetes-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-docker-sur-raspberry-pi-via-ansible.md
Traduction terminée en 10.08 secondes.
Fichier 'installation-de-docker-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/initialisation-auto-de-raspbian-sur-raspberry-pi.md
Traduction terminée en 17.17 secondes.
Fichier 'initialisation-auto-de-raspbian-sur-raspberry-pi.md' traité.
Traitement du fichier : content/posts/blog/nouveau-theme-logo.md
Traduction terminée en 12.91 secondes.
Fichier 'nouveau-theme-logo.md' traité.
Traitement du fichier : content/posts/infrastructure/infrastruture-as-code-serverless-ha-jls42-org.md
Traduction terminée en 12.64 secondes.
Fichier 'infrastruture-as-code-serverless-ha-jls42-org.md' traité.
Traitement du fichier : content/mentions/mentions-legales.md
Traduction terminée en 11.90 secondes.
Fichier 'mentions-legales.md' traité.
Traitement du fichier : content/about/a-propos-du-blog-jls42.md
Traduction terminée en 18.72 secondes.
Fichier 'a-propos-du-blog-jls42.md' traité.
################################################
# Demande de traduction à l'IA vers l'espagnol #
################################################
jls42@Boo:~/blog/jls42$ python3 translate.py --source_dir content/ --target_dir content/traductions_es --target_lang es
Traitement du fichier : content/posts/ia/stable-difusion-aws-ec2.md
Traduction terminée en 33.19 secondes.
Fichier 'stable-difusion-aws-ec2.md' traité.
Traitement du fichier : content/posts/ia/poc-openai-api-gpt4.md
Traduction terminée en 25.24 secondes.
Fichier 'poc-openai-api-gpt4.md' traité.
Traitement du fichier : content/posts/ia/poc-mistral-ai-mixtral.md
Traduction terminée en 58.78 secondes.
Fichier 'poc-mistral-ai-mixtral.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-kubernetes-sur-raspberry-pi-via-ansible.md
Traduction terminée en 17.64 secondes.
Fichier 'installation-de-kubernetes-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/installation-de-docker-sur-raspberry-pi-via-ansible.md
Traduction terminée en 19.60 secondes.
Fichier 'installation-de-docker-sur-raspberry-pi-via-ansible.md' traité.
Traitement du fichier : content/posts/raspberry-pi/initialisation-auto-de-raspbian-sur-raspberry-pi.md
Traduction terminée en 37.12 secondes.
Fichier 'initialisation-auto-de-raspbian-sur-raspberry-pi.md' traité.
Traitement du fichier : content/posts/blog/nouveau-theme-logo.md
Traduction terminée en 18.91 secondes.
Fichier 'nouveau-theme-logo.md' traité.
Traitement du fichier : content/posts/infrastructure/infrastruture-as-code-serverless-ha-jls42-org.md
Traduction terminée en 30.73 secondes.
Fichier 'infrastruture-as-code-serverless-ha-jls42-org.md' traité.
Traitement du fichier : content/mentions/mentions-legales.md
Traduction terminée en 13.14 secondes.
Fichier 'mentions-legales.md' traité.
Traitement du fichier : content/about/a-propos-du-blog-jls42.md
Traduction terminée en 11.24 secondes.
Fichier 'a-propos-du-blog-jls42.md' traité.
处理时间
- 英语 : 约 4 分钟(248.70 秒)
- 西班牙语 : 约 4.7 分钟(284.05 秒)
- 总计 : 约 8.7 分钟(532.75 秒) 这些时间展示了脚本的效率与速度。
结果
注意:此示例演示脚本在博客旧的 Hugo 结构上的运行情况。博客后来已迁移到基于 Astro 的新多语言架构。翻译现可通过内置的语言选择器访问。
这篇博客文章浓缩了我在使用 AI 进行翻译自动化方面的经验。它证明了当编程与人工智能结合时,可能性几乎是无限的,为知识分享和内容可及性开辟了新的、令人兴奋的前景。
本文件已使用模型 gpt-5-mini 从 fr 版本翻译为 zh 语言。有关翻译过程的更多信息,请参阅 https://gitlab.com/jls42/ai-powered-markdown-translator