이 글에서는 Proof of Concept(POC)으로 개발한 Python 스크립트를 공유합니다. 이 스크립트는 OpenAI의 GPT-4 언어 모델을 사용하여 블로그 게시물 번역을 자동화하도록 설계되었습니다. 이 스크립트는 Markdown 파일을 처리하도록 특별히 만들어져 있어, 제 글의 다국어 관리를 더 쉽게 해줍니다. 번역본은 페이지 상단의 언어 선택기를 통해 제공됩니다.
프로젝트 시작: 내 블로그를 위한 AI와 자동화의 결합
이 게시물 번역 자동화 프로젝트는 인공지능에 대한 제 관심이 커지면서 시작되었습니다. 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
해결책: 혁신적인 스크립트
저는 OpenAI GPT-4 API를 활용해 텍스트를 번역하면서 비텍스트 요소는 보존하는 Python 스크립트를 설계했습니다. 일련의 처리 규칙과 플레이스홀더 사용을 통해 스크립트는 코드 블록과 번역하지 말아야 할 다른 요소들을 식별하고 제외할 수 있으므로, 번역된 콘텐츠가 원본에 충실하도록 보장합니다.
주요 기능
- GPT-4를 통한 정확한 번역 : 스크립트는 OpenAI의 GPT-4 모델을 사용해 프랑스어에서 영어로 텍스트를 번역하며, 원문이 가진 품질과 뉘앙스를 유지하도록 합니다.
- 서식 보존 : 코드 블록, URL 및 이미지 경로는 번역 과정에서 식별되어 그대로 유지되므로 원래 서식이 보존됩니다.
- 다국어 유연성 : 스크립트는 소스 및 대상 언어를 쉽게 변경할 수 있도록 설계되어 다양한 다국어 응용에 적합합니다.
- Markdown 파일 지원 : Markdown으로 작성된 문서를 번역하고 해당 구조와 포맷을 유지할 수 있습니다.
- 디렉토리 번역 자동화 : 지정된 디렉토리와 하위 디렉토리에서 Markdown 파일을 자동으로 번역하여 대량 콘텐츠 관리를 용이하게 합니다.
- 번역 노트 통합 : 번역된 문서의 끝에 사용된 GPT 모델을 표시하는 번역 노트를 자동으로 추가합니다.
- 간편한 설정 및 사용자화 : 기본 API 키, GPT 모델, 소스/대상 언어, 파일 디렉토리 등의 기본 설정을 사용자화할 수 있어 유연하게 사용할 수 있습니다.
- 성능 보고 : 각 파일 번역에 소요된 시간을 알려주어 성능 모니터링이 가능합니다.
스크립트 코드
코드는 여기에 있습니다 : AI 기반 마크다운 번역기
#!/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ée)으로 구분됩니다. - 그런 다음 함수는 텍스트에서 코드 블록을 플레이스홀더로 대체합니다. 플레이스홀더는
#CODEBLOCK{index}#형태의 문자열이며, 여기서index는 리스트code_blocks에서 해당 코드 블록의 인덱스입니다. - 함수는 OpenAI API에 보낼 메시지를 생성합니다. 이 메시지는 시스템 메시지(URL, 이미지 경로, 코드 블록 등은 번역되지 않도록 지시)와 번역할 텍스트를 포함하는 사용자 메시지의 두 부분으로 구성됩니다.
- 함수는
client.chat.completions.create()메서드를 사용해 번역 요청을 전송합니다. 이때 사용할 모델과 번역할 메시지들을 지정합니다. - 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) 버전에서 한국어(ko)로 번역되었습니다. 번역 과정에 대한 자세한 정보는 https://gitlab.com/jls42/ai-powered-markdown-translator 를 참조하세요.