Translator AI

Traduce archivos JSON i18n usando Google Gemini o modelos locales de Ollama, con soporte de caché incremental.

Documentación

translator-ai

CI npm version Buy Me A Coffee

Traductor i18n JSON rápido y eficiente que admite múltiples proveedores de IA (Google Gemini, OpenAI y Ollama/DeepSeek) con caché inteligente, deduplicación de múltiples archivos e integración con MCP.

Características

  • Múltiples proveedores de IA: Elige entre Google Gemini, OpenAI (nube) u Ollama/DeepSeek (local) para las traducciones
  • Soporte para múltiples archivos: Procesa múltiples archivos con deduplicación automática para ahorrar llamadas a la API
  • Caché incremental: Solo traduce cadenas nuevas o modificadas, reduciendo drásticamente las llamadas a la API
  • Procesamiento por lotes: Agrupa traducciones de forma inteligente para un rendimiento óptimo
  • Preservación de rutas: Mantiene la estructura JSON exacta, incluidos objetos anidados y arreglos
  • Multiplataforma: Funciona en Windows, macOS y Linux con detección automática del directorio de caché
  • Amigable para desarrolladores: Estadísticas de rendimiento e indicadores de progreso integrados
  • Rentable: Minimiza el uso de la API mediante caché inteligente y deduplicación
  • Detección de idioma: Detecta automáticamente el idioma de origen en lugar de asumir inglés
  • Múltiples idiomas de destino: Traduce a varios idiomas con un solo comando
  • Metadatos de traducción: Incluye opcionalmente detalles de traducción en los archivos de salida para seguimiento
  • Modo de prueba: Previsualiza lo que se traduciría sin realizar llamadas a la API
  • Preservación de formato: Mantiene URLs, correos electrónicos, fechas, números y variables de plantilla sin cambios

Instalación

Instalación global (recomendada)

npm install -g translator-ai

Instalación local

npm install translator-ai

Configuración

Opción 1: API de Google Gemini (nube)

Crea un archivo .env en la raíz de tu proyecto o establece la variable de entorno:

GEMINI_API_KEY=your_gemini_api_key_here

Obtén tu clave de API desde Google AI Studio.

Opción 2: API de OpenAI (nube)

Crea un archivo .env en la raíz de tu proyecto o establece la variable de entorno:

OPENAI_API_KEY=your_openai_api_key_here

Obtén tu clave de API desde OpenAI Platform.

Opción 3: Ollama con DeepSeek-R1 (local)

Para traducción completamente local sin costos de API:

  1. Instala Ollama
  2. Descarga el modelo DeepSeek-R1:
    ollama pull deepseek-r1:latest
    
  3. Usa la bandera --provider ollama:
    translator-ai source.json -l es -o spanish.json --provider ollama
    

Uso

Uso básico

# Translate a single file
translator-ai source.json -l es -o spanish.json

# Translate multiple files with deduplication
translator-ai src/locales/en/*.json -l es -o "{dir}/{name}.{lang}.json"

# Use glob patterns
translator-ai "src/**/*.en.json" -l fr -o "{dir}/{name}.fr.json"

Opciones de línea de comandos

translator-ai <inputFiles...> [options]

Arguments:
  inputFiles                   Path(s) to source JSON file(s) or glob patterns

Options:
  -l, --lang <langCodes>      Target language code(s), comma-separated for multiple
  -o, --output <pattern>      Output file path or pattern
  --stdout                    Output to stdout instead of file
  --stats                     Show detailed performance statistics
  --no-cache                  Disable incremental translation cache
  --cache-file <path>         Custom cache file path
  --provider <type>           Translation provider: gemini, openai, or ollama (default: gemini)
  --ollama-url <url>          Ollama API URL (default: http://localhost:11434)
  --ollama-model <model>      Ollama model name (default: deepseek-r1:latest)
  --gemini-model <model>      Gemini model name (default: gemini-2.0-flash-lite)
  --openai-model <model>      OpenAI model name (default: gpt-4o-mini)
  --list-providers            List available translation providers
  --verbose                   Enable verbose output for debugging
  --detect-source             Auto-detect source language instead of assuming English
  --dry-run                   Preview what would be translated without making API calls
  --preserve-formats          Preserve URLs, emails, numbers, dates, and other formats
  --metadata                  Add translation metadata to output files (may break some i18n parsers)
  --sort-keys                 Sort output JSON keys alphabetically
  --check-keys                Verify all source keys exist in output (exit with error if keys are missing)
  -h, --help                  Display help
  -V, --version               Display version

Output Pattern Variables (for multiple files):
  {dir}   - Original directory path
  {name}  - Original filename without extension
  {lang}  - Target language code

Ejemplos

Traducir un solo archivo

translator-ai en.json -l es -o es.json

Traducir múltiples archivos con patrón

# All JSON files in a directory
translator-ai locales/en/*.json -l es -o "locales/es/{name}.json"

# Recursive glob pattern
translator-ai "src/**/en.json" -l fr -o "{dir}/fr.json"

# Multiple specific files
translator-ai file1.json file2.json file3.json -l de -o "{name}.de.json"

Traducir con ahorros de deduplicación

# Shows statistics including how many API calls were saved
translator-ai src/i18n/*.json -l ja -o "{dir}/{name}.{lang}.json" --stats

Salida a stdout (útil para tuberías)

translator-ai en.json -l de --stdout > de.json

Analizar la salida con jq

translator-ai en.json -l de --stdout | jq

Deshabilitar la caché para una traducción nueva

translator-ai en.json -l ja -o ja.json --no-cache

Usar ubicación de caché personalizada

translator-ai en.json -l ko -o ko.json --cache-file /path/to/cache.json

Usar Ollama para traducción local

# Basic usage with Ollama
translator-ai en.json -l es -o es.json --provider ollama

# Use a different Ollama model
translator-ai en.json -l fr -o fr.json --provider ollama --ollama-model llama2:latest

# Connect to remote Ollama instance
translator-ai en.json -l de -o de.json --provider ollama --ollama-url http://192.168.1.100:11434

# Check available providers
translator-ai --list-providers

Funciones avanzadas

# Detect source language automatically
translator-ai content.json -l es -o spanish.json --detect-source

# Translate to multiple languages at once
translator-ai en.json -l es,fr,de,ja -o translations/{lang}.json

# Dry run - see what would be translated without making API calls
translator-ai en.json -l es -o es.json --dry-run

# Preserve formats (URLs, emails, dates, numbers, template variables)
translator-ai app.json -l fr -o app-fr.json --preserve-formats

# Include translation metadata (disabled by default to ensure compatibility)
translator-ai en.json -l fr -o fr.json --metadata

# Sort keys alphabetically for consistent output
translator-ai en.json -l fr -o fr.json --sort-keys

# Verify all keys are present in the translation
translator-ai en.json -l fr -o fr.json --check-keys

# Use a different Gemini model
translator-ai en.json -l es -o es.json --gemini-model gemini-2.5-flash

# Combine features
translator-ai src/**/*.json -l es,fr,de -o "{dir}/{name}.{lang}.json" \
  --detect-source --preserve-formats --stats --check-keys

Modelos Gemini disponibles

La opción --gemini-model te permite elegir entre varios modelos de Gemini. Las opciones populares incluyen:

  • gemini-2.0-flash-lite (predeterminado) - Rápido y eficiente para la mayoría de las traducciones
  • gemini-2.5-flash - Rendimiento mejorado con capacidades más nuevas
  • gemini-pro - Comprensión más sofisticada para traducciones complejas
  • gemini-1.5-pro - Modelo pro de generación anterior
  • gemini-1.5-flash - Modelo rápido de generación anterior

Ejemplo de uso:

# Use the latest flash model
translator-ai en.json -l es -o es.json --gemini-model gemini-2.5-flash

# Use the default lightweight model
translator-ai en.json -l fr -o fr.json --gemini-model gemini-2.0-flash-lite

Modelos OpenAI disponibles

La opción --openai-model te permite elegir entre varios modelos de OpenAI. Las opciones populares incluyen:

  • gpt-4o-mini (predeterminado) - Rentable y rápido para la mayoría de las traducciones
  • gpt-4o - Modelo más capaz con comprensión avanzada
  • gpt-4-turbo - Modelo insignia de generación anterior
  • gpt-3.5-turbo - Rápido y eficiente para traducciones más simples

Ejemplo de uso:

# Use OpenAI with the default model
translator-ai en.json -l es -o es.json --provider openai

# Use GPT-4o for complex translations
translator-ai en.json -l ja -o ja.json --provider openai --openai-model gpt-4o

# Use GPT-3.5-turbo for faster, simpler translations
translator-ai en.json -l fr -o fr.json --provider openai --openai-model gpt-3.5-turbo

Metadatos de traducción

Cuando se habilita con la bandera --metadata, translator-ai agrega metadatos para ayudar a rastrear las traducciones:

{
  "_translator_metadata": {
    "tool": "translator-ai v1.1.0",
    "repository": "https://github.com/DatanoiseTV/translator-ai",
    "provider": "Google Gemini",
    "source_language": "English",
    "target_language": "fr",
    "timestamp": "2025-06-20T12:34:56.789Z",
    "total_strings": 42,
    "source_file": "en.json"
  },
  "greeting": "Bonjour",
  "farewell": "Au revoir"
}

Los metadatos están deshabilitados por defecto para garantizar compatibilidad con los analizadores i18n. Usa --metadata para habilitarlos.

Ordenamiento de claves

Usa la bandera --sort-keys para ordenar todas las claves JSON alfabéticamente en la salida:

translator-ai en.json -l es -o es.json --sort-keys

Esto garantiza un orden consistente en todas las traducciones y hace que los diffs sean más limpios. Las claves se ordenan:

  • Sin distinción de mayúsculas y minúsculas (a, B, c, no B, a, c)
  • Recursivamente a través de todos los objetos anidados
  • Los arreglos mantienen el orden de sus elementos

Verificación de claves

Usa la bandera --check-keys para garantizar la integridad de la traducción:

translator-ai en.json -l es -o es.json --check-keys

Esta función:

  • Verifica que todas las claves de origen existan en la salida traducida
  • Informa cualquier clave faltante con sus rutas completas
  • Sale con código de error 1 si falta alguna clave
  • Ayuda a detectar fallos de la API de traducción o problemas de formato
  • Ignora las claves de metadatos al verificar

Códigos de idioma admitidos

Debería admitir cualquier código de idioma estandarizado.

Cómo funciona

  1. Análisis: Lee y aplana tu estructura JSON en rutas
  2. Deduplicación: Al procesar múltiples archivos, identifica cadenas compartidas
  3. Caché: Verifica la caché para cadenas previamente traducidas
  4. Diferenciación: Identifica cadenas nuevas o modificadas que necesitan traducción
  5. Agrupación por lotes: Agrupa cadenas únicas en tamaños de lote óptimos para la eficiencia de la API
  6. Traducción: Envía lotes al proveedor seleccionado (API de Gemini u Ollama local)
  7. Reconstrucción: Reconstruye la estructura JSON exacta con las traducciones
  8. Caché: Actualiza la caché con nuevas traducciones para uso futuro

Deduplicación de múltiples archivos

Al traducir múltiples archivos, translator-ai automáticamente:

  • Identifica cadenas duplicadas entre archivos
  • Traduce cada cadena única solo una vez
  • Aplica la misma traducción de manera consistente en todos los archivos
  • Ahorra llamadas significativas a la API y garantiza consistencia

Ejemplo: ¡Si 10 archivos comparten el 50% de sus cadenas, ahorras ~50% en llamadas a la API!

Gestión de caché

Ubicaciones de caché predeterminadas

  • Windows: %APPDATA%\translator-ai\translation-cache.json
  • macOS: ~/Library/Caches/translator-ai/translation-cache.json
  • Linux: ~/.cache/translator-ai/translation-cache.json

El archivo de caché almacena traducciones indexadas por:

  • Ruta del archivo de origen
  • Idioma de destino
  • Hash SHA-256 de la cadena de origen

Esto garantiza que:

  • Las cadenas modificadas se vuelvan a traducir
  • Las cadenas eliminadas se eliminen de la caché
  • Múltiples proyectos puedan compartir la misma caché sin conflictos

Comparación de proveedores

Google Gemini

  • Ventajas: Rápido, preciso, maneja lotes grandes de manera eficiente
  • Desventajas: Requiere clave de API, tiene costos de uso
  • Modelos disponibles:
    • gemini-2.0-flash-lite (predeterminado) - Más rápido, más rentable
    • gemini-pro - Rendimiento equilibrado
    • gemini-1.5-pro - Capacidades avanzadas
    • gemini-1.5-flash - Rápido con buena calidad
  • Ideal para: Uso en producción, proyectos grandes, cuando la precisión es crítica

Ollama (local)

  • Ventajas: Gratuito, se ejecuta localmente, sin límites de API, respeta la privacidad
  • Desventajas: Más lento, requiere recursos locales, necesita descargar el modelo
  • Ideal para: Desarrollo, datos sensibles a la privacidad, proyectos conscientes de costos

Consejos de rendimiento

  1. Usa la caché (habilitada por defecto) para minimizar las llamadas a la API
  2. Procesa múltiples archivos en la misma sesión para aprovechar la caché caliente
  3. Usa la bandera --stats para monitorear el rendimiento y las oportunidades de optimización
  4. Mantén los archivos de origen consistentes para maximizar los aciertos de caché
  5. Para Ollama: Usa una máquina potente para un mejor rendimiento

Límites de API y costos

API de Gemini

  • Usa el modelo Gemini 2.0 Flash Lite para velocidad y costo óptimos
  • Elige el tamaño de lote óptimo dinámicamente según la cantidad de claves de entrada
  • Agrupa hasta 100 cadenas por llamada a la API
  • Consulta los precios de Google para conocer las tarifas actuales

Ollama

  • Sin costos de API: se ejecuta completamente en tu hardware
  • El rendimiento depende de las capacidades de tu máquina
  • Admite varios modelos con diferentes compensaciones de velocidad/calidad

Uso con Model Context Protocol (MCP)

translator-ai se puede usar como servidor MCP, lo que permite que asistentes de IA como Claude Desktop traduzcan archivos directamente.

Configuración de MCP

Agrega a tu configuración de Claude Desktop:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "translator-ai": {
      "command": "npx",
      "args": [
        "-y",
        "translator-ai-mcp"
      ],
      "env": {
        "GEMINI_API_KEY": "your-gemini-api-key-here"
        // Or for Ollama:
        // "TRANSLATOR_PROVIDER": "ollama"
      }
    }
  }
}

Ejemplos de uso con MCP

Una vez configurado, puedes pedirle a Claude que traduzca archivos:

Human: Can you translate my English locale file to Spanish?

Claude: I'll translate your English locale file to Spanish using translator-ai.

<use_tool name="translate_json">
{
  "inputFile": "locales/en.json",
  "targetLanguage": "es",
  "outputFile": "locales/es.json"
}
</use_tool>

Successfully translated! The file has been saved to locales/es.json.

Para múltiples archivos con deduplicación:

Human: Translate all my English JSON files in the locales folder to German.

Claude: I'll translate all your English JSON files to German with deduplication.

<use_tool name="translate_multiple">
{
  "pattern": "locales/en/*.json",
  "targetLanguage": "de",
  "outputPattern": "locales/de/{name}.json",
  "showStats": true
}
</use_tool>

Translation complete! Processed 5 files with 23% deduplication savings.

Herramientas MCP disponibles

  1. translate_json: Traducir un solo archivo JSON

    • inputFile: Ruta al archivo de origen
    • targetLanguage: Código de idioma de destino
    • outputFile: Ruta del archivo de salida
  2. translate_multiple: Traducir múltiples archivos con deduplicación

    • pattern: Patrón de archivo o rutas
    • targetLanguage: Código de idioma de destino
    • outputPattern: Patrón de salida con variables {dir}, {name}, {lang}
    • showStats: Mostrar estadísticas de deduplicación (opcional)

Integración con generadores de sitios estáticos

Trabajo con archivos YAML (Hugo, Jekyll, etc.)

Dado que translator-ai trabaja con archivos JSON, necesitarás convertir YAML a JSON y viceversa. Aquí tienes un flujo de trabajo práctico:

Configurar herramientas de conversión YAML

# Install yaml conversion tools
npm install -g js-yaml
# or
pip install pyyaml

Ejemplo de Hugo con conversión YAML

  1. Crea un script de traducción (translate-hugo.sh):
#!/bin/bash
# translate-hugo.sh - Translate Hugo YAML i18n files

# Function to translate YAML file
translate_yaml() {
  local input_file=$1
  local lang=$2
  local output_file=$3
  
  echo "Translating $input_file to $lang..."
  
  # Convert YAML to JSON
  npx js-yaml $input_file > temp_input.json
  
  # Translate JSON
  translator-ai temp_input.json -l $lang -o temp_output.json
  
  # Convert back to YAML
  npx js-yaml temp_output.json > $output_file
  
  # Cleanup
  rm temp_input.json temp_output.json
}

# Translate Hugo i18n files
translate_yaml themes/your-theme/i18n/en.yaml es themes/your-theme/i18n/es.yaml
translate_yaml themes/your-theme/i18n/en.yaml fr themes/your-theme/i18n/fr.yaml
translate_yaml themes/your-theme/i18n/en.yaml de themes/your-theme/i18n/de.yaml
  1. Convertidor basado en Python para escenarios más complejos:
#!/usr/bin/env python3
# hugo-translate.py

import yaml
import json
import subprocess
import sys
import os

def yaml_to_json(yaml_file):
    """Convert YAML to JSON"""
    with open(yaml_file, 'r', encoding='utf-8') as f:
        data = yaml.safe_load(f)
    return json.dumps(data, ensure_ascii=False, indent=2)

def json_to_yaml(json_str):
    """Convert JSON back to YAML"""
    data = json.loads(json_str)
    return yaml.dump(data, allow_unicode=True, default_flow_style=False)

def translate_yaml_file(input_yaml, target_lang, output_yaml):
    """Translate a YAML file using translator-ai"""
    
    # Create temp JSON file
    temp_json_in = 'temp_in.json'
    temp_json_out = f'temp_out_{target_lang}.json'
    
    try:
        # Convert YAML to JSON
        json_content = yaml_to_json(input_yaml)
        with open(temp_json_in, 'w', encoding='utf-8') as f:
            f.write(json_content)
        
        # Run translator-ai
        cmd = [
            'translator-ai',
            temp_json_in,
            '-l', target_lang,
            '-o', temp_json_out
        ]
        subprocess.run(cmd, check=True)
        
        # Read translated JSON and convert back to YAML
        with open(temp_json_out, 'r', encoding='utf-8') as f:
            translated_json = f.read()
        
        yaml_content = json_to_yaml(translated_json)
        
        # Write YAML output
        with open(output_yaml, 'w', encoding='utf-8') as f:
            f.write(yaml_content)
        
        print(f"✓ Translated {input_yaml} to {output_yaml}")
        
    finally:
        # Cleanup temp files
        for f in [temp_json_in, temp_json_out]:
            if os.path.exists(f):
                os.remove(f)

# Usage
if __name__ == "__main__":
    languages = ['es', 'fr', 'de', 'ja']
    
    for lang in languages:
        translate_yaml_file(
            'i18n/en.yaml',
            lang,
            f'i18n/{lang}.yaml'
        )

Solución Node.js con manejo adecuado de YAML

Crea translate-yaml.js:

#!/usr/bin/env node
const fs = require('fs');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const path = require('path');

function translateYamlFile(inputPath, targetLang, outputPath) {
  console.log(`Translating ${inputPath} to ${targetLang}...`);
  
  // Read and parse YAML
  const yamlContent = fs.readFileSync(inputPath, 'utf8');
  const data = yaml.load(yamlContent);
  
  // Write temporary JSON
  const tempJsonIn = `temp_${path.basename(inputPath)}.json`;
  const tempJsonOut = `temp_${path.basename(inputPath)}_${targetLang}.json`;
  
  fs.writeFileSync(tempJsonIn, JSON.stringify(data, null, 2));
  
  try {
    // Translate using translator-ai
    execSync(`translator-ai ${tempJsonIn} -l ${targetLang} -o ${tempJsonOut}`);
    
    // Read translated JSON
    const translatedData = JSON.parse(fs.readFileSync(tempJsonOut, 'utf8'));
    
    // Convert back to YAML
    const translatedYaml = yaml.dump(translatedData, {
      indent: 2,
      lineWidth: -1,
      noRefs: true
    });
    
    // Write output YAML
    fs.writeFileSync(outputPath, translatedYaml);
    console.log(`✓ Created ${outputPath}`);
    
  } finally {
    // Cleanup
    [tempJsonIn, tempJsonOut].forEach(f => {
      if (fs.existsSync(f)) fs.unlinkSync(f);
    });
  }
}

// Example usage
const languages = ['es', 'fr', 'de'];
languages.forEach(lang => {
  translateYamlFile(
    'i18n/en.yaml',
    lang,
    `i18n/${lang}.yaml`
  );
});

Flujo de trabajo real con Hugo

Hugo admite dos métodos de traducción: por nombre de archivo (about.en.md, about.fr.md) o por directorio de contenido (content/en/, content/fr/). Así es como se automatizan ambos:

Método 1: Traducción por nombre de archivo

Crea hugo-translate-files.sh:

#!/bin/bash
# Translate Hugo content files using filename convention

SOURCE_LANG="en"
TARGET_LANGS=("es" "fr" "de" "ja")

# Find all English content files
find content -name "*.${SOURCE_LANG}.md" | while read -r file; do
  # Extract base filename without language suffix
  base_name="${file%.${SOURCE_LANG}.md}"
  
  for lang in "${TARGET_LANGS[@]}"; do
    output_file="${base_name}.${lang}.md"
    
    # Skip if translation already exists
    if [ -f "$output_file" ]; then
      echo "Skipping $output_file (already exists)"
      continue
    fi
    
    # Extract front matter
    awk '/^---$/{p=1; next} p&&/^---$/{exit} p' "$file" > temp_frontmatter.yaml
    
    # Convert front matter to JSON
    npx js-yaml temp_frontmatter.yaml > temp_frontmatter.json
    
    # Translate front matter
    translator-ai temp_frontmatter.json -l "$lang" -o "temp_translated.json"
    
    # Convert back to YAML
    echo "---" > "$output_file"
    npx js-yaml temp_translated.json >> "$output_file"
    echo "---" >> "$output_file"
    
    # Copy content (you might want to translate this too)
    awk '/^---$/{p++} p==2{print}' "$file" | tail -n +2 >> "$output_file"
    
    echo "Created $output_file"
  done
  
  # Cleanup
  rm -f temp_frontmatter.yaml temp_frontmatter.json temp_translated.json
done

Método 2: Traducción por directorio de contenido

  1. Configura Hugo (config.yaml):
defaultContentLanguage: en
defaultContentLanguageInSubdir: false

languages:
  en:
    contentDir: content/en
    languageName: English
    weight: 1
  es:
    contentDir: content/es
    languageName: Español
    weight: 2
  fr:
    contentDir: content/fr
    languageName: Français
    weight: 3

# Rest of your config...
  1. Crea el script de traducción (hugo-translate-dirs.js):
#!/usr/bin/env node
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const glob = require('glob');

const SOURCE_LANG = 'en';
const TARGET_LANGS = ['es', 'fr', 'de'];

async function translateHugoContent() {
  // Ensure target directories exist
  for (const lang of TARGET_LANGS) {
    await fs.ensureDir(`content/${lang}`);
  }
  
  // Find all content files in source language
  const files = glob.sync(`content/${SOURCE_LANG}/**/*.md`);
  
  for (const file of files) {
    const relativePath = path.relative(`content/${SOURCE_LANG}`, file);
    
    for (const lang of TARGET_LANGS) {
      const targetFile = path.join(`content/${lang}`, relativePath);
      
      // Skip if already translated
      if (await fs.pathExists(targetFile)) {
        console.log(`Skipping ${targetFile} (exists)`);
        continue;
      }
      
      await translateFile(file, targetFile, lang);
    }
  }
}

async function translateFile(sourceFile, targetFile, targetLang) {
  console.log(`Translating ${sourceFile} to ${targetLang}...`);
  
  const content = await fs.readFile(sourceFile, 'utf8');
  const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  
  if (!frontMatterMatch) {
    // No front matter, just copy
    await fs.ensureDir(path.dirname(targetFile));
    await fs.copyFile(sourceFile, targetFile);
    return;
  }
  
  // Parse front matter
  const frontMatter = yaml.load(frontMatterMatch[1]);
  const body = content.substring(frontMatterMatch[0].length);
  
  // Extract translatable fields
  const translatable = {
    title: frontMatter.title || '',
    description: frontMatter.description || '',
    summary: frontMatter.summary || '',
    keywords: frontMatter.keywords || []
  };
  
  // Save for translation
  await fs.writeJson('temp_meta.json', translatable);
  
  // Translate
  execSync(`translator-ai temp_meta.json -l ${targetLang} -o temp_translated.json`);
  
  // Read translations
  const translated = await fs.readJson('temp_translated.json');
  
  // Update front matter
  Object.assign(frontMatter, translated);
  
  // Write translated file
  await fs.ensureDir(path.dirname(targetFile));
  const newContent = `---\n${yaml.dump(frontMatter)}---${body}`;
  await fs.writeFile(targetFile, newContent);
  
  // Cleanup
  await fs.remove('temp_meta.json');
  await fs.remove('temp_translated.json');
  
  console.log(`✓ Created ${targetFile}`);
}

// Run translation
translateHugoContent().catch(console.error);

Traducción de archivos i18n de Hugo

  1. Instala las dependencias:
npm install -g translator-ai js-yaml
  1. Crea un Makefile para facilitar la traducción:
# Makefile for Hugo translations
LANGUAGES := es fr de ja zh
SOURCE_YAML := i18n/en.yaml
THEME_DIR := themes/your-theme

.PHONY: translate
translate: $(foreach lang,$(LANGUAGES),translate-$(lang))

translate-%:
	@echo "Translating to $*..."
	@npx js-yaml $(SOURCE_YAML) > temp.json
	@translator-ai temp.json -l $* -o temp_$*.json
	@npx js-yaml temp_$*.json > i18n/$*.yaml
	@rm temp.json temp_$*.json
	@echo "✓ Created i18n/$*.yaml"

.PHONY: translate-theme
translate-theme:
	@for lang in $(LANGUAGES); do \
		make translate-theme-$$lang; \
	done

translate-theme-%:
	@echo "Translating theme to $*..."
	@npx js-yaml $(THEME_DIR)/i18n/en.yaml > temp_theme.json
	@translator-ai temp_theme.json -l $* -o temp_theme_$*.json
	@npx js-yaml temp_theme_$*.json > $(THEME_DIR)/i18n/$*.yaml
	@rm temp_theme.json temp_theme_$*.json

.PHONY: clean
clean:
	@rm -f temp*.json

# Translate everything
.PHONY: all
all: translate translate-theme

Uso:

# Translate to all languages
make all

# Translate to specific language
make translate-es

# Translate theme files
make translate-theme

Flujo de trabajo completo de traducción con Hugo

Aquí tienes un script completo que maneja tanto contenido como traducciones i18n:

#!/usr/bin/env node
// hugo-complete-translator.js
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const glob = require('glob');

class HugoTranslator {
  constructor(targetLanguages = ['es', 'fr', 'de']) {
    this.targetLanguages = targetLanguages;
    this.tempFiles = [];
  }

  async translateSite() {
    console.log('Starting Hugo site translation...\n');
    
    // 1. Translate i18n files
    await this.translateI18nFiles();
    
    // 2. Translate content
    await this.translateContent();
    
    // 3. Update config
    await this.updateConfig();
    
    console.log('\nTranslation complete!');
  }

  async translateI18nFiles() {
    console.log('Translating i18n files...');
    const i18nFiles = glob.sync('i18n/en.{yaml,yml,toml}');
    
    for (const file of i18nFiles) {
      const ext = path.extname(file);
      
      for (const lang of this.targetLanguages) {
        const outputFile = `i18n/${lang}${ext}`;
        
        if (await fs.pathExists(outputFile)) {
          console.log(`  Skipping ${outputFile} (exists)`);
          continue;
        }
        
        // Convert to JSON
        const tempJson = `temp_i18n_${lang}.json`;
        await this.convertToJson(file, tempJson);
        
        // Translate
        const translatedJson = `temp_i18n_${lang}_translated.json`;
        execSync(`translator-ai ${tempJson} -l ${lang} -o ${translatedJson}`);
        
        // Convert back
        await this.convertFromJson(translatedJson, outputFile, ext);
        
        // Cleanup
        await fs.remove(tempJson);
        await fs.remove(translatedJson);
        
        console.log(`  ✓ Created ${outputFile}`);
      }
    }
  }

  async translateContent() {
    console.log('\nTranslating content...');
    
    // Detect translation method
    const useContentDirs = await fs.pathExists('content/en');
    
    if (useContentDirs) {
      await this.translateContentByDirectory();
    } else {
      await this.translateContentByFilename();
    }
  }

  async translateContentByDirectory() {
    const files = glob.sync('content/en/**/*.md');
    
    for (const file of files) {
      const relativePath = path.relative('content/en', file);
      
      for (const lang of this.targetLanguages) {
        const targetFile = path.join('content', lang, relativePath);
        
        if (await fs.pathExists(targetFile)) continue;
        
        await this.translateMarkdownFile(file, targetFile, lang);
      }
    }
  }

  async translateContentByFilename() {
    const files = glob.sync('content/**/*.en.md');
    
    for (const file of files) {
      const baseName = file.replace('.en.md', '');
      
      for (const lang of this.targetLanguages) {
        const targetFile = `${baseName}.${lang}.md`;
        
        if (await fs.pathExists(targetFile)) continue;
        
        await this.translateMarkdownFile(file, targetFile, lang);
      }
    }
  }

  async translateMarkdownFile(sourceFile, targetFile, targetLang) {
    const content = await fs.readFile(sourceFile, 'utf8');
    const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
    
    if (!frontMatterMatch) {
      await fs.copy(sourceFile, targetFile);
      return;
    }
    
    const frontMatter = yaml.load(frontMatterMatch[1]);
    const body = content.substring(frontMatterMatch[0].length);
    
    // Translate front matter
    const translatable = this.extractTranslatableFields(frontMatter);
    const tempJson = `temp_content_${path.basename(sourceFile)}.json`;
    const translatedJson = `${tempJson}.translated`;
    
    await fs.writeJson(tempJson, translatable);
    execSync(`translator-ai ${tempJson} -l ${targetLang} -o ${translatedJson}`);
    
    const translated = await fs.readJson(translatedJson);
    Object.assign(frontMatter, translated);
    
    // Write translated file
    await fs.ensureDir(path.dirname(targetFile));
    const newContent = `---\n${yaml.dump(frontMatter)}---${body}`;
    await fs.writeFile(targetFile, newContent);
    
    // Cleanup
    await fs.remove(tempJson);
    await fs.remove(translatedJson);
    
    console.log(`  ✓ ${targetFile}`);
  }

  extractTranslatableFields(frontMatter) {
    const fields = ['title', 'description', 'summary', 'keywords', 'tags'];
    const translatable = {};
    
    fields.forEach(field => {
      if (frontMatter[field]) {
        translatable[field] = frontMatter[field];
      }
    });
    
    return translatable;
  }

  async convertToJson(inputFile, outputFile) {
    const ext = path.extname(inputFile);
    const content = await fs.readFile(inputFile, 'utf8');
    let data;
    
    if (ext === '.yaml' || ext === '.yml') {
      data = yaml.load(content);
    } else if (ext === '.toml') {
      // You'd need a TOML parser here
      throw new Error('TOML support not implemented in this example');
    }
    
    await fs.writeJson(outputFile, data, { spaces: 2 });
  }

  async convertFromJson(inputFile, outputFile, format) {
    const data = await fs.readJson(inputFile);
    let content;
    
    if (format === '.yaml' || format === '.yml') {
      content = yaml.dump(data, { 
        indent: 2, 
        lineWidth: -1,
        noRefs: true 
      });
    } else if (format === '.toml') {
      throw new Error('TOML support not implemented in this example');
    }
    
    await fs.writeFile(outputFile, content);
  }

  async updateConfig() {
    console.log('\nUpdating Hugo config...');
    
    const configFile = glob.sync('config.{yaml,yml,toml,json}')[0];
    if (!configFile) return;
    
    // This is a simplified example - you'd need to properly parse and update
    console.log('  ! Remember to update your config.yaml with language settings');
  }
}

// Run the translator
if (require.main === module) {
  const translator = new HugoTranslator(['es', 'fr', 'de']);
  translator.translateSite().catch(console.error);
}

module.exports = HugoTranslator;

Uso con módulos de Hugo

Si estás usando módulos de Hugo, puedes crear un módulo de traducción:

// go.mod
module github.com/yourusername/hugo-translator

go 1.19

require (
    github.com/yourusername/your-theme v1.0.0
)

Luego en tu package.json:

{
  "scripts": {
    "translate": "node hugo-complete-translator.js",
    "translate:content": "node hugo-complete-translator.js --content-only",
    "translate:i18n": "node hugo-complete-translator.js --i18n-only",
    "build": "npm run translate && hugo"
  }
}

Jekyll con YAML Front Matter

Para publicaciones de Jekyll con YAML front matter:

#!/usr/bin/env python3
# translate-jekyll-posts.py

import os
import yaml
import json
import subprocess
import frontmatter

def translate_jekyll_post(post_path, target_lang, output_dir):
    """Translate Jekyll post including front matter"""
    
    # Load post with front matter
    post = frontmatter.load(post_path)
    
    # Extract translatable front matter fields
    translatable = {
        'title': post.metadata.get('title', ''),
        'description': post.metadata.get('description', ''),
        'excerpt': post.metadata.get('excerpt', '')
    }
    
    # Save as JSON for translation
    with open('temp_meta.json', 'w', encoding='utf-8') as f:
        json.dump(translatable, f, ensure_ascii=False, indent=2)
    
    # Translate
    subprocess.run([
        'translator-ai',
        'temp_meta.json',
        '-l', target_lang,
        '-o', f'temp_meta_{target_lang}.json'
    ])
    
    # Load translations
    with open(f'temp_meta_{target_lang}.json', 'r', encoding='utf-8') as f:
        translations = json.load(f)
    
    # Update post metadata
    for key, value in translations.items():
        if value:  # Only update if translation exists
            post.metadata[key] = value
    
    # Add language to metadata
    post.metadata['lang'] = target_lang
    
    # Save translated post
    output_path = os.path.join(output_dir, os.path.basename(post_path))
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(frontmatter.dumps(post))
    
    # Cleanup
    os.remove('temp_meta.json')
    os.remove(f'temp_meta_{target_lang}.json')

# Translate all posts
for lang in ['es', 'fr', 'de']:
    os.makedirs(f'_posts/{lang}', exist_ok=True)
    for post in os.listdir('_posts/en'):
        if post.endswith('.md'):
            translate_jekyll_post(
                f'_posts/en/{post}',
                lang,
                f'_posts/{lang}'
            )

Consejos para la conversión YAML/JSON

  1. Preservar el formato: Usa js-yaml con las opciones adecuadas para mantener la estructura YAML
  2. Manejar caracteres especiales: Asegura la codificación adecuada (UTF-8) en todo momento
  3. Validar la salida: Algunas características de YAML (anclas, alias) pueden requerir un manejo especial
  4. Considerar TOML: Para Hugo, es posible que también necesites manejar archivos de configuración TOML

Alternativa: Soporte directo de YAML (solicitud de función)

Si trabajas frecuentemente con archivos YAML, considera crear un script contenedor que maneje la conversión automáticamente, o solicita soporte YAML como una función para translator-ai.

Desarrollo

Compilar desde el código fuente

git clone https://github.com/DatanoiseTV/translator-ai.git
cd translator-ai
npm install
npm run build

Pruebas locales

npm start -- test.json -l es -o output.json

Licencia

Este proyecto requiere atribución tanto para uso comercial como no comercial. Consulta el archivo LICENSE para más detalles.

Contribuciones

¡Las contribuciones son bienvenidas! No dudes en enviar un Pull Request.

Soporte

Para problemas, preguntas o sugerencias, abre un issue en GitHub.

Si encuentras útil esta herramienta, considera apoyar el desarrollo:

Buy Me A Coffee