|
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 lleva de principio a fin por el proceso de localización de un sitio con Next.js App Router: configurar el CLI, organizar el contenido por idioma, renderizar Markdoc en rutas dinámicas y automatizar las traducciones con GitHub Actions.

Repositorio de ejemplo

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

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/[locale]/pages/home.md
Cadenas de IUEtiquetas de navegación, CTA y estados de botonessrc/content/[locale]/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).

El bucket markdoc del CLI procesa archivos de Markdoc conservando el frontmatter y las etiquetas personalizadas, mientras que el bucket json se encarga del catálogo de cadenas de IU. Ambos traducen el delta a través de tu motor de localización y escriben archivos por idioma junto al contenido de origen.

Requisitos previos#

1

Crea un motor de localización

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

2

Verifica Node.js

El CLI requiere Node.js 18 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 src/content/[locale]/ con dos subcarpetas (pages/ y blog/) además de un archivo ui.json. Consulta 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#

Crea un archivo i18n.json en la raíz de tu proyecto. Declara dos buckets: uno para el contenido de Markdoc y otro para el catálogo de cadenas de IU:

json
{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.15",
  "locale": {
    "source": "en",
    "targets": ["es", "fr", "de"]
  },
  "buckets": {
    "markdoc": {
      "include": [
        "src/content/[locale]/pages/*.md",
        "src/content/[locale]/blog/*.md"
      ]
    },
    "json": {
      "include": ["src/content/[locale]/ui.json"]
    }
  }
}

El placeholder [locale] se resuelve al código de cada idioma configurado. Con source: "en", el CLI lee desde src/content/en/ y escribe los archivos traducidos en src/content/es/, src/content/fr/ y src/content/de/.

Catálogos de un solo archivo

Si tus cadenas de IU viven en un único archivo JSON multiidioma, en lugar de un archivo por idioma, usa el tipo de bucket json-per-locale. Consulta Localización de contenido estático para ver la lista completa de tipos de bucket.

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#

Configura tu API key y ejecuta el CLI:

bash
export LINGO_API_KEY="your-api-key"
npx lingo.dev@latest run

El CLI lee cada archivo que coincide con los patrones de tus buckets, identifica las entradas sin traducir usando el lockfile, traduce el delta a través de tu motor de localización y escribe los resultados en el directorio de cada idioma de destino. Se conservan las claves de frontmatter, las etiquetas personalizadas de Markdoc y la estructura del JSON; solo cambia el texto traducible.

Para apuntar a un idioma específico durante el desarrollo:

bash
npx lingo.dev@latest run --target-locale es

Automatiza con GitHub Actions#

Agrega un archivo de flujo de trabajo en .github/workflows/translate.yml para traducir en cada push:

Las traducciones se confirman directamente en main: sin fricción, ideal para equipos pequeños:

yaml
name: Translate
on:
  push:
    branches: [main]
permissions:
  contents: write
jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Lingo.dev
        uses: lingodotdev/lingo.dev@main
        with:
          api-key: ${{ secrets.LINGODOTDEV_API_KEY }}

Guarda tu API key como LINGODOTDEV_API_KEY en Settings > Secrets and variables > Actions de tu repositorio de GitHub.

Verifica antes de desplegar#

Usa la bandera --frozen como compuerta de despliegue para asegurarte de que no llegue contenido sin traducir a producción. El CLI finaliza con un estado distinto de cero si alguna entrada necesita traducción:

bash
npx lingo.dev@latest run --frozen

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

yaml
- name: Verify translations
  run: npx lingo.dev@latest run --frozen
- name: Build
  run: pnpm build

Siguientes pasos#

Localización de contenido estático
Markdown, MDX, JSON, YAML y más tipos de bucket
Localización de aplicaciones web
Patrones de cadenas de IU en frameworks web comunes
Flujos de trabajo de CI/CD
Patrones para GitHub Actions, GitLab CI y Bitbucket Pipelines
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 4 días·6 min de lectura