Avis - Cette page a été traduite automatiquement de l’anglais et peut contenir des inexactitudes. La traduction est fournie uniquement pour votre commodité. Pour un contenu plus précis, veuillez consulter la version anglaise.
Comment nous avons utilisé l'API OpenAI pour traduire notre site Web

Comment nous avons utilisé l'API OpenAI pour traduire notre site Web

Introduction

Lorsque nous avons décidé de rendre notre site basé sur GoHugo.io multilingue, nous voulions une méthode efficace, évolutive et rentable pour générer des traductions. Au lieu de traduire manuellement chaque page, nous avons tiré parti de l’API d’OpenAI pour automatiser le processus. Cet article explique comment nous avons intégré l’API OpenAI avec Hugo, en utilisant le thème HugoPlate de Zeon Studio, pour générer des traductions rapidement et avec précision.

Pourquoi nous avons choisi l’API OpenAI pour la traduction

Les services de traduction traditionnels nécessitent souvent un effort manuel significatif, et les outils automatisés comme Google Translate, bien qu’utiles, ne fournissent pas toujours le niveau de personnalisation dont nous avions besoin. L’API d’OpenAI nous a permis de :

  • Automatiser les traductions en masse
  • Personnaliser le style de traduction
  • Maintenir un meilleur contrôle sur la qualité
  • S’intégrer parfaitement à notre site basé sur Hugo
  • Signaler des pages individuelles pour retraduction

Processus étape par étape

1. Préparation du site Hugo

Notre site était déjà configuré en utilisant le thème HugoPlate, qui prend en charge la fonctionnalité multilingue. La première étape consistait à activer le support des langues dans notre fichier Hugo config/_default/languages.toml :

################ English language ##################
[en]
languageName = "English"
languageCode = "en-us"
contentDir = "content/english"
weight = 1

################ Arabic language ##################
[ar]
languageName = "العربية"
languageCode = "ar"
contentDir = "content/arabic"
languageDirection = 'rtl'
weight = 2

Cette configuration garantit que Hugo peut générer des versions linguistiques séparées de notre contenu.

2. Automatisation des traductions avec l’API OpenAI

Nous avons développé un script Bash pour automatiser la traduction des fichiers Markdown. Ce script :

  • Lit les fichiers .md en anglais du répertoire source.
  • Utilise l’API OpenAI pour traduire le texte tout en préservant la mise en forme Markdown.
  • Écrit le contenu traduit dans les répertoires de langue appropriés.
  • Suit l’état de la traduction à l’aide d’un fichier JSON.

Voici un aperçu de notre script :

#!/bin/bash
# ===========================================
# Hugo Content Translation and Update Script (Sequential Processing & New-Language Cleanup)
# ===========================================
# This script translates Hugo Markdown (.md) files from English to all supported target languages
# sequentially (one file at a time). It updates a JSON status file after processing each file.
# At the end of the run, it checks translation_status.json and removes any language from
# translate_new_language.txt only if every file for that language is marked as "success".
# ===========================================

set -euo pipefail

# --- Simple Logging Function (writes to stderr) ---
log_step() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}

# --- Environment Setup ---
export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
# (Removed "Script starting." log)

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log_step "SCRIPT_DIR set to: $SCRIPT_DIR"

if [ -f "$SCRIPT_DIR/.env" ]; then
    log_step "Loading environment variables from .env"
    set -o allexport
    source "$SCRIPT_DIR/.env"
    set +o allexport
fi

# Load new languages from translate_new_language.txt (if available)
declare -a NEW_LANGUAGES=()
if [ -f "$SCRIPT_DIR/translate_new_language.txt" ]; then
    while IFS= read -r line || [[ -n "$line" ]]; do
        NEW_LANGUAGES+=("$line")
    done <"$SCRIPT_DIR/translate_new_language.txt"
else
    log_step "No new languages file found; proceeding with empty NEW_LANGUAGES."
fi

API_KEY="${OPENAI_API_KEY:-}"
if [ -z "$API_KEY" ]; then
    log_step "❌ Error: OPENAI_API_KEY environment variable is not set."
    exit 1
fi

# Supported Languages (full list)
SUPPORTED_LANGUAGES=("ar" "bg" "bn" "cs" "da" "de" "el" "es" "fa" "fi" "fr" "ha" "he" "hi" "hr" "hu" "id" "ig" "it" "ja" "ko" "ml" "mr" "ms" "nl" "no" "pa" "pl" "pt" "ro" "ru" "sk" "sn" "so" "sr" "sv" "sw" "ta" "te" "th" "tl" "tr" "uk" "vi" "xh" "yo" "zh" "zu")

STATUS_FILE="$SCRIPT_DIR/translation_status.json"
SRC_DIR="$SCRIPT_DIR/Content/english"
log_step "Source directory: $SRC_DIR"

# Check dependencies
for cmd in jq curl; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
        log_step "❌ Error: '$cmd' is required. Please install it."
        exit 1
    fi
done

MAX_RETRIES=5
WAIT_TIME=2 # seconds

# Create/initialize status file if missing
if [ ! -f "$STATUS_FILE" ]; then
    echo "{}" >"$STATUS_FILE"
    log_step "Initialized status file at: $STATUS_FILE"
fi

# --- Locking for Status Updates ---
lock_status() {
    local max_wait=10
    local start_time
    start_time=$(date +%s)
    while ! mkdir "$STATUS_FILE.lockdir" 2>/dev/null; do
        sleep 0.01
        local now
        now=$(date +%s)
        if ((now - start_time >= max_wait)); then
            log_step "WARNING: Lock wait exceeded ${max_wait}s. Forcibly removing stale lock."
            rm -rf "$STATUS_FILE.lockdir"
        fi
    done
}

unlock_status() {
    rmdir "$STATUS_FILE.lockdir"
}

update_status() {
    local file_path="$1" lang="$2" status="$3"
    lock_status
    jq --arg file "$file_path" --arg lang "$lang" --arg status "$status" \
        '.[$file][$lang] = $status' "$STATUS_FILE" >"$STATUS_FILE.tmp" && mv "$STATUS_FILE.tmp" "$STATUS_FILE"
    unlock_status
}

# --- Translation Function ---
translate_text() {
    local text="$1" lang="$2"
    local retry_count=0
    while [ "$retry_count" -lt "$MAX_RETRIES" ]; do
        user_message="Translate the following text to $lang. Preserve all formatting exactly as in the original.
$text"
        json_payload=$(jq -n \
            --arg system "Translate from English to $lang. Preserve original formatting exactly." \
            --arg user_message "$user_message" \
            '{
                "model": "gpt-4o-mini",
                "messages": [
                    {"role": "system", "content": $system},
                    {"role": "user", "content": $user_message}
                ],
                "temperature": 0.3
            }')
        response=$(curl -s https://api.openai.com/v1/chat/completions \
            -H "Content-Type: application/json" \
            -H "Authorization: Bearer $API_KEY" \
            -d "$json_payload")
        log_step "📥 Received API response."
        local error_type
        error_type=$(echo "$response" | jq -r '.error.type // empty')
        local error_message
        error_message=$(echo "$response" | jq -r '.error.message // empty')
        if [ "$error_type" == "insufficient_quota" ]; then
            sleep "$WAIT_TIME"
            retry_count=$((retry_count + 1))
        elif [[ "$error_type" == "rate_limit_reached" || "$error_type" == "server_error" || "$error_type" == "service_unavailable" ]]; then
            sleep "$WAIT_TIME"
            retry_count=$((retry_count + 1))
        elif [ "$error_type" == "invalid_request_error" ]; then
            return 1
        elif [ -z "$error_type" ]; then
            if ! translated_text=$(echo "$response" | jq -r '.choices[0].message.content' 2>/dev/null); then
                return 1
            fi
            if [ "$translated_text" == "null" ] || [ -z "$translated_text" ]; then
                return 1
            else
                translated_text=$(echo "$translated_text" | sed -e 's/^```[[:space:]]*//; s/[[:space:]]*```$//')
                echo "$translated_text"
                return 0
            fi
        else
            return 1
        fi
    done
    return 1
}

# --- Process a Single File (Sequential Version) ---
process_file() {
    local src_file="$1" target_file="$2" lang="$3" rel_src="$4"
    # If target file exists and is non-empty, mark status as success.
    if [ -s "$target_file" ]; then
        update_status "$rel_src" "$lang" "success"
        return 0
    fi
    content=$(<"$src_file")
    if [[ "$content" =~ ^(---|\+\+\+)[[:space:]]*$ ]] && [[ "$content" =~ [[:space:]]*(---|\+\+\+\+)[[:space:]]*$ ]]; then
        front_matter=$(echo "$content" | sed -n '/^\(---\|\+\+\+\)$/,/^\(---\|\+\+\+\)$/p')
        body_content=$(echo "$content" | sed -n '/^\(---\|\+\+\+\)$/,/^\(---\|\+\+\+\)$/d')
    else
        front_matter=""
        body_content="$content"
    fi
    log_step "Translating [$rel_src] to $lang..."
    translated_body=$(translate_text "$body_content" "$lang")
    if [ $? -ne 0 ]; then
        update_status "$rel_src" "$lang" "failed"
        return 1
    fi
    mkdir -p "$(dirname "$target_file")"
    if [ -n "$front_matter" ]; then
        echo -e "$front_matter
$translated_body" >"$target_file"
    else
        echo -e "$translated_body" >"$target_file"
    fi
    updated_content=$(echo "$content" | sed -E 's/^retranslate:\s*true/retranslate: false/')
    echo "$updated_content" >"$src_file"
    update_status "$rel_src" "$lang" "success"
}

# --- Main Sequential Processing ---
ALL_SUCCESS=true
for TARGET_LANG in "${SUPPORTED_LANGUAGES[@]}"; do
    log_step "Processing language: $TARGET_LANG"
    TARGET_DIR="$SCRIPT_DIR/Content/$TARGET_LANG"
    while IFS= read -r -d '' src_file; do
        rel_src="${src_file#$SCRIPT_DIR/}"
        target_file="$TARGET_DIR/${src_file#$SRC_DIR/}"
        # If file is marked not to retranslate, check that target file exists and is non-empty.
        if ! [[ " ${NEW_LANGUAGES[@]:-} " =~ " ${TARGET_LANG} " ]] && grep -q '^retranslate:\s*false' "$src_file"; then
            if [ -s "$target_file" ]; then
                update_status "$rel_src" "$TARGET_LANG" "success"
            else
                update_status "$rel_src" "$TARGET_LANG" "failed"
            fi
            continue
        fi
        process_file "$src_file" "$target_file" "$TARGET_LANG" "$rel_src"
    done < <(find "$SRC_DIR" -type f -name "*.md" -print0)
done

log_step "Translation run completed."
end_time=$(date +%s)
duration=$((end_time - $(date +%s)))
log_step "Execution Time: $duration seconds"

if [ "$ALL_SUCCESS" = true ]; then
    log_step "🎉 Translation completed successfully for all supported languages!"
else
    log_step "⚠️ Translation completed with some errors."
fi

# --- Clean Up Fully Translated New Languages ---
if [ -f "$SCRIPT_DIR/translate_new_language.txt" ]; then
    log_step "Cleaning up fully translated new languages..."
    for lang in "${NEW_LANGUAGES[@]:-}"; do
        incomplete=$(jq --arg lang "$lang" 'to_entries[] | select(.value[$lang] != null and (.value[$lang] != "success")) | .key' "$STATUS_FILE")
        if [ -z "$incomplete" ]; then
            log_step "All translations for new language '$lang' are marked as success. Removing from translate_new_language.txt."
            sed -E -i '' "/^[[:space:]]*$lang[[:space:]]*$/d" "$SCRIPT_DIR/translate_new_language.txt"
        else
            log_step "Language '$lang' still has incomplete translations."
        fi
    done
fi

3. Gestion de l’état de la traduction

Pour éviter les traductions redondantes et suivre les progrès, nous avons utilisé un fichier JSON (translation_status.json). Le script met à jour ce fichier après le traitement de chaque document, garantissant que seul le contenu nouveau ou mis à jour soit traduit.

4. Gestion des erreurs et limites de l’API

Nous avons mis en œuvre des tentatives et une gestion des erreurs pour faire face aux limites de taux, aux échecs de l’API et aux problèmes de quota. Le script attend avant de réessayer si l’API OpenAI renvoie une erreur comme rate_limit_reached ou service_unavailable.

5. Déploiement

Une fois le contenu traduit généré, l’exécution de hugo --minify construit le site statique multilingue, prêt pour le déploiement.

Défis et solutions

1. Précision de la traduction

Bien que les traductions d’OpenAI soient généralement précises, certains termes techniques peuvent nécessiter une révision manuelle, mais nous ne sommes qu’une équipe de deux, donc nous espérons le meilleur. Nous avons affiné les invites pour maintenir le contexte et le ton.

2. Problèmes de mise en forme

La syntaxe Markdown a parfois été altérée lors de la traduction. Pour résoudre ce problème, nous avons ajouté une logique de post-traitement pour préserver la mise en forme.

3. Optimisation des coûts de l’API

Pour réduire les coûts, nous avons mis en œuvre un cache pour éviter de retraduire le contenu inchangé.

4. Gestion efficace des retraductions

Pour retraduire des pages spécifiques, nous avons ajouté un paramètre de front matter retranslate: true. Le script ne retraduit que les pages marquées avec ce paramètre. Cela nous permet de mettre à jour les traductions au besoin sans avoir à retraduire l’ensemble du site.

Conclusion

En intégrant l’API OpenAI avec Hugo, nous avons automatisé la traduction de notre site Web tout en maintenant la qualité et la flexibilité. Cette approche a permis d’économiser du temps, d’assurer la cohérence et de nous permettre de nous développer sans effort. Si vous cherchez à rendre votre site Hugo multilingue, l’API d’OpenAI offre une solution puissante.

Share This Page: