|
Документация
Заказать демоПлатформа
ПлатформаMCPCLIAPIРабочие процессы
Руководства
Журнал изменений

Локализация

  • Обзор
  • API локализации
  • Локализация веб-приложений
  • Локализация мобильных приложений
  • iOS и String Catalogs
  • Android и strings.xml
  • Локализация email-писем
  • Статический контент, например .md и .json
  • Next.js с Markdoc
  • Rails с i18n

Рабочие процессы

  • Настройка движка с MCP
  • Jira Triage
  • CI/CD

Локализация Next.js App Router с Markdoc

Lingo.dev CLI переводит файлы Markdoc и JSON-каталоги строк интерфейса через настроенный движок локализации. Markdoc — это формат для работы с контентом на базе Markdown с типизированными пользовательскими тегами на React — отличный выбор для сайтов на Next.js App Router, где длинные тексты сочетаются с интерактивными компонентами.

В этом руководстве — полная локализация сайта на Next.js App Router: настройка CLI, организация контента по локалям, рендеринг Markdoc в динамических маршрутах и автоматизация переводов с помощью Lingo.dev GitHub App.

Демо-репозиторий

Чтобы следовать руководству, клонируйте или форкните lingodotdev/markdoc-nextjs-localization-example. В репозитории есть рабочее приложение на Next.js App Router с контентом Markdoc, конфигурацией Lingo.dev CLI и рабочим процессом CI.

Как работает локализация в Next.js + Markdoc#

В большинстве сайтов на Next.js App Router локализованный контент делится на два слоя:

СлойЧто здесь хранитсяПример файла
Длинные текстыМаркетинговые страницы, документация, посты в блогеsrc/content/en/pages/home.md
Строки интерфейсаПодписи в навигации, CTA, состояния кнопокsrc/content/en/ui.json

Маршруты находятся в src/app/[lang]/ и при каждом запросе читают файлы нужной локали. Middleware определяет локаль по умолчанию из заголовка браузера Accept-Language и перенаправляет пути без префикса, например /, на /en (или на наиболее подходящий вариант).

CLI разбирает файлы Markdoc с frontmatter и пользовательскими тегами, а каталог строк интерфейса обрабатывает как JSON. И то, и другое переводит дельту через движок локализации и записывает файлы для каждой локали рядом с исходниками.

Что понадобится#

1

Создайте движок локализации

Каждый запуск CLI прогоняет контент через движок локализации — это конфигурация, которая задаёт модель LLM, глоссарий, тональность бренда и правила. Создайте его в панели управления Lingo.dev и получите API-ключ для CI.

2

Проверьте Node.js

Для CLI нужен Node.js 22 или выше:

bash
node -v
3

Настройте проект Next.js

В проекте нужен App Router (src/app/) и каталог контента с разбивкой по локалям. В демо-репозитории — по одному каталогу на локаль внутри src/content/ (например src/content/en/) с двумя подпапками (pages/ и blog/) и файлом ui.json. Основы маршрутизации — в документации по интернационализации Next.js.

Организуйте контент#

Разделяйте контент по назначению. Длинные страницы и посты создаются в Markdoc, а короткие строки интерфейса хранятся в JSON, чтобы компоненты могли загружать их напрямую.

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/

Файлы Markdoc поддерживают frontmatter для метаданных страницы (title, description, date, author) и пользовательские теги, которые рендерятся как React-компоненты. Вот как выглядит минимальная страница:

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.

Настройте CLI#

Установите CLI и войдите в систему:

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

Затем создайте конфиг и привяжите его к движку:

bash
lingo init
lingo link

lingo init создаёт .lingo/config.json с исходной и целевыми локалями и шаблонами файлов для перевода; lingo link добавляет orgId и engineId. Зафиксируйте .lingo/config.json в репозитории — тогда все участники команды и запуски CI будут работать с одной конфигурацией.

Для этого проекта в конфиге объявлены два шаблона файлов — один для контента Markdoc, другой для каталога строк интерфейса:

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" }
  ]
}

Сегмент локали в каждом пути подставляется для каждой целевой локали: src/content/en/pages/home.md становится src/content/es/pages/home.md, а src/content/en/ui.json — src/content/de/ui.json. Исходный путь должен содержать код локали. Форматы определяются автоматически по расширению файла, поэтому для файлов Markdoc (.md) и JSON (.json) явно указывать тип не нужно. Подробности — в разделах Конфигурация и Форматы.

Каталоги в одном файле

Новый CLI ожидает один файл на локаль с кодом локали в пути (как показано выше). Если строки интерфейса хранятся в одном многолокальном JSON-файле, такой формат (прежний бакет json-per-locale) новым CLI пока не поддерживается — оставьте его на устаревшем CLI и следите за журналом изменений. Рекомендуемый подход — разбить на отдельные файлы, по одному на локаль.

Рендеринг Markdoc в App Router#

Обычно динамический маршрут загружает документ и рендерит преобразованное дерево. В демо-репозитории для этого есть небольшой helper:

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 };
}

Страница App Router — это тонкая обёртка, которая связывает документ со строками интерфейса для конкретной локали:

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>
  );
}

Пользовательские теги Markdoc (callout, bento, blog-hero и т. д.) объявляются в markdoc.schema.ts и подключаются к React-компонентам из src/components/markdoc/. Полное API смотрите в документации по схеме Markdoc.

Определяйте локаль в middleware#

Middleware в Next.js анализирует запрос до рендеринга маршрута. Используйте его, чтобы перенаправлять пути без префикса на наиболее подходящую локаль на основе заголовка 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|.*\\..*).*)", ],
};

Пользователи попадают на /en, /es, /fr или /de, даже если не вводят префикс вручную.

Переводите локально#

После lingo login выполните push. При первом запуске — или после добавления новой целевой локали — переведите всё с нуля:

bash
lingo push --backfill-missing

В последующих запусках достаточно отправить дельту:

bash
lingo push

lingo push читает все файлы, соответствующие шаблонам, определяет непереведённые записи с помощью файла блокировки (.lingo/lock.json, зафиксированного в репозитории), переводит дельту через движок локализации, ожидает завершения и записывает результаты в каталог каждой целевой локали. Ключи frontmatter, пользовательские теги Markdoc и структуры JSON сохраняются — меняется только переводимый текст. Чтобы забрать переводы, созданные в другом месте (например в CI), выполните lingo pull.

Чтобы ограничить запуск конкретными файлами, передайте glob:

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

Автоматизация в CI#

Установите Lingo.dev GitHub App и укажите ваш репозиторий. Приложение читает .lingo/config.json и привязанный engineId на стороне сервера и открывает pull request с переводами при каждом изменении исходного контента — никаких файлов рабочего процесса, runner'а, секретов с API-ключом и хлопот с файлом блокировки.

Проверяйте перед деплоем#

Используйте lingo check как условие деплоя — так непереведённый контент не попадёт в production. Команда завершается с ненулевым кодом, если какие-то записи ещё не переведены:

bash
lingo check

Добавьте это как отдельный шаг CI перед сборкой Next.js:

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

Что дальше#

Локализация статического контента
Markdown, MDX, JSON, YAML и другие форматы файлов
Локализация веб-приложений
Паттерны строк интерфейса для популярных веб-фреймворков
Процессы CI/CD
GitHub App и паттерны с self-hosted runner
Глоссарии
Зафиксируйте названия брендов и технические термины, чтобы они не переводились

Эта страница была полезной?

Max PrilutskiyMax Prilutskiy·Обновлено 8 дней назад·6 минут чтения