Handlebars
Локализуйте шаблоны Handlebars с помощью Lingo.dev CLI
Что такое Handlebars?
Handlebars — это популярный шаблонизатор, который даёт все возможности для эффективного создания семантических шаблонов. Он использует простой синтаксис и компилирует шаблоны в функции JavaScript, поэтому широко применяется для генерации HTML как на клиенте, так и на сервере.
Что такое Lingo.dev CLI?
Lingo.dev CLI — это бесплатный open-source CLI для перевода приложений и контента с помощью ИИ. Он создан, чтобы заменить традиционные системы управления переводами и легко интегрируется в существующие пайплайны.
Подробнее см. в разделе Обзор.
О данном гиде
В этом гайде рассказывается, как локализовать шаблоны Handlebars с помощью Lingo.dev CLI.
Вы узнаете, как:
- Структурировать файлы переводов для проектов на Handlebars
- Настроить пайплайн переводов
- Генерировать переводы с помощью ИИ
Необходимые условия
Для работы с Lingo.dev CLI убедитесь, что установлен Node.js v18+:
❯ node -v
v22.17.0
Подход к локализации Handlebars
В шаблонах Handlebars лучше ссылаться на переводимый контент из JSON-файлов, а не вставлять текст напрямую. Такой подход даёт:
- Чёткое разделение: структура шаблона и переводимый контент
- Контроль версий: переводы хранятся в JSON-файлах
- Без двусмысленности: явно указано, что подлежит переводу
Чтобы использовать переводы в шаблонах, понадобится вспомогательная функция для переводов. Часто используют такие варианты:
Простой кастомный helper:
{{t "product.title"}}
{{t "greeting" name="John"}}
handlebars-i18n — много возможностей и форматирование:
{{__ "product.title"}}
{{_date releaseDate}}
{{_price amount "USD"}}
handlebars-i18next — альтернатива с интеграцией i18next:
{{t "product.title"}}
В этом гайде в примерах используется {{t}}, но этот подход подходит для любого выбора helper.
Шаг 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.
Полный список кодов локалей, поддерживаемых 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. Создайте исходный контент
Создайте JSON-файлы с переводимым контентом. Эти файлы должны быть организованы в структуре каталогов, включающей код исходной локали:
project/
├── locales/
│ └── en/
│ ├── common.json
│ └── store.json
├── templates/
│ └── product.handlebars
└── i18n.json
Каталог es/ и переведённые файлы будут созданы автоматически Lingo.dev CLI при генерации переводов на шаге 8.
Пример JSON-файлов
locales/en/common.json:
{
"navigation": {
"home": "Home",
"products": "Products",
"about": "About",
"contact": "Contact"
},
"footer": {
"copyright": "All rights reserved",
"privacy": "Privacy Policy",
"terms": "Terms of Service"
}
}
locales/en/store.json:
{
"product": {
"title": "Wireless Headphones",
"description": "Premium sound quality with active noise cancellation",
"price": "Price",
"inStock": "In Stock",
"outOfStock": "Out of Stock"
},
"cart": {
"add": "Add to Cart"
},
"actions": {
"buyNow": "Buy Now"
}
}
Шаг 5. Создайте bucket
-
В файле
i18n.jsonдобавьте объект"json"в объектbuckets:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "json": {} } } -
В объекте
"json"определите массив из одного или нескольких шаблоновinclude:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "json": { "include": ["./locales/[locale]/*.json"] } } }Эти шаблоны определяют, какие файлы переводить.
Сами шаблоны:
- должны содержать
[locale]как плейсхолдер для выбранной локали - могут указывать на пути к файлам (например,
"locales/[locale]/common.json") - могут использовать звёздочки как подстановочные знаки (например,
"locales/[locale]/*.json")
Рекурсивные glob-шаблоны (например,
**/*.json) не поддерживаются. - должны содержать
Шаг 6. Используйте переводы в шаблонах
Обращайтесь к ключам переводов в ваших Handlebars-шаблонах с помощью выбранного вами хелпера:
templates/product.handlebars:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{t "product.title"}}</title>
</head>
<body>
<nav>
<a href="/">{{t "navigation.home"}}</a>
<a href="/products">{{t "navigation.products"}}</a>
<a href="/about">{{t "navigation.about"}}</a>
<a href="/contact">{{t "navigation.contact"}}</a>
</nav>
<main>
<article>
<h1>{{t "product.title"}}</h1>
<p>{{t "product.description"}}</p>
<div class="price">
<span>{{t "product.price"}}:</span>
<span>$299.99</span>
</div>
<div class="stock">
{{#if inStock}}
<span class="available">{{t "product.inStock"}}</span>
{{else}}
<span class="unavailable">{{t "product.outOfStock"}}</span>
{{/if}}
</div>
<div class="actions">
<button class="primary">{{t "cart.add"}}</button>
<button class="secondary">{{t "actions.buyNow"}}</button>
</div>
</article>
</main>
<footer>
<p>{{t "footer.copyright"}}</p>
<a href="/privacy">{{t "footer.privacy"}}</a>
<a href="/terms">{{t "footer.terms"}}</a>
</footer>
</body>
</html>
То, как вы загружаете эти переводы и компилируете шаблоны, зависит от вашей сборки и выбранной библиотеки-хелпера.
Шаг 7. Настройте LLM
Lingo.dev CLI использует большие языковые модели (LLM) для перевода контента с помощью ИИ. Чтобы использовать одну из этих моделей, вам нужен API-ключ от поддерживаемого провайдера.
Чтобы начать как можно быстрее, рекомендуем использовать Lingo.dev Engine:
-
Выполните следующую команду:
npx lingo.dev@latest loginОткроется ваш браузер по умолчанию и попросит пройти аутентификацию.
-
Следуйте подсказкам.
Шаг 8. Сгенерируйте переводы
В каталоге, где находится файл i18n.json, выполните следующую команду:
npx lingo.dev@latest run
Эта команда:
- Считывает файл
i18n.json. - Находит JSON-файлы, которые нужно перевести.
- Извлекает переводимый контент из файлов.
- Использует настроенный LLM для перевода извлечённого контента.
- Записывает переведённый контент обратно в файловую систему.
Когда переводы генерируются впервые, создаётся файл i18n.lock. Этот файл отслеживает, какой контент уже переведён, чтобы избежать ненужных повторных переводов при следующих запусках.
Пример
Структура проекта
handlebars-localization/
├── locales/
│ ├── en/
│ │ ├── common.json
│ │ └── store.json
│ └── es/
│ ├── common.json
│ └── store.json
├── templates/
│ └── product.handlebars
└── i18n.json
locales/en/common.json
{
"navigation": {
"home": "Home",
"products": "Products",
"about": "About",
"contact": "Contact"
},
"footer": {
"copyright": "All rights reserved",
"privacy": "Privacy Policy",
"terms": "Terms of Service"
}
}
locales/en/store.json
{
"product": {
"title": "Wireless Headphones",
"description": "Premium sound quality with active noise cancellation",
"price": "Price",
"inStock": "In Stock",
"outOfStock": "Out of Stock"
},
"cart": {
"add": "Add to Cart"
},
"actions": {
"buyNow": "Buy Now"
}
}
locales/es/common.json
{
"navigation": {
"home": "Inicio",
"products": "Productos",
"about": "Acerca de",
"contact": "Contacto"
},
"footer": {
"copyright": "Todos los derechos reservados",
"privacy": "Política de privacidad",
"terms": "Términos de servicio"
}
}
locales/es/store.json
{
"product": {
"title": "Auriculares inalámbricos",
"description": "Calidad de sonido premium con cancelación activa de ruido",
"price": "Precio",
"inStock": "En stock",
"outOfStock": "Agotado"
},
"cart": {
"add": "Añadir al carrito"
},
"actions": {
"buyNow": "Comprar ahora"
}
}
templates/product.handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{t "product.title"}}</title>
</head>
<body>
<nav>
<a href="/">{{t "navigation.home"}}</a>
<a href="/products">{{t "navigation.products"}}</a>
<a href="/about">{{t "navigation.about"}}</a>
<a href="/contact">{{t "navigation.contact"}}</a>
</nav>
<main>
<article>
<h1>{{t "product.title"}}</h1>
<p>{{t "product.description"}}</p>
<div class="price">
<span>{{t "product.price"}}:</span>
<span>$299.99</span>
</div>
<div class="stock">
{{#if inStock}}
<span class="available">{{t "product.inStock"}}</span>
{{else}}
<span class="unavailable">{{t "product.outOfStock"}}</span>
{{/if}}
</div>
<div class="actions">
<button class="primary">{{t "cart.add"}}</button>
<button class="secondary">{{t "actions.buyNow"}}</button>
</div>
</article>
</main>
<footer>
<p>{{t "footer.copyright"}}</p>
<a href="/privacy">{{t "footer.privacy"}}</a>
<a href="/terms">{{t "footer.terms"}}</a>
</footer>
</body>
</html>
i18n.json
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {
"json": {
"include": ["./locales/[locale]/*.json"]
}
}
}
i18n.lock
version: 1
checksums:
8a4f2c9e1d6b3a7f5e8c2d1b9a3f6e4c:
navigation.home: 7b2e4f9a1c8d3b6f5e2a9d1c8b4f7e3a
navigation.products: 3f8e2a9d1c6b4f7e5a2c9d1b8f4e7a3b
navigation.about: 9d1c8b4f7e3a2f9e1d6b3a7f5e8c2d1b
navigation.contact: 4f7e3a2c9d1b8f6e5a3f2d9c1b7e4a8f
footer.copyright: 2c9d1b8f4e7a3b6f5e2a9d1c8b4f7e3a
footer.privacy: 8b4f7e3a2c9d1b6f5e2a9d1c8f4e7a3b
footer.terms: 6f5e2a9d1c8b4f7e3a2c9d1b8f4e7a3b
3b6f5e2a9d1c8b4f7e3a2c9d1b8f4e7a:
product.title: 1c8b4f7e3a2c9d1b6f5e2a9d1c8f4e7a
product.description: 7e3a2c9d1b6f5e2a9d1c8b4f7e3a2c9d
product.price: 9d1b6f5e2a9d1c8b4f7e3a2c9d1b8f4e
product.inStock: 4f7e3a2c9d1b6f5e2a9d1c8b4f7e3a2c
product.outOfStock: 2c9d1b6f5e2a9d1c8b4f7e3a2c9d1b8f
cart.add: 8b4f7e3a2c9d1b6f5e2a9d1c8b4f7e3a
actions.buyNow: 7e3a2c9d1b6f5e2a9d1c8b4f7e3a2c9d