Twig
AI-перевод Twig-шаблонов с помощью Lingo.dev CLI
Что такое Twig?
Twig — это гибкий, быстрый и безопасный шаблонизатор для PHP. Он широко используется в приложениях на Symfony и других PHP-фреймворках для разделения логики отображения и логики приложения. Twig использует чистый синтаксис, благодаря которому шаблоны легко читать и писать.
Что такое Lingo.dev CLI?
Lingo.dev CLI — это бесплатный open-source CLI для перевода приложений и контента с помощью ИИ. Он создан, чтобы заменить традиционные системы управления переводами и легко интегрируется в существующие пайплайны.
Чтобы узнать больше, смотрите Обзор.
О данном руководстве
В этом гайде рассказывается, как переводить Twig-шаблоны с помощью Lingo.dev CLI.
Вы узнаете, как:
- Создать проект с нуля
- Настроить пайплайн для перевода
- Генерировать переводы с помощью ИИ
Необходимые условия
Для работы с Lingo.dev CLI убедитесь, что установлен Node.js v18+:
❯ node -v
v22.17.0
Шаг 1. Создайте проект
В директории вашего проекта создайте файл i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
Этот файл определяет поведение пайплайна перевода: какие языки использовать и где на файловой системе находится локализуемый контент.
Подробнее о доступных свойствах смотрите в i18n.json.
Шаг 2. Настройте исходную локаль
Исходная локаль — это оригинальный язык и регион, на которых был написан ваш контент. Чтобы настроить исходную локаль, укажите свойство locale.source в файле i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
Исходная локаль должна быть указана в формате BCP 47 language tag.
Полный список поддерживаемых кодов локалей в Lingo.dev CLI смотрите в разделе Поддерживаемые коды локалей.
Шаг 3. Настройка целевых локалей
Целевые локали — это языки и регионы, на которые вы хотите перевести свой контент. Чтобы настроить целевые локали, укажите свойство locale.targets в файле i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
Шаг 4. Создайте исходный контент
Если вы ещё не сделали этого, создайте один или несколько Twig-шаблонов, которые содержат переводимый контент. Эти файлы должны находиться по пути, в котором где-то встречается исходная локаль (например, в названии папки как en/ или в имени файла как template_en.html.twig).
В Twig-шаблонах к переводимому контенту относятся:
- Текст внутри блочных элементов:
h1-h6,p,div,li,blockquote,article,sectionи др. - Текст внутри строчных элементов:
a,strong,em,span,codeи др. - Значения атрибутов, включая:
- атрибут
contentу теговmeta - атрибуты
altиtitleу теговimg - атрибуты
placeholder,titleиaria-labelу теговinput,textarea,button,a,abbrиlink
- атрибут
Как обрабатывается синтаксис Twig:
Синтаксис Twig полностью сохраняется при переводе:
- Twig-переменные и выражения (
{{ user.name }},{{ product.price|number_format }}) остаются без изменений внутри переводимого текста - Twig-структуры управления (
{% if %},{% for %},{% set %}) сохраняются и не переводятся - Twig-комментарии (
{# internal note #}) сохраняются и не переводятся - Twig-фильтры (
{{ "now"|date('Y') }}) продолжают работать в переведённых шаблонах
Как обрабатывается встроенный HTML:
Когда текст содержит встроенные HTML-элементы (например, <strong>, <span>, <em>, <a> и др.), весь текстовый блок переводится как единое целое. Это сохраняет контекст для лучшего качества перевода и поддерживает встроенное форматирование.
Например:
<p>Hello <strong>{{ user.name }}</strong>, welcome back!</p>
Весь абзац "Hello <strong>{{ user.name }}</strong>, welcome back!" переводится как единый блок, при этом сохраняются и теги <strong>, и переменная Twig. Это гарантирует, что AI-переводчик видит весь контекст, а также сохраняет и инлайн-форматирование, и синтаксис Twig в переводе.
Автоматический атрибут lang:
Атрибут lang у элемента <html> автоматически обновляется в соответствии с целевой локалью:
<!-- Source (en/template.html.twig) -->
<html>
<head>
<title>Welcome</title>
</head>
<!-- Target (es/template.html.twig) -->
<html lang="es">
<head>
<title>Bienvenido</title>
</head>
Непереводимый контент:
Содержимое внутри тегов <script> и <style> не переводится.
Шаг 5. Создайте bucket
-
В файле
i18n.jsonдобавьте объект"twig"в объектbuckets:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "twig": {} } } -
В объекте
"twig"определите массив из одного или нескольких паттерновinclude:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "twig": { "include": ["./[locale]/*.twig"] } } }Эти паттерны определяют, какие файлы переводить.
Сами паттерны:
- должны содержать
[locale]как плейсхолдер для выбранной локали - могут указывать на пути к файлам (например,
"[locale]/template.html.twig") - могут использовать звёздочки как подстановочные знаки (например,
"[locale]/*.twig")
Рекурсивные glob-паттерны (например,
**/*.twig) не поддерживаются. - должны содержать
Шаг 6. Настройте LLM
Lingo.dev CLI использует большие языковые модели (LLM) для AI-перевода контента. Чтобы использовать одну из этих моделей, вам нужен API-ключ от поддерживаемого провайдера.
Чтобы начать как можно быстрее, рекомендуем использовать Lingo.dev Engine:
-
Выполните следующую команду:
npx lingo.dev@latest loginОткроется ваш браузер по умолчанию и появится запрос на аутентификацию.
-
Следуйте подсказкам.
Шаг 7. Сгенерируйте переводы
В каталоге, где находится файл i18n.json, выполните следующую команду:
npx lingo.dev@latest run
Эта команда:
- Считывает файл
i18n.json. - Находит файлы, которые нужно перевести.
- Извлекает переводимый контент из файлов.
- Использует настроенную LLM для перевода извлечённого контента.
- Записывает переведённый контент обратно в файловую систему.
При первом создании переводов создаётся файл i18n.lock. Этот файл отслеживает, какой контент уже переведён, чтобы избежать лишних повторных переводов при следующих запусках.
Пример
en/example.html.twig
{% set user = app.user %}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Welcome to our application">
<title>Welcome</title>
</head>
<body>
<header>
<nav>
<a href="/" title="Go to homepage">Home</a>
<a href="/about" title="Learn more about us">About</a>
<a href="/contact" title="Get in touch">Contact</a>
</nav>
</header>
<main>
<section class="hero">
<h1>Welcome to Our Platform</h1>
<p>Hello <strong>{{ user.name }}</strong>, we're glad to have you here!</p>
<p>Start exploring our features and discover what makes us <em>unique</em>.</p>
</section>
{% if user.isPremium %}
<section class="premium-benefits">
<h2>Premium Benefits</h2>
<ul>
<li>Unlimited access to all features</li>
<li>Priority customer support</li>
<li>Advanced analytics and reporting</li>
</ul>
</section>
{% endif %}
<section class="getting-started">
<h2>Getting Started</h2>
<p>Follow these simple steps to begin your journey:</p>
<ol>
<li>Complete your profile</li>
<li>Explore the dashboard</li>
<li>Invite your team members</li>
</ol>
<form action="/profile/update" method="post">
<label for="bio">Tell us about yourself:</label>
<textarea id="bio" name="bio" placeholder="Enter your bio here" aria-label="User biography"></textarea>
<label for="email">Email address:</label>
<input type="email" id="email" name="email" placeholder="[email protected]" aria-label="Email address">
<button type="submit" title="Save your profile changes">Save Profile</button>
</form>
</section>
{# This section is for internal notes and won't be displayed #}
{% if app.debug %}
<section class="debug-info">
<h3>Debug Information</h3>
<p>User ID: {{ user.id }}</p>
<p>Last login: {{ user.lastLogin|date('Y-m-d H:i:s') }}</p>
</section>
{% endif %}
</main>
<footer>
<p>Need help? <a href="/support" title="Visit our support center">Contact Support</a></p>
<p>© {{ "now"|date('Y') }} Our Company. All rights reserved.</p>
</footer>
</body>
</html>
es/example.html.twig
{% set user = app.user %}
<!DOCTYPE html>
<html lang="es">
<head>
<meta name="description" content="Bienvenido a nuestra aplicación">
<title>Bienvenido</title>
</head>
<body>
<header>
<nav><a href="/" title="Ir a la página de inicio">Inicio</a>
<a href="/about" title="Conoce más sobre nosotros">Acerca de</a>
<a href="/contact" title="Ponte en contacto">Contacto</a></nav>
</header>
<main>
<section class="hero">
<h1>Bienvenido a nuestra plataforma</h1>
<p>Hola <strong>{{ user.name }}</strong>, nos alegra tenerte aquí!</p>
<p>Comienza a explorar nuestras funciones y descubre lo que nos hace <em>únicos</em>.</p>
</section>
{% if user.isPremium %}
<section class="premium-benefits">
<h2>Beneficios premium</h2>
<ul>
<li>Acceso ilimitado a todas las funciones</li>
<li>Soporte prioritario al cliente</li>
<li>Análisis y reportes avanzados</li>
</ul>
</section>
{% endif %}
<section class="getting-started">
<h2>Primeros pasos</h2>
<p>Sigue estos sencillos pasos para comenzar tu experiencia:</p>
<ol>
<li>Completa tu perfil</li>
<li>Explora el panel de control</li>
<li>Invita a los miembros de tu equipo</li>
</ol>
<form action="/profile/update" method="post"><label for="bio">Cuéntanos sobre ti:</label>
<textarea id="bio" name="bio" placeholder="Ingresa tu biografía aquí" aria-label="Biografía del usuario"></textarea>
<label for="email">Dirección de correo electrónico:</label>
<input type="email" id="email" name="email" placeholder="[email protected]" aria-label="Dirección de correo electrónico">
<button type="submit" title="Guardar los cambios de tu perfil">Guardar perfil</button></form>
</section>
{# This section is for internal notes and won't be displayed #}
{% if app.debug %}
<section class="debug-info">
<h3>Información de depuración</h3>
<p>ID de usuario: {{ user.id }}</p>
<p>Último inicio de sesión: {{ user.lastLogin|date('Y-m-d H:i:s') }}</p>
</section>
{% endif %}
</main>
<footer>
<p>¿Necesitas ayuda? <a href="/support" title="Visita nuestro centro de soporte">Contacta con soporte</a></p>
<p>© {{ "now"|date('Y') }} Nuestra empresa. Todos los derechos reservados.</p>
</footer>
</body>
</html>
i18n.json
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {
"twig": {
"include": ["./[locale]/*.twig"]
}
}
}
i18n.lock
version: 1
checksums:
2d3d028d905803e471ca9f97a4969d5e:
head/0#content: 1308168cca4fa5d8d7a0cf24e55e93fc
head/1: 3180ad6b8de344b781637750259e0f53
body/0/0: 9de5fe40cbf5f851a6d2270f01fe0739
body/1/0/0: c59070fe496d5e4bd0066295b63a9056
body/1/0/1: 12d74865332bf1988d51e84ba67aae09
body/1/0/2: 58f0e438e665c77eedc440c5a8529b1a
body/1/1/0: 119e3aa396d12a5a1aa7058e0983f9b9
body/1/1/1/0: 60f9a22f4200bb4620a6ff7a1797ec30
body/1/1/1/1: 03846a81f16f5e4a11acfd9445ad497d
body/1/1/1/2: 15aae9d70ff1fb682f7d86baca81dcc0
body/1/2/0: fbd403146395526d68ac68d142a50e21
body/1/2/1: da8dc7fe06175d8b805f7f565bfe2788
body/1/2/2/0: 061e1acc1b9ebad9de09fd5626e813c7
body/1/2/2/1: 67f022a3f9e278d065a063b5e29dd932
body/1/2/2/2: 7e23f048179f6661050edaa796528fe0
body/1/2/3: 635f7e9a4afc00de34f975914afbb8b8
body/1/3/0: 7a7892379e31868abba9865d20be2b72
body/1/3/1: 8740df822561d74d51bb30e4b39d6193
body/1/3/2: 0429f12258fabbde3abaca3dd9986178
body/2/0: d32e57e4a5a65f3bee8b63dcb2bfa8e7
body/2/1: 7e10a8ab9cc4e6d603b3cdc48849688f