La CLI de Lingo.dev traduce archivos YAML de Rails config/locales a través de un motor de localización configurado. Rails incorpora la API de i18n de serie, por lo que el texto traducible de tu aplicación se guarda en archivos YAML por idioma. Lingo.dev encaja en tu pipeline actual sin añadir dependencias en tiempo de ejecución.
Esta guía te acompaña en la localización integral de una aplicación Rails: desde la configuración de la CLI y la organización de archivos YAML por idioma hasta el cambio de idioma en tiempo de petición y la automatización de traducciones en CI.
Repositorio de ejemplo
Clona o haz un fork de lingodotdev/ruby-on-rails-localization-example para seguir el proceso. Es una aplicación Rails funcional con archivos YAML de config/locales, un .lingo/config.json ya versionado y las traducciones ya aplicadas, para que puedas revisar la configuración junto con el resultado que genera.
Cómo funciona la localización en Rails#
Rails lee las traducciones desde los archivos YAML de config/locales/. Cada archivo usa un código de idioma como clave raíz y contiene claves anidadas que reflejan las rutas de búsqueda que tu código utiliza con I18n.t.
| Capa | Qué contiene | Archivo de ejemplo |
|---|---|---|
| Textos de la interfaz | Botones, etiquetas, mensajes flash | config/locales/en.yml |
| Textos del mailer | Asuntos y cuerpos de ActionMailer | config/locales/mailers.en.yml |
| Errores del modelo | Mensajes de validación y nombres de atributos | config/locales/activerecord.en.yml |
La primera clave de cada archivo YAML de Rails es el propio código de idioma: en:, es:, fr:. Rails indexa las traducciones por esa clave raíz, no por el nombre del archivo: carga todos los archivos de config/locales/ y guarda el contenido de cada uno bajo la raíz que declare. Así que un es.yml que siga teniendo en: como raíz no se ignora: se integra en el espacio de nombres en. El español se queda sin traducciones y las inglesas se sobrescriben silenciosamente.
Por eso, traducir este formato implica reescribir esa clave, no solo los valores. El formato yaml-root-key hace exactamente eso: recorre el árbol por debajo de la clave raíz, traduce únicamente los valores de tipo cadena y escribe el archivo de destino con la raíz del idioma de destino. Las claves anidadas, los tokens de interpolación %{name} y las categorías de plural CLDR (zero/one/two/few/many/other) forman parte de la estructura, así que se conservan intactos, igual que los comentarios y las anclas YAML.
Requisitos previos#
Crea un motor de localización
Cada ejecución de CLI envía el contenido a través de un motor de localización, la configuración que determina qué modelo de LLM, glossary, voz de marca y rules se aplican. Crea uno en el panel de Lingo.dev y genera una API key.
Comprueba Ruby y Rails
Esta guía está pensada para Rails 7.2 o superior, que requiere Ruby 3.1 o superior. Comprueba qué versiones tienes:
ruby -v
rails -vComprueba Node.js
La CLI requiere Node.js 22 o superior:
node -vConfigura i18n en Rails
Esta guía da por hecho que tu aplicación ya guarda las traducciones en config/locales/*.yml. Si tienes cadenas hardcodeadas en vistas o controladores, extráelas primero a llamadas de t(). Por ejemplo, sustituye:
<h1>Welcome</h1>por:
<h1><%= t(".welcome") %></h1>y luego añade la clave correspondiente en config/locales/en.yml. Consulta la guía de internacionalización de Rails para ver todos los pasos de la migración.
Organiza los archivos de traducción#
Rails carga automáticamente todos los archivos *.yml que encuentre en config/locales/. Mantén el idioma de origen junto a sus versiones traducidas para que el directorio actúe como fuente única de verdad:
config/locales/
en.yml # Source locale
es.yml # Generated by Lingo.dev
fr.yml
de.ymlUn archivo en.yml típico combina cadenas simples, espacios de nombres anidados, interpolación %{name} y pluralización:
en:
hello: "Hello"
home:
welcome: "Welcome, %{name}!"
cta: "Get started"
notifications:
unread:
zero: "No unread notifications"
one: "1 unread notification"
other: "%{count} unread notifications"
errors:
messages:
blank: "can't be blank"Configura la CLI#
Instala la CLI, autentícate y vincula el proyecto a tu motor:
npm install -g @lingo.dev/cli
lingo login
lingo init
lingo linklingo init y lingo link crean .lingo/config.json con tus idiomas y el orgId y engineId de tu motor. Haz commit de ese archivo, junto con .lingo/lock.json, para que todas las máquinas y todas las ejecuciones de CI compartan el mismo estado.
Haz que una entrada de files[] apunte al archivo del idioma de origen y define el formato de forma explícita:
{
"orgId": "org_...",
"engineId": "eng_...",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de"],
"files": [{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" }]
}Aquí "format": "yaml-root-key" es obligatorio, no opcional. Una ruta .yml no permite saber si su clave raíz es un idioma o una configuración normal, así que la CLI no intenta adivinarlo: si omites format, el archivo se trata como yaml genérico, lo que traduce los valores y deja la raíz en en:, provocando el fallo silencioso descrito antes.
Los archivos de destino se derivan de la ruta de origen, así que config/locales/en.yml genera config/locales/es.yml, config/locales/fr.yml y config/locales/de.yml.
Rails también carga archivos por funcionalidad junto con en.yml: devise.en.yml, mailers.en.yml, activerecord.en.yml. Añade una segunda entrada con un glob para incluirlos:
{
"files": [
{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" },
{ "pattern": "config/locales/*.en.yml", "format": "yaml-root-key" }
]
}Los dos patrones no se solapan: el primero solo coincide con en.yml y el segundo solo con archivos que terminan en .en.yml, así que los archivos ya traducidos, como es.yml y devise.es.yml, nunca se toman como origen. devise.en.yml genera devise.es.yml.
Configura Rails para varios idiomas#
Indica a Rails qué idiomas están disponibles y cuál debe usar por defecto. En config/application.rb:
module YourApp
class Application < Rails::Application
config.i18n.available_locales = [:en, :es, :fr, :de]
config.i18n.default_locale = :en
config.i18n.fallbacks = [:en]
end
endSelecciona el idioma de cada solicitud en ApplicationController a partir de un parámetro de la URL o de la cabecera Accept-Language:
class ApplicationController < ActionController::Base
around_action :switch_locale
private
def switch_locale(&action)
locale = params[:locale] || http_accept_locale || I18n.default_locale
I18n.with_locale(locale, &action)
end
def http_accept_locale
header = request.headers["Accept-Language"].to_s
header.scan(/[a-z]{2}/).find { |l| I18n.available_locales.map(&:to_s).include?(l) }
end
def default_url_options
{ locale: I18n.locale }
end
endMuestra las traducciones en las vistas#
Usa los helpers t y l en las plantillas ERB. Un punto al principio de la clave se resuelve en función de la ruta de la vista actual, lo que mantiene las claves de traducción junto a las plantillas que las usan:
<h1><%= t(".welcome", name: @user_name) %></h1>
<p><%= t("notifications.unread", count: @unread_count) %></p>
<%= link_to t(".cta"), signup_path, class: "btn-primary" %>Añade un selector de idioma a tu layout:
<nav>
<% I18n.available_locales.each do |locale| %>
<%= link_to locale.upcase, url_for(locale: locale) %>
<% end %>
</nav>Traduce en local#
lingo push --waitpush sube los archivos de origen, los procesa con tu motor de localización y vuelve a escribir los archivos traducidos. --wait hace que el comando permanezca bloqueado hasta que lleguen los resultados; merece la pena indicarlo explícitamente, porque una próxima versión cambiará el comportamiento por defecto para que push envíe la ejecución y devuelva el control de inmediato, dejándote recoger los resultados con lingo pull.
Las ejecuciones posteriores traducen solo el delta: push calcula un hash del origen y lo compara con .lingo/lock.json, así que las entradas sin cambios no cuestan nada. La primera ejecución en un proyecto, o tras añadir un idioma, necesita el corpus completo:
lingo push --backfill-missingLimita una ejecución a un subconjunto de archivos pasando un glob:
lingo push "config/locales/**"Para recuperar resultados generados en otro entorno —otra máquina o CI—, ejecuta lingo pull.
Reinicia el servidor de Rails después de la primera ejecución de traducción para que se carguen los nuevos archivos YAML:
bin/rails serverVisita /es para ver el resultado en español.
Plurales#
Rails usa categorías de plural de CLDR: zero, one, two, few, many, other. Pasa un argumento count: a I18n.t y Rails elegirá la clave correspondiente:
t("notifications.unread", count: 0) # => "No unread notifications"
t("notifications.unread", count: 1) # => "1 unread notification"
t("notifications.unread", count: 12) # => "12 unread notifications"La CLI traduce cada variante plural en su sitio. Si tu idioma de destino necesita más categorías que one/other en inglés, defínelas en tu archivo fuente en.yml.
Automatízalo en CI#
La GitHub App de Lingo.dev traduce en cada push y pull request, del lado del servidor: sin runner y sin guardar ninguna API key en tu repositorio. Resuelve el motor a partir del .lingo/config.json versionado, así que orgId y engineId deben estar presentes en el archivo que subas al repositorio.
Si prefieres ejecutar la CLI en tu propio pipeline —GitHub Actions, GitLab CI o Bitbucket Pipelines—, consulta CI/CD Workflows. Proporciona LINGO_API_KEY como secreto y ejecuta lingo push --wait como cualquier otro paso de compilación.
Verifica antes de desplegar#
Usa lingo check como control previo al despliegue para que no llegue contenido sin traducir a producción. Sale con un estado distinto de cero si alguna entrada sigue necesitando traducción y no escribe nada:
lingo checkAñádelo como un paso de CI independiente antes de la precompilación de assets o de la construcción del contenedor:
- name: Verify translations
run: lingo check
- name: Precompile assets
run: bundle exec rails assets:precompile