|
Documentación
Agenda una demoPlataforma
PlataformaMCPCLIAPIFlujos de trabajo
Guías
Registro de cambios

Localización

  • Resumen
  • API de traducción
  • Localización de apps web
  • Localización de apps móviles
  • iOS con String Catalogs
  • Android con strings.xml
  • Localización de emails
  • Contenido estático (p. ej., .md, .json)
  • Next.js con Markdoc
  • Rails con i18n

Flujos de trabajo

  • Configuración del motor con MCP
  • Triaje de Jira
  • CI/CD

Localización de Next.js App Router con Markdoc

El CLI de Lingo.dev traduce archivos de Markdoc y catálogos JSON de cadenas de IU mediante un motor de localización configurado. Markdoc es un formato de autoría basado en Markdown, con etiquetas personalizadas tipadas y respaldadas por React, ideal para sitios con Next.js App Router que combinan contenido extenso con componentes interactivos.

Esta guía te acompaña paso a paso en la localización integral de un sitio con Next.js App Router: configurar la CLI, organizar contenido por idioma, renderizar Markdoc en rutas dinámicas y automatizar las traducciones con la GitHub App de Lingo.dev.

Repositorio de ejemplo

Clona o haz fork de lingodotdev/markdoc-nextjs-localization-example para seguir el proceso. El repositorio incluye una app funcional de Next.js App Router con contenido de Markdoc, una configuración de la CLI de Lingo.dev y un flujo de trabajo de CI.

Cómo funciona la localización con Next.js + Markdoc#

La mayoría de los sitios con Next.js App Router dividen el contenido localizado en dos capas:

CapaQué incluyeArchivo de ejemplo
Contenido extensoPáginas de marketing, documentación y artículos de blogsrc/content/en/pages/home.md
Cadenas de IUEtiquetas de navegación, CTA y estados de botonessrc/content/en/ui.json

Las rutas viven bajo src/app/[lang]/ y leen los archivos del idioma correspondiente en tiempo de solicitud. Un middleware elige un idioma predeterminado a partir del encabezado Accept-Language del navegador y redirige rutas sin prefijo, como /, a /en (o a la mejor coincidencia).

La CLI procesa archivos de Markdoc sin alterar el frontmatter ni las etiquetas personalizadas, y maneja el catálogo de cadenas de UI como JSON. En ambos casos, traduce el delta a través de tu motor de localización y escribe archivos por idioma junto al contenido fuente.

Requisitos previos#

1

Crea un motor de localización

Cada ejecución de CLI envía el contenido a través de un motor de localización, la configuración que define qué modelo de LLM, glosario, voz de marca y reglas se aplican. Crea uno en el panel de Lingo.dev y genera una API key para CI.

2

Verifica Node.js

La CLI requiere Node.js 22 o superior:

bash
node -v
3

Configura tu proyecto de Next.js

Tu proyecto necesita App Router (src/app/) y un directorio de contenido por idioma. El repositorio de ejemplo usa un directorio por idioma dentro de src/content/ (por ejemplo, src/content/en/), con dos subcarpetas (pages/ y blog/) más un archivo ui.json. Consulta la internacionalización de Next.js para conocer los conceptos básicos del enrutamiento.

Organiza el contenido#

Separa el contenido según su función. Las páginas y publicaciones extensas se escriben en Markdoc; las cadenas cortas de IU viven en JSON para que los componentes puedan cargarlas directamente.

text
src/content/
  en/                  # Source locale
    pages/home.md      # Long-form Markdoc
    blog/hello.md
    ui.json            # UI strings (navbar, CTAs, button states)
  es/                  # Target locales – generated by Lingo.dev
  fr/
  de/

Los archivos de Markdoc admiten frontmatter para metadatos por página (título, descripción, fecha y autor), además de etiquetas personalizadas que se renderizan como componentes de React. Una página mínima se ve así:

markdown
---
title: Author once in Markdoc, ship in every language.
description: An example Next.js App Router app that localizes Markdoc with Lingo.
---

{% inline-callout type="info" %}
This page is authored in Markdoc and translated by Lingo.dev.
{% /inline-callout %}

## Built from three pieces

Markdoc custom tags render as React components – even interactive ones.

Configura el CLI#

Instala la CLI e inicia sesión:

bash
npm install -g @lingo.dev/cli
lingo login

Después, genera la configuración y vincúlala a tu motor:

bash
lingo init
lingo link

lingo init crea .lingo/config.json con tus idiomas de origen y destino, además de los patrones de archivo que se van a traducir; lingo link agrega tu orgId y engineId. Haz commit de .lingo/config.json para que todo el equipo y cada ejecución de CI compartan la misma configuración.

En este proyecto, la configuración define dos patrones de archivo: uno para el contenido de Markdoc y otro para el catálogo de cadenas de UI:

json
{
  "orgId": "org_...",
  "engineId": "eng_...",
  "sourceLocale": "en",
  "targetLocales": ["es", "fr", "de"],
  "files": [
    { "pattern": "src/content/en/pages/*.md" },
    { "pattern": "src/content/en/blog/*.md" },
    { "pattern": "src/content/en/ui.json" }
  ]
}

El segmento de idioma en cada ruta se sustituye según el idioma de destino: src/content/en/pages/home.md pasa a ser src/content/es/pages/home.md y src/content/en/ui.json pasa a ser src/content/de/ui.json. La ruta de origen debe incluir el código de idioma. Los formatos se detectan automáticamente por la extensión del archivo, así que los archivos de Markdoc (.md) y JSON (.json) no necesitan un tipo explícito. Consulta Configuración y Formatos para ver los detalles.

Catálogos de un solo archivo

La nueva CLI espera un archivo por idioma, con el código de idioma en la ruta (como se muestra arriba). Si tus cadenas de UI viven en un único archivo JSON multiidioma, esa estructura (el antiguo bucket json-per-locale) todavía no es compatible con la nueva CLI. En ese caso, mantenla en la legacy CLI y sigue el changelog para enterarte cuando haya soporte. La opción recomendada es dividirlo en un archivo por idioma.

Renderiza Markdoc en App Router#

Una ruta dinámica típica carga un documento y renderiza el árbol transformado. El repositorio de ejemplo expone un helper pequeño:

ts
// src/lib/markdoc.ts
export async function loadDoc(
  locale: Locale,
  collection: "pages" | "blog",
  slug: string,
) {
  const raw = await fs.readFile(
    path.join(process.cwd(), "src/content", locale, collection, `${slug}.md`),
    "utf8",
  );
  const ast = Markdoc.parse(raw);
  const frontmatter = ast.attributes.frontmatter
    ? parseFrontmatter(ast.attributes.frontmatter)
    : {};
  const content = Markdoc.transform(ast, { ...schema, variables: { frontmatter } });
  return { frontmatter, content };
}

La página de App Router es un contenedor ligero que combina el documento con cadenas de IU específicas por idioma:

tsx
// src/app/[lang]/page.tsx
export default async function Home({ params }: PageProps<"/[lang]">) {
  const { lang } = await params;
  const doc = await loadDoc(lang, "pages", "home");
  const { home } = await getMessages(lang);

  return (
    <main>
      <h1>{doc.frontmatter.title}</h1>
      {renderMarkdoc(doc.content)}
    </main>
  );
}

Las etiquetas personalizadas de Markdoc (callout, bento, blog-hero, etc.) se declaran en markdoc.schema.ts y se conectan a componentes de React bajo src/components/markdoc/. Consulta la documentación del esquema de Markdoc para ver la API completa.

Detecta el idioma en el middleware#

El middleware de Next.js inspecciona la solicitud antes de que se renderice una ruta. Úsalo para redirigir rutas sin prefijo al idioma que mejor coincida según el encabezado Accept-Language:

ts
// src/middleware.ts
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const hasLocale = locales.some(
    (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`),
  );
  if (hasLocale) return;

  const locale = pickLocale(request); // parses Accept-Language
  const url = request.nextUrl.clone();
  url.pathname = `/${locale}${pathname === "/" ? "" : pathname}`;
  return NextResponse.redirect(url);
}

export const config = {
  matcher: ["/((?!_next|api|.*\\..*).*)", ],
};

Los visitantes llegan a /en, /es, /fr o /de sin tener que escribir el prefijo.

Traduce localmente#

Después de lingo login, ejecuta un push. En la primera ejecución, o después de agregar un nuevo idioma de destino, completa todo el contenido:

bash
lingo push --backfill-missing

En las ejecuciones siguientes, solo haz push del delta:

bash
lingo push

lingo push lee todos los archivos que coinciden con tus patrones, identifica las entradas sin traducir usando el lock file (.lingo/lock.json, versionado en el repositorio), traduce el delta a través de tu motor de localización, espera a que termine y escribe los resultados en el directorio de cada idioma de destino. Las claves del frontmatter, las etiquetas personalizadas de Markdoc y las estructuras JSON se conservan: solo cambia el texto traducible. Para traer traducciones generadas en otro lugar (por ejemplo, por CI), ejecuta lingo pull.

Para limitar una ejecución a archivos específicos, pasa un glob:

bash
lingo push "src/content/en/blog/*.md"

Automatiza en CI#

Instala la GitHub App de Lingo.dev y conéctala a tu repositorio. Lee .lingo/config.json y el engineId vinculado del lado del servidor, y abre un pull request de traducción cada vez que cambia el contenido fuente, sin archivo de flujo de trabajo, sin runner, sin secreto de API key y sin tener que estar manipulando el lock file.

Verifica antes de desplegar#

Usa lingo check como control previo al despliegue para asegurarte de que no llegue contenido sin traducir a producción. Sale con un estado distinto de cero si todavía hay entradas que necesitan traducción:

bash
lingo check

Agrega esto como un paso de CI independiente antes del build de Next.js:

yaml
- name: Verify translations
  run: lingo check
  env:
    LINGO_API_KEY: ${{ secrets.LINGO_API_KEY }}
- name: Build
  run: pnpm build

Siguientes pasos#

Localización de contenido estático
Markdown, MDX, JSON, YAML y muchos más formatos de archivo
Localización de aplicaciones web
Patrones de cadenas de IU en frameworks web comunes
Flujos de trabajo de CI/CD
Patrones para GitHub App y runners autohospedados
Glossaries
Bloquea la traducción de nombres de marca y términos técnicos

¿Te resultó útil esta página?

Max PrilutskiyMax Prilutskiy·Actualizado hace 8 días·7 min de lectura