검색

infrastructureblogia

AI로 블로그 게시물 번역 혁신하기

AI로 블로그 게시물 번역 혁신하기

이 글에서는 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 스크립트를 설계했습니다. 일련의 처리 규칙과 플레이스홀더 사용을 통해 스크립트는 코드 블록과 번역하지 말아야 할 다른 요소들을 식별하고 제외할 수 있으므로, 번역된 콘텐츠가 원본에 충실하도록 보장합니다.

주요 기능

  1. GPT-4를 통한 정확한 번역 : 스크립트는 OpenAI의 GPT-4 모델을 사용해 프랑스어에서 영어로 텍스트를 번역하며, 원문이 가진 품질과 뉘앙스를 유지하도록 합니다.
  2. 서식 보존 : 코드 블록, URL 및 이미지 경로는 번역 과정에서 식별되어 그대로 유지되므로 원래 서식이 보존됩니다.
  3. 다국어 유연성 : 스크립트는 소스 및 대상 언어를 쉽게 변경할 수 있도록 설계되어 다양한 다국어 응용에 적합합니다.
  4. Markdown 파일 지원 : Markdown으로 작성된 문서를 번역하고 해당 구조와 포맷을 유지할 수 있습니다.
  5. 디렉토리 번역 자동화 : 지정된 디렉토리와 하위 디렉토리에서 Markdown 파일을 자동으로 번역하여 대량 콘텐츠 관리를 용이하게 합니다.
  6. 번역 노트 통합 : 번역된 문서의 끝에 사용된 GPT 모델을 표시하는 번역 노트를 자동으로 추가합니다.
  7. 간편한 설정 및 사용자화 : 기본 API 키, GPT 모델, 소스/대상 언어, 파일 디렉토리 등의 기본 설정을 사용자화할 수 있어 유연하게 사용할 수 있습니다.
  8. 성능 보고 : 각 파일 번역에 소요된 시간을 알려주어 성능 모니터링이 가능합니다.

스크립트 코드

코드는 여기에 있습니다 : 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, timere와 같은 몇몇 모듈을 임포트합니다.

상수

다음으로 DEFAULT_API_KEY, DEFAULT_MODEL, DEFAULT_SOURCE_LANG, DEFAULT_TARGET_LANG, DEFAULT_SOURCE_DIRDEFAULT_TARGET_DIR와 같은 상수들이 정의되어 있습니다. 이 상수들은 스크립트에서 사용되는 기본값을 나타내며, 커맨드라인 인수를 통해 변경할 수 있습니다.

함수 translate_with_openai

다음으로 translate_with_openai 함수가 있습니다. 이 함수는 텍스트, OpenAI 클라이언트 객체 및 추가 인수를 매개변수로 받습니다. 이 함수는 OpenAI API를 사용해 소스 언어에서 대상 언어로 텍스트를 번역합니다. 작동 방식은 다음과 같습니다:

  1. 함수는 정규식을 사용해 텍스트 내의 코드 블록을 감지하고 저장합니다. 이러한 코드 블록은 삼중 백틱(). Les blocs de code sont stockés dans une liste appelée )으로 구분됩니다.
  2. 그런 다음 함수는 텍스트에서 코드 블록을 플레이스홀더로 대체합니다. 플레이스홀더는 #CODEBLOCK{index}# 형태의 문자열이며, 여기서 index는 리스트 code_blocks에서 해당 코드 블록의 인덱스입니다.
  3. 함수는 OpenAI API에 보낼 메시지를 생성합니다. 이 메시지는 시스템 메시지(URL, 이미지 경로, 코드 블록 등은 번역되지 않도록 지시)와 번역할 텍스트를 포함하는 사용자 메시지의 두 부분으로 구성됩니다.
  4. 함수는 client.chat.completions.create() 메서드를 사용해 번역 요청을 전송합니다. 이때 사용할 모델과 번역할 메시지들을 지정합니다.
  5. API 응답에는 번역된 텍스트가 포함되어 있습니다. 함수는 번역된 텍스트를 가져와 플레이스홀더를 원래의 코드 블록으로 교체합니다.
  6. 마지막으로 함수는 번역된 텍스트를 반환합니다.

함수 add_translation_note

다음으로 add_translation_note 함수가 있습니다. 이 함수는 문서에 번역 노트를 추가합니다. OpenAI 클라이언트 객체와 추가 인수를 매개변수로 받습니다. 작동 방식은 다음과 같습니다:

  1. 함수는 translation_note_fr 변수를 사용해 프랑스어로 된 번역 노트를 작성합니다.
  2. 그런 다음 함수는 translate_with_openai 함수를 사용해 OpenAI API로 번역 노트를 번역합니다. translate_with_openai에 전달되는 인수들에는 프랑스어 번역 노트와 기타 인수들이 포함됩니다.
  3. 함수는 번역된 번역 노트를 서식 문자를 추가해 형식화합니다.
  4. 마지막으로 함수는 형식화된 번역 노트를 반환합니다.

함수 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 를 참조하세요.