A CLI da Lingo.dev traduz ficheiros Markdoc e catálogos JSON de strings de UI através de um motor de localização configurado. O Markdoc é um formato de autoria baseado em Markdown, com tags personalizadas tipadas e suportadas por React — ideal para sites em Next.js App Router que combinam conteúdo extenso com componentes interativos.
Este guia acompanha todo o processo de localização de um site em Next.js App Router: configurar a CLI, organizar conteúdo por idioma, renderizar Markdoc em rotas dinâmicas e automatizar traduções com GitHub Actions.
Repositório de demonstração
Faça clone ou fork de lingodotdev/markdoc-nextjs-localization-example para acompanhar. O repositório inclui uma aplicação funcional em Next.js App Router com conteúdo Markdoc, uma configuração da CLI da Lingo.dev e um workflow de GitHub Actions.
Como funciona a localização com Next.js + Markdoc#
A maioria dos sites em Next.js App Router divide o conteúdo localizado em duas camadas:
| Camada | O que inclui | Ficheiro de exemplo |
|---|---|---|
| Conteúdo extenso | Páginas de marketing, documentação, artigos de blogue | src/content/[locale]/pages/home.md |
| Strings de UI | Rótulos da barra de navegação, CTAs, estados de botões | src/content/[locale]/ui.json |
As rotas ficam em src/app/[lang]/ e leem os ficheiros do idioma correspondente no momento do pedido. Um middleware escolhe um idioma predefinido com base no cabeçalho Accept-Language do navegador e redireciona caminhos sem prefixo, como /, para /en (ou para a melhor correspondência).
O bucket markdoc da CLI processa ficheiros Markdoc, preservando o frontmatter e as tags personalizadas, enquanto o bucket json trata do catálogo de strings de UI. Ambos traduzem apenas o delta através do seu motor de localização e escrevem os ficheiros por idioma ao lado da origem.
Pré-requisitos#
Criar um motor de localização
Cada execução da CLI envia o conteúdo através de um motor de localização — a configuração que determina que modelo LLM, glossário, voz da marca e instrução se aplicam. Crie um no dashboard da Lingo.dev e gere uma chave de API.
Verificar o Node.js
A CLI requer Node.js 18 ou superior:
node -vConfigurar o seu projeto em Next.js
O seu projeto precisa do App Router (src/app/) e de uma diretoria de conteúdo por idioma. O repositório de demonstração usa src/content/[locale]/, com duas subpastas (pages/ e blog/) e um ficheiro ui.json. Consulte internationalization no Next.js para rever os conceitos base de routing.
Organizar o conteúdo#
Separe o conteúdo por função. As páginas e os artigos mais longos são escritos em Markdoc; as strings curtas de UI ficam em JSON para que os componentes as possam carregar diretamente.
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/Os ficheiros Markdoc suportam frontmatter para metadados por página (título, descrição, data, autor) e tags personalizadas que são renderizadas como componentes React. Uma página mínima é assim:
---
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.Configurar a CLI#
Crie um ficheiro i18n.json na raiz do projeto. Declare dois buckets — um para conteúdo Markdoc e outro para o catálogo de strings de UI:
{
"$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"]
}
}
}O marcador [locale] é resolvido para cada código de idioma configurado. Com source: "en", a CLI lê de src/content/en/ e escreve os ficheiros traduzidos em src/content/es/, src/content/fr/ e src/content/de/.
Catálogos num único ficheiro
Se as suas strings de UI estiverem num único ficheiro JSON multi-idioma, em vez de um ficheiro por idioma, use o tipo de bucket json-per-locale. Consulte Static Content Localization para ver a lista completa de tipos de bucket.
Renderizar Markdoc no App Router#
Uma rota dinâmica típica carrega um documento e renderiza a árvore transformada. O repositório de demonstração disponibiliza um pequeno helper:
// 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 };
}A página do App Router é um wrapper simples que combina o documento com strings de UI específicas de cada idioma:
// 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>
);
}As tags personalizadas do Markdoc (callout, bento, blog-hero, etc.) são declaradas em markdoc.schema.ts e ligadas a componentes React em src/components/markdoc/. Consulte a documentação do schema do Markdoc para conhecer a API completa.
Detetar o idioma no middleware#
O middleware do Next.js inspeciona o pedido antes de a rota ser renderizada. Use-o para redirecionar caminhos sem prefixo para o idioma com melhor correspondência, com base no cabeçalho Accept-Language:
// 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|.*\\..*).*)", ],
};Os visitantes chegam a /en, /es, /fr ou /de sem terem de escrever o prefixo.
Traduzir localmente#
Defina a sua chave de API e execute a CLI:
export LINGO_API_KEY="your-api-key"
npx lingo.dev@latest runA CLI lê todos os ficheiros que correspondem aos padrões dos buckets, identifica entradas por traduzir através do lockfile, traduz apenas o delta com o seu motor de localização e escreve os resultados na diretoria de cada idioma de destino. As chaves de frontmatter, as tags personalizadas do Markdoc e a estrutura do JSON são preservadas — só o texto traduzível muda.
Para direcionar um idioma específico durante o desenvolvimento:
npx lingo.dev@latest run --target-locale esAutomatizar com GitHub Actions#
Adicione um ficheiro de workflow em .github/workflows/translate.yml para traduzir a cada push:
As traduções são enviadas diretamente para a main — zero fricção, ideal para equipas pequenas:
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 }}Guarde a sua chave de API como LINGODOTDEV_API_KEY em Settings > Secrets and variables > Actions no seu repositório GitHub.
Verificar antes do deploy#
Use a flag --frozen como gate de deploy para garantir que nenhum conteúdo por traduzir chega à produção. A CLI termina com um estado diferente de zero se existir alguma entrada que precise de tradução:
npx lingo.dev@latest run --frozenAdicione isto como um passo de CI separado antes do build em Next.js:
- name: Verify translations
run: npx lingo.dev@latest run --frozen
- name: Build
run: pnpm build