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:
- Una estructura de enrutamiento i18n limpia en tu proyecto Astro, con una carpeta por idioma.
- Un script repetible que extrae cada cadena traducible de un archivo markdown (o de una exportación de CMS) a un único CSV.
- Un CSV traducido producido en AI Glot, completado en una sola pasada en todas las columnas de idioma.
- Un segundo script que reensambla los archivos markdown traducidos, con las rutas de imágenes y los enlaces internos corregidos para los subdirectorios localizados.
- 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/promisesfuncionen 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

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. |

¿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 `
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:
- 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/.... - Enlaces internos. Un enlace como
/blog/glossary-website-translationsen inglés debe convertirse en/fr/blog/glossary-website-translationsen 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.

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:
- Vuelve a ejecutar tu script de extracción añadiendo
nla la lista de locales. Como el script lee la carpeta fuente en inglés, se incluyen todos los artículos (antiguos y nuevos). - Abre el CSV. La columna
English stringes idéntica a la anterior. Las columnasfr, de, es, it, ptsiguen vacías (el script no mira las traducciones existentes; solo construye la matriz vacía). La columnanles nueva y está vacía. - Elimina las columnas que no necesitas volver a traducir. Borra
fr, de, es, it, ptde la hoja de cálculo y manténpath, English string, nl. - Súbelo a AI Glot y pídele que traduzca
English stringsolo alnl. - 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(oastro build) finaliza con éxito sin errores deImageNotFound.- 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 anotacioneshreflangsean 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.