Traducción de sitios web Astro a escala: cómo automatizar la i18n con CSV

Traducción de sitios web Astro a escala: cómo automatizar la i18n con CSV

14 de mayo de 2026

Conclusión: un sitio Astro multilingüe es un problema estructural antes que un problema de traducción. Configura carpetas de contenido limpias por locale, extrae cada fragmento traducible en un solo CSV, pide a AI Glot en una frase que rellene cada columna de idioma utilizando la traducción de CSV de AI Glot y luego reensambla los archivos programáticamente. Una vez establecida la canalización, añadir un nuevo idioma es una operación de un solo CSV.

Este es el flujo de trabajo exacto que utilizamos para mantener ai-glot.com en seis idiomas (inglés, francés, alemán, español, italiano, portugués) sin reescribir ni un solo artículo del blog a mano. Lo analizaremos de principio a fin, con los prompts que utilizamos para estructurar cada paso con un asistente de codificación IA como Claude Code o Cursor.

El objetivo

Al final de esta guía tendrás:

  1. Una estructura de enrutamiento i18n limpia en tu proyecto Astro, con una carpeta por idioma.
  2. Un script repetible que extrae cada cadena traducible de un archivo markdown (o de una exportación de CMS) a un único CSV.
  3. Un CSV traducido producido en AI Glot, completado en una sola pasada en todas las columnas de idioma.
  4. Un segundo script que reensambla los archivos markdown traducidos, con las rutas de imágenes y los enlaces internos corregidos para los subdirectorios localizados.
  5. Un archivo de habilidades que puedes entregar a tu asistente de IA para que cada nuevo artículo sea traducible por defecto.

Si ya tienes Weglot, Crowdin o un CMS headless que exporte a CSV, puedes saltarte el script de extracción de markdown y cargar esas exportaciones directamente en AI Glot. La lógica de traducción y reensamblaje es la misma para cualquier proyecto de traducción de contenido de CMS.

Requisitos previos

Una breve lista de comprobación antes de empezar.

  • Un proyecto Astro (v4 o v5) ejecutándose localmente, con colecciones de contenido configuradas para tu blog.
  • Node 20 o superior, para que las API modernas de CSV y fs/promises funcionen correctamente.
  • Una cuenta de AI Glot (el nivel gratuito es suficiente para el primer lote).
  • Un asistente de IA en tu editor para generar el código base (Claude Code, Cursor, Copilot, Codeium, el que prefieras).

Por qué Astro es ideal para el multilingüismo a escala

Astro renderiza HTML estático por defecto, lo que significa que cada página localizada es simplemente un archivo en el disco. No hay una capa de traducción en tiempo de ejecución, ni búsquedas en bases de datos para el selector de idioma, ni paquetes de JavaScript que descargar por locale. Google indexa cada versión de idioma como una URL distinta con sus propias metaetiquetas, y el resultado de la compilación es básicamente un espejo traducido de tu árbol en inglés.

Esto es exactamente lo que buscas para el SEO. El SEO multilingüe funciona mejor cuando cada idioma tiene su propia página generada estáticamente y rastreable, con anotaciones hreflang adecuadas y una estructura de URL limpia. Astro te da eso gratis.

La desventaja: tú eres responsable de mantener sincronizados los archivos de contenido en todos los idiomas. Ese es precisamente el problema que resuelve el flujo de trabajo con CSV que veremos a continuación.

Parte 1: Configurar la estructura de rutas de i18n

Astro 4 introdujo una configuración de i18n nativa. El patrón más limpio es declarar tus locales, establecer que el predeterminado no tenga prefijo (de modo que el inglés esté en /blog/my-post y el francés en /fr/blog/my-post) y reflejar tus colecciones de contenido en una carpeta [lang]/ por idioma.

Aquí tienes una estructura objetivo para un blog con seis idiomas:

src/
  content/
    blog/
      live/                          # English (default locale)
        my-first-post.md
      fr/live/                       # French
        my-first-post.md
      de/live/                       # German
        my-first-post.md
      es/live/                       # Spanish
        my-first-post.md
      it/live/                       # Italian
        my-first-post.md
      pt/live/                       # Portuguese
        my-first-post.md
  pages/
    blog/[slug].astro                # English routes
    [lang]/blog/[slug].astro         # Localized routes
  i18n/
    ui.ts                            # UI string dictionary per locale

Fíjate en que los archivos markdown localizados están un nivel de carpeta más profundos que los de inglés. Esa profundidad es importante: las rutas relativas de las imágenes en el markdown localizado necesitan un ../ adicional para llegar a src/assets/. El script de reensamblaje de la Parte 4 gestiona esto automáticamente.

Si prefieres no escribir este andamiaje tú mismo, entrega el siguiente prompt a tu asistente de programación con IA. Es intencionadamente genérico para que funcione en cualquier proyecto de Astro y plantea las preguntas aclaratorias adecuadas antes de generar el código.

You are helping me scaffold internationalization (i18n) in an existing Astro
project. I want to translate my entire site into multiple languages while
keeping clean URLs, proper hreflang annotations, and per-locale static pages.

Goals:
1. Declare locales in astro.config.mjs using the built-in `i18n` option.
   Default locale should NOT be prefixed (e.g. English lives at `/blog/...`,
   French at `/fr/blog/...`).
2. Create a per-locale folder structure for my content collections so each
   blog post (or CMS item) has a 1:1 file per language. The English files
   stay at their current location; localized files live one folder deeper
   (e.g. `src/content/blog/fr/live/`).
3. Add a typed UI string dictionary at `src/i18n/ui.ts` that exports an object
   keyed by locale, plus a `useTranslations(lang)` helper.
4. Update the content collection schema so the same Zod definition applies to
   every language folder.
5. Generate the dynamic route file `src/pages/[lang]/blog/[slug].astro` that
   reads from the localized content collection and falls back to English when
   a localized version is missing.
6. Add a `<LanguageSwitcher />` component that renders an anchor per locale,
   preserving the current pathname.
7. Inject `<link rel="alternate" hreflang="..." />` tags into the layout for
   every locale that has the page available.

Before you generate any code, ask me:
- The list of target locales (BCP 47 codes) and which one is the default.
- The path to my existing content collection(s) and current route files.
- Whether I want a language switcher in the header, footer, or both.
- Whether I store UI strings in JSON, TS, or somewhere else today.

Then make the changes in small, surgical commits. Do not refactor unrelated
code. Show me the diff and explain any decision where there are two valid
patterns (e.g. prefixed vs. non-prefixed default locale).

Este patrón es intencionado: aclarar y luego construir. Al principio probamos el enfoque de “generar todo de una vez” y terminamos con tres componentes distintos para el selector de idioma y un esquema de rutas que chocaba con nuestro esquema de colección de contenido. Pedir al asistente que te pregunte primero ahorra una hora de limpieza posterior.

Parte 2: Extraer el contenido en un CSV de traducción

Script de extracción de traducción automatizado en el editor de Antigravity AI que muestra la lógica por fragmentos

Aquí es donde la mayoría de los equipos se atascan.

Aquí es donde la mayoría de los equipos se atascan. La traducción en sí es la parte fácil. Lo difícil es introducir y extraer el contenido de una herramienta de traducción sin romper la estructura.

El truco para que esto funcione bien en AI Glot es la extracción por fragmentos. Divides cada archivo markdown en las unidades traducibles coherentes más pequeñas (un campo de frontmatter, un par de preguntas frecuentes, un solo párrafo), escribes cada fragmento como una fila en un CSV y añades una columna por idioma de destino. El resultado es así:

path English string fr de es it pt
live/my-first-post.md How to translate an Astro website at scale into many languages
live/my-first-post.md A practical, copy-paste tutorial for shipping a multilingual Astro site.
live/my-first-post.md Astro renders static HTML by default, which means each localized page is just a file on disk.

Matriz CSV multilingüe enriquecida procesada por AI Glot que muestra múltiples columnas traducidas

¿Por qué por fragmentos?

¿Por qué por fragmentos? Porque la traducción por IA funciona mucho mejor con unidades coherentes del tamaño de un párrafo que con documentos completos. Las reglas del glosario se aplican de forma consistente. Los presupuestos de tokens son predecibles. Y si un fragmento necesita un arreglo, editas una celda, no un archivo de 2,000 palabras.

Opción A: Almacenas el contenido en archivos markdown (blog, docs, landing pages)

Usa este prompt para generar un script de extracción adaptado a tu repositorio. Nosotros utilizamos una versión casi idéntica para el sitio web de AI Glot.

Write a Node.js script at `scripts/extract-translations.mjs` that prepares a
CSV for bulk translation in AI Glot.

Inputs:
- A source directory containing English markdown files
  (e.g. `src/content/blog/live/`).
- A list of target locale codes (e.g. `fr de es it pt`).
- An output CSV path (default `multilang-translation.csv`).

For each markdown file:
1. Parse the frontmatter using `gray-matter`.
2. Extract these translatable strings, one per CSV row:
   - `title` (frontmatter)
   - `metaDescription` (frontmatter)
   - For each entry in `faqs`: the `question` and the `answer` as separate rows.
   - Each body paragraph, splitting the markdown body on `\n\n` so paragraphs,
     bullet lists, blockquotes, and headings each become one row.
3. SKIP these chunks (do not include them in the CSV):
   - Pure image lines starting with `![`.
   - Fenced code blocks (between triple backticks).
   - Lines that are only HTML comments or empty after trim.

CSV schema (exact order, UTF-8, RFC 4180 quoting):
`path,English string,<locale1>,<locale2>,...`

Where `path` is the file path relative to the source dir (e.g.
`live/my-first-post.md`), `English string` is the chunk, and every target
locale column is left EMPTY. AI Glot will fill them.

Rules:
- Use `papaparse` for CSV writing so quoting and escaping are correct.
- Preserve the exact whitespace and punctuation of the source chunk; do not
  trim trailing punctuation or collapse internal whitespace.
- Deduplicate rows that have the same `path` + `English string` pair
  (sometimes a paragraph appears twice in a post).
- Print a summary at the end: number of files processed, total rows,
  estimated word count.

Make it a single self-contained file with no dependencies beyond
`gray-matter`. Usage:
`node scripts/extract-translations.mjs <source-file> <output.csv> <locales...>`

Ejecútalo una vez y obtendrás un único archivo multilang-translation.csv que captura cada cadena traducible de tu blog, listo para subir.

Opción B: Tu contenido reside en un CMS (Webflow, Notion, Sanity, Strapi, Airtable)

Si usas un CMS headless, no necesitas ningún script de extracción. La mayoría de las plataformas de CMS modernas ya pueden exportar un CSV de tu colección de contenidos con una columna por campo. Solo tienes que ajustar la exportación al formato que AI Glot espera.

Los dos formatos más comunes que encontrarás:

  • Una fila por elemento, una columna por campo (la exportación típica de Webflow). Indica a AI Glot en una frase qué campos de texto traducir, dejando intactos los IDs, slugs y timestamps. Hemos analizado este patrón exacto en Cómo traducir una exportación de Webflow CMS con IA.
  • Una fila por cadena, una columna por idioma (una exportación creada manualmente). Este es el formato que usamos para nuestro blog en Astro.

Si la exportación de tu CMS tiene el primer formato y prefieres el segundo (es más rápido cuando quieres traducir a muchos idiomas a la vez), este prompt realiza la conversión:

Write a Node.js script `scripts/cms-to-translation-csv.mjs` that reshapes a
flat CMS export into a one-column-per-language CSV ready for AI Glot.

Inputs:
- A source CSV exported from my CMS, with one row per content item and many
  columns per item (e.g. `id, slug, title, metaDescription, body, faq_1_q,
  faq_1_a, faq_2_q, faq_2_a, ...`).
- A list of columns to translate (passed as a CLI arg, comma-separated).
- A list of target locales (passed as a CLI arg).
- An output CSV path.

For each row in the input:
- For each column in the "translate" list that has a non-empty value, emit
  one row in the output CSV with:
    - `id`: the original row id (or slug) for traceability
    - `field`: the original column name
    - `English string`: the cell value
    - one empty column per target locale
- Skip empty cells; preserve all whitespace inside non-empty cells.

Use `papaparse` for both read and write. Make IDs and field names safe to
parse back (no quoting issues). Print a row count and a per-field breakdown.

Usage:
`node scripts/cms-to-translation-csv.mjs in.csv out.csv "title,metaDescription,body" "fr,de,es,it,pt"`

Independientemente de la opción que elijas, terminarás con el mismo formato de CSV: una columna de origen y una columna por idioma de destino, listo para subir a AI Glot.

Parte 3: Traducir el CSV con AI Glot

Esta es la parte que lleva minutos, no días. Sube tu archivo multilang-translation.csv a tu cuenta de AI Glot y deja que la plataforma te guíe en la configuración.

El flujo de trabajo:

  1. Sube el CSV. AI Glot analiza la estructura del archivo: tipos de columna, idiomas detectados, valores de muestra, recuento total de palabras y estimación de créditos.
  2. Describe el trabajo. Indica a AI Glot, en una frase, que traduzca English string a las columnas fr, de, es, it, pt y que deje path intacto.
  3. Revisa el plan. AI Glot muestra los idiomas, el alcance, el recuento de palabras y el coste antes de gastar nada. Puedes solicitar un cambio y volverá a planificarlo.
  4. Añade términos al glosario (opcional pero recomendado). Nombres de marca, nombres de productos o vocabulario técnico que quieras mantener coherente en miles de filas. Los glosarios son la diferencia entre una traducción que suena como tu marca y una que suena como una herramienta de traducción genérica. Consulta Cómo los glosarios de traducción mejoran la localización de CSV para ver los patrones que funcionan.
  5. Añade instrucciones personalizadas (opcional). Le decimos a AI Glot cosas como: “Usa el tono de un fundador seguro de sí mismo que escribe una guía técnica; mantén el formato markdown y los enlaces internos intactos; nunca traduzcas fragmentos de código ni URLs”.
  6. Lanzamiento. AI Glot procesa el archivo fila por fila, rellena cada columna de destino y genera un CSV traducido que puedes descargar.
Instructions for this batchOptional
On top of your glossaryApply
Describe el trabajo en inglés sencillo. AI Glot lo convierte en un plan que puedes revisar y corregir antes de ejecutarlo.

Un blog de 30 artículos con cinco idiomas de destino suele procesarse en unos pocos minutos en modo Standard, y puedes cambiar al modo Pro para obtener una calidad superior en las filas más importantes (títulos, meta descripciones, párrafos principales).

Consejo: una vez que el archivo esté subido a AI Glot, puedes borrar la versión local de columnas vacías. A partir de ese momento, tu espacio de trabajo es la fuente de verdad y siempre puedes volver a descargarlo.

Página de resultados de traducción de AI Glot que muestra el estado de finalización al 100%

Parte 4: Reensamblar el markdown traducido en Astro

Ahora tienes un archivo multilang-translation_translated.csv con todas las columnas de destino completas. El objetivo es tomar cada fragmento traducido y volver a escribirlo en el archivo markdown localizado correspondiente, preservando exactamente la estructura original.

Aquí es donde la extracción por fragmentos vuelve a dar sus frutos. Como cada fila está vinculada al fragmento original en inglés, simplemente puedes buscar y reemplazar los fragmentos en inglés por su contraparte localizada dentro del archivo fuente original, y luego guardar el resultado en la carpeta localizada. Sin análisis de AST, sin conversiones repetidas de markdown y sin riesgo de romper el formato.

Deben realizarse dos correcciones estructurales durante la reconstrucción:

  1. Rutas de imágenes. Los archivos en inglés referencian los assets con ../../../assets/.... Los archivos localizados están un nivel más profundos en las carpetas, por lo que la ruta relativa pasa a ser ../../../../assets/....
  2. Enlaces internos. Un enlace como /blog/glossary-website-translations en inglés debe convertirse en /fr/blog/glossary-website-translations en francés (y así sucesivamente para cada locale).

Aquí tienes el prompt que utilizamos para generar el script de reensamblado. Es lo suficientemente genérico como para implementarlo en cualquier proyecto de Astro que siga la estructura de la Parte 1.

Use the unified assembly script at `scripts/assemble-blog-translations.mjs` that rebuilds
localized markdown files from an AI Glot translated CSV.

Inputs (CLI args):
- Path to the translated CSV (e.g. `multilang-translation_translated.csv`)
  with columns `path, English string, fr, de, es, it, pt`.
- The source directory containing the original English markdown files
  (default `src/content/blog/live/`).
- The destination directory pattern for localized files
  (default `src/content/blog/{lang}/live/`).
- The site origin used for internal links (default `https://ai-glot.com`).

For each unique `path` value in the CSV:
1. Read the original English file from `<sourceDir>/<path>`.
2. For each target locale column (everything after `English string`):
   a. Start from the English file's full text.
   b. For every row that matches this `path`, find the `English string`
      verbatim in the file and replace it with the value in the target
      locale column. Use literal string replace, not regex. Throw if the
      English chunk is not found (it means the source drifted from the CSV;
      this should fail loudly rather than silently skip).
   c. After all replacements, run two structural fixes on the resulting text:
      - Image paths: replace every occurrence of `../../../assets/` with
        `../../../../assets/` (one extra `../` because localized files live
        one folder deeper).
      - Internal links: replace every absolute internal URL on this site
        (e.g. `https://ai-glot.com/blog/...`) and every root-relative path
        (e.g. `/blog/...`, `/sign-up`) with the locale-prefixed equivalent
        (`/fr/blog/...`, `/fr/sign-up`). Skip anchors (`#...`), external URLs,
        and `mailto:` / `tel:` links.
   d. Write the file to `<destDir resolved with this lang>/<path>`. Create
      directories as needed.
3. After processing all paths, print a summary: files written per locale,
   total replacements, any chunks that could not be found.

Robustness:
- Use `papaparse` to read the CSV. Trim BOM if present.
- Preserve the exact frontmatter formatting (delimiters, key order, quoting).
  The simplest way is to do replacements on the raw file string, not on a
  parsed AST.
- Be idempotent: running the script twice on the same inputs produces the
  same outputs.

Make it a single self-contained file. Usage:
`node scripts/assemble-blog-translations.mjs translated.csv`

Ejecuta el script y verás aparecer una serie de archivos nuevos en src/content/blog/fr/live/, src/content/blog/de/live/, etcétera. Verifica uno al azar, ejecuta astro dev y tus rutas localizadas deberían renderizarse al instante.

Archivo de skill del flujo de trabajo de traducción del blog de AI Glot en Antigravity

Bonus 1: Un archivo de skill para que cada artículo nuevo sea “traducible por defecto”

Una vez que el pipeline funciona, querrás que cada artículo futuro fluya a través de él de la misma manera. El truco: guarda la convención en un archivo de skill que tu asistente de IA lea antes de escribir cualquier post nuevo.

Si usas Claude Code, Cursor o cualquier editor de IA que cargue instrucciones a nivel de proyecto, coloca este archivo en tu repo como skills/blog_translation_workflow.md. En AI Glot usamos exactamente este patrón.

---
name: blog_translation_workflow
description: How to ship a new blog post so it can be translated in bulk later.
---

# Blog translation workflow

When writing a new blog post or CMS item, follow these conventions so it
flows cleanly through the multilingual extraction pipeline.

## 1. File location and naming
- Drafts live under `src/content/blog/draft/`.
- Slug = filename = the same string used in `slug:` frontmatter.
- Move to `src/content/blog/live/` only after review.

## 2. Frontmatter contract
Every post MUST have:
- `title`: sentence case, no trailing period.
- `metaDescription`: 140 to 160 characters, full sentence.
- `coverImage`: relative path from this file's location.
- `publishedDate`: ISO date.
- `slug`: matches the filename.
- `faqs`: array of `{ question, answer }`. Minimum 3 items.

## 3. Body structure that translates well
- Use sentence-case headings (no Title Case).
- Keep paragraphs to 1 to 4 sentences. Each paragraph becomes one CSV row,
  so shorter paragraphs translate more reliably.
- Never put two images back to back; always separate them with at least one
  paragraph of explanatory text.
- Internal links use root-relative paths (`/blog/...`, `/sign-up`).
- Image paths use `../../../assets/...` (English depth).

## 4. After publishing
Run:
```bash
node scripts/extract-translations.mjs src/content/blog/live/`<slug>`.md \
  multilang-translation.csv fr de es it pt
```
to append this article's translatable chunks to the master CSV. Upload the
CSV to AI Glot and ask it to fill every language column, then run:
```bash
node scripts/assemble-blog-translations.mjs multilang-translation_translated.csv
```
to generate the localized markdown files.

## 5. Anti-patterns
- Do NOT use em-dashes; use commas or colons.
- Do NOT bold entire paragraphs. Bold key phrases only.
- Do NOT skip an FAQ section; FAQs are the highest-leverage SEO blocks.
- Do NOT inline raw HTML inside markdown unless absolutely necessary;
  it complicates chunk-based reassembly.

La próxima vez que tú (o cualquier persona de tu equipo) escribáis un artículo nuevo, el asistente leerá primero este archivo y generará un post que encaje en el pipeline sin necesidad de limpieza manual.

Bonus 2: Añadir un idioma nuevo a todo tu blog en un solo lote

Esta es la parte que más sorprende a la gente. Una vez que el pipeline existe, añadir un idioma nuevo es una operación de un solo CSV para todo el archivo de tu blog.

Imagina que tienes 80 artículos traducidos al francés, alemán, español, italiano y portugués, y decides añadir el holandés.

El flujo de trabajo:

  1. Vuelve a ejecutar tu script de extracción añadiendo nl a la lista de locales. Como el script lee la carpeta fuente en inglés, se incluyen todos los artículos (antiguos y nuevos).
  2. Abre el CSV. La columna English string es idéntica a la anterior. Las columnas fr, de, es, it, pt siguen vacías (el script no mira las traducciones existentes; solo construye la matriz vacía). La columna nl es nueva y está vacía.
  3. Elimina las columnas que no necesitas volver a traducir. Borra fr, de, es, it, pt de la hoja de cálculo y mantén path, English string, nl.
  4. Súbelo a AI Glot y pídele que traduzca English string solo al nl.
  5. Ejecuta el script de reensamblado con el nuevo CSV. Este escribirá cada artículo en src/content/blog/nl/live/.

Has pasado de “tenemos que añadir el holandés” a “el holandés ya está publicado” en una sola subida. Para 80 artículos. Así es cuando el flujo de trabajo hace el trabajo, no el equipo.

El mismo patrón funciona para cambios puntuales: edita un solo párrafo en inglés, vuelve a exportar solo los fragmentos afectados, tradúcelos y reconstruye. Las actualizaciones siguen siendo baratas porque el pipeline es estructural, no basado en documentos.

Lista de verificación

Antes de pasar a producción, haz un control de calidad de cada locale:

  • npm run build (o astro build) finaliza con éxito sin errores de ImageNotFound.
  • Pasa el ratón por el selector de idioma en un post localizado y confirma que la URL se mantiene limpia.
  • Abre un post en francés y revisa al azar: título, meta descripción, los tres primeros párrafos y una respuesta de la FAQ. Compáralos con el original.
  • Haz clic en un enlace interno dentro del post en francés. Debería llevarte a otra página en francés, no volver al inglés.
  • Inspecciona el <head> para verificar que las anotaciones hreflang sean correctas en todos los locales.
  • Ejecuta una auditoría de Lighthouse en una URL localizada; la puntuación de SEO debe ser la misma que la de su equivalente en inglés.

Si alguno de estos puntos falla, los culpables más comunes suelen ser: una profundidad de ruta de imagen incorrecta, un enlace interno que escapó a la regex o un fragmento que varió entre la extracción y el reensamblado. Las advertencias de “could not find chunk” del script de reensamblado te indican exactamente dónde buscar.

La conclusión

Un sitio de Astro multilingüe a escala no es un proyecto de traducción. Es un pipeline de contenido. Configura correctamente el pipeline (enrutamiento de locales, extracción de CSV por fragmentos, traducción con AI Glot y reensamblado determinista) y traducir un artículo nuevo, corregir un párrafo o añadir un idioma nuevo se convertirá en una operación rutinaria en lugar de una iniciativa trimestral.

Las piezas que hacen que funcione:

  • El i18n integrado de Astro para obtener páginas estáticas limpias por locale.
  • Un pequeño script de extracción que convierte el markdown en un CSV traducible.
  • La traducción de CSV de AI Glot, configurada en una frase para completar cada columna de idioma en una sola pasada, con control de glosario e instrucciones.
  • Un script de reensamblado que gestiona la profundidad de las imágenes y la localización de enlaces internos.
  • Un archivo de skill para que cada artículo nuevo encaje en el pipeline por defecto.

¿Listo para expandir tu alcance multilingüe? Regístrate en AI Glot, sube tu primer CSV de extracción y haz que tu blog esté disponible en seis idiomas hoy mismo.

10.000 palabras gratis al registrarte

¿Listo para traducir tus archivos grandes?