Handlebars
Localiza plantillas de Handlebars con Lingo.dev CLI
¿Qué es Handlebars?
Handlebars es un motor de plantillas popular que proporciona la potencia necesaria para construir plantillas semánticas de manera efectiva. Utiliza una sintaxis limpia y compila plantillas en funciones de JavaScript, lo que lo hace ampliamente utilizado para generar HTML tanto en aplicaciones del lado del cliente como del servidor.
¿Qué es Lingo.dev CLI?
Lingo.dev CLI es una CLI gratuita y de código abierto para traducir aplicaciones y contenido con IA. Está diseñada para reemplazar el software tradicional de gestión de traducciones mientras se integra con los pipelines existentes.
Para obtener más información, consulta Descripción general.
Acerca de esta guía
Esta guía explica cómo localizar plantillas de Handlebars utilizando Lingo.dev CLI.
Aprenderás a:
- Estructurar archivos de traducción para proyectos de Handlebars
- Configurar un pipeline de traducción
- Generar traducciones con IA
Requisitos previos
Para usar Lingo.dev CLI, asegúrate de que Node.js v18+ esté instalado:
❯ node -v
v22.17.0
El enfoque de localización de Handlebars
Las plantillas de Handlebars deben hacer referencia al contenido traducible desde archivos JSON en lugar de contener texto codificado de forma rígida. Este enfoque proporciona:
- Separación clara: estructura de plantilla vs. contenido traducible
- Control de versiones: traducciones rastreadas en archivos JSON
- Sin ambigüedad: define explícitamente qué es traducible
Para acceder a las traducciones en las plantillas, necesitarás una función auxiliar de traducción. Las opciones comunes incluyen:
Helper personalizado simple:
{{t "product.title"}}
{{t "greeting" name="John"}}
handlebars-i18n - Rico en funciones con formato:
{{__ "product.title"}}
{{_date releaseDate}}
{{_price amount "USD"}}
handlebars-i18next - Integración alternativa con i18next:
{{t "product.title"}}
Esta guía utiliza {{t}} en los ejemplos, pero el flujo de trabajo se aplica a cualquier helper que elijas.
Paso 1. Configurar un proyecto
En el directorio de tu proyecto, crea un archivo i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
Este archivo define el comportamiento del pipeline de traducción, incluyendo los idiomas entre los que traducir y dónde existe el contenido localizable en el sistema de archivos.
Para obtener más información sobre las propiedades disponibles, consulta i18n.json.
Paso 2. Configurar el locale de origen
El locale de origen es el idioma y región originales en los que se escribió tu contenido. Para configurar el locale de origen, establece la propiedad locale.source en el archivo i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
El locale de origen debe proporcionarse como una etiqueta de idioma BCP 47.
Para ver la lista completa de códigos de locale que admite Lingo.dev CLI, consulta Códigos de locale admitidos.
Paso 3. Configurar los locales de destino
Los locales de destino son los idiomas y regiones a los que deseas traducir tu contenido. Para configurar los locales de destino, establece la propiedad locale.targets en el archivo i18n.json:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.10",
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {}
}
Paso 4. Crear el contenido de origen
Crea archivos JSON que contengan tu contenido traducible. Estos archivos deben organizarse en una estructura de directorios que incluya el código del locale de origen:
project/
├── locales/
│ └── en/
│ ├── common.json
│ └── store.json
├── templates/
│ └── product.handlebars
└── i18n.json
El directorio es/ y los archivos traducidos se crearán automáticamente por Lingo.dev CLI cuando generes las traducciones en el paso 8.
Archivos JSON de ejemplo
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"
}
}
Paso 5. Crear un bucket
-
En el archivo
i18n.json, añade un objeto"json"al objetobuckets:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "json": {} } } -
En el objeto
"json", define un array de uno o más patronesinclude:{ "$schema": "https://lingo.dev/schema/i18n.json", "version": "1.10", "locale": { "source": "en", "targets": ["es"] }, "buckets": { "json": { "include": ["./locales/[locale]/*.json"] } } }Estos patrones definen qué archivos traducir.
Los patrones en sí:
- deben contener
[locale]como marcador de posición para la configuración regional configurada - pueden apuntar a rutas de archivos (p. ej.,
"locales/[locale]/common.json") - pueden usar asteriscos como marcadores de posición comodín (p. ej.,
"locales/[locale]/*.json")
No se admiten patrones glob recursivos (p. ej.,
**/*.json). - deben contener
Paso 6. Usa las traducciones en las plantillas
Referencia las claves de traducción en tus plantillas Handlebars usando el helper que hayas elegido:
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>
Cómo cargas estas traducciones y compilas tus plantillas depende de tu configuración de compilación y la biblioteca de helpers elegida.
Paso 7. Configura un LLM
Lingo.dev CLI utiliza modelos de lenguaje grandes (LLM) para traducir contenido con IA. Para usar uno de estos modelos, necesitas una clave API de un proveedor compatible.
Para empezar lo más rápido posible, recomendamos usar Lingo.dev Engine:
-
Ejecuta el siguiente comando:
npx lingo.dev@latest loginEsto abrirá tu navegador predeterminado y te pedirá que te autentiques.
-
Sigue las instrucciones.
Paso 8. Genera las traducciones
En el directorio que contiene el archivo i18n.json, ejecuta el siguiente comando:
npx lingo.dev@latest run
Este comando:
- Lee el archivo
i18n.json. - Encuentra los archivos JSON que necesitan ser traducidos.
- Extrae el contenido traducible de los archivos.
- Utiliza el LLM configurado para traducir el contenido extraído.
- Escribe el contenido traducido de vuelta al sistema de archivos.
La primera vez que se generan las traducciones, se crea un archivo i18n.lock. Este archivo realiza un seguimiento del contenido que ha sido traducido, evitando retraducciones innecesarias en ejecuciones posteriores.
Ejemplo
Estructura del proyecto
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