
O Rails vem com uma estrutura de internacionalização integrada. Não é preciso muito mais para começar — a API de internacionalização faz parte da pilha e lida bem com o básico: tradução de strings estáticas, formatação de datas e números e gerenciamento da pluralização entre diferentes configurações regionais.
Mas “integrado” não significa “completo”. No momento em que suas necessidades se estendem a URLs localizadas, traduções em JavaScript ou SEO multilíngue, você está indo além do que o Rails oferece nativamente. Essas lacunas exigem decisões arquitetônicas deliberadas.
Vamos examinar as duas camadas: o que a API I18n do Rails cuida e onde você precisará implementar – ou delegar.
A API I18n oferece dois métodos principais: I18n.t para traduzir strings e I18n.l para localizar datas e números. Ambos leem arquivos YAML ou Ruby na pasta config/locales/, e o Rails os carrega automaticamente na inicialização.
Por padrão, isso abrange strings estáticas da interface do usuário, formatação de datas e números e mensagens de validação do ActiveRecord. Para a maioria dos projetos que envolvem a tradução de um idioma para outro, isso já é suficiente para o lançamento.
As lacunas ficam evidentes rapidamente à medida que se avança.
O Rails I18n não oferece suporte à estrutura de URLs: não há rotas localizadas, detecção de subdomínios nem prefixos de caminho. Ele não possui nenhum mecanismo para traduzir conteúdos armazenados no banco de dados, como nomes de produtos ou posts de blog. Os arquivos JavaScript ficam totalmente fora do pipeline. E o SEO multilíngue — que inclui elementos como tags hreflang, slugs traduzidos e mapas de site específicos para cada idioma — exige um trabalho à parte, que a estrutura não aborda.
É preciso saber onde fica o limite antes de começar a construir.
Você pode começar usando a gem rails-i18n.
O Rails fornece apenas dados de localização em inglês; portanto, sem eles, a função `I18n.l(Date.today)` falha em localizações que não sejam em inglês, e as strings do nível do framework, como as mensagens de validação do Active Record, permanecem em inglês, independentemente da localização atual.
# Gemfile
gem "rails-i18n"Em seguida, configure as configurações padrão do seu aplicativo:
# config/application.rb
config.i18n.default_locale = :en
config.i18n.available_locales = [:en, :fr]
config.i18n.enforce_available_locales = true
O parâmetro `enforce_available_locales = true` gera um erro se o seu aplicativo tentar definir uma localidade fora dessa lista. Sem ele, um erro de digitação ou um parâmetro de URL malformado define silenciosamente uma localidade não suportada em produção.
Embora o Rails moderno faça a varredura em um nível de profundidade, muitas vezes ele não detecta pastas profundamente aninhadas (como config/locales/views/products/). Adicionar esta linha garante que todos os subdiretórios sejam incluídos à medida que sua aplicação cresce:
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]Isso permite dividir os arquivos de tradução por domínio:
config/locales/
en/
models.yml
visualizações.yml
mailers.yml
fr/
models.yml
visualizações.yml
mailers.yml⚠️ O YAML interpreta valores como true e false como booleanos. Se você precisar deles como texto literal, coloque-os entre aspas explicitamente. As versões mais recentes do Ruby/YAML agora tratam yes e no como strings, mas colocá-los entre aspas continua sendo um hábito seguro por uma questão de compatibilidade:
pt:
respostas:
afirmativo: "sim"
negativo: "não"Isso detecta um tipo de erro que só ocorre quando uma tradução retorna “true” em vez de “sim” e sua visualização não exibe nada ou, pior ainda, exibe a string “true”.
Nas visualizações, t e l são os dois auxiliares que você usará constantemente.
O `l` localiza datas, horas e números de acordo com a configuração regional atual. Com o `rails-i18n` instalado, as definições de formato para mais de 100 configurações regionais já vêm incluídas. Sem ele, chamar `l(Date.today)` em uma configuração regional que não seja o inglês geralmente retornará uma mensagem do tipo “tradução ausente” ou uma data sem formatação, já que o Rails não possui os padrões de formato localizados necessários.
<%= l(Date.today) %>
<%= l(Date.today, format: :long) %>t traduz strings por chave. A forma completa é t("products.index.title"), mas nas visualizações você pode usar a sintaxe abreviada de pesquisa diferida:
<%= t(".title") %>O ponto inicial indica ao Rails para resolver a chave em relação ao caminho da visualização atual. Em app/views/products/index.html.erb, t(".title") é expandido automaticamente para t("products.index.title"). Isso mantém as chaves curtas e garante uma convenção de nomenclatura consistente sem esforço adicional.
For interpolation, use %{variable} in your YAML and pass the value as a keyword argument:
en:
welcome: "Hello, %{name}"<%= t(".welcome", name: current_user.name) %>Se uma tradução contiver HTML em que você confia, acrescente _html ao nome da chave. O Rails a marca automaticamente como segura, sem a necessidade de chamar o método html_safe.
en:
notice_html: "Please <strong>confirm</strong> your email."A pesquisa preguiçosa só funciona quando o Rails consegue inferir o caminho da visualização, o que significa que ela não funciona em tarefas em segundo plano e controladores de API. Nesses contextos, use sempre a chave completa.
Antes de escrever qualquer código de detecção de localidade, escolha uma estratégia. Existem cinco abordagens principais, e a escolha certa depende da estrutura do seu aplicativo e dos requisitos de SEO.
O prefixo de caminho, como /fr/products, é a opção mais comum para aplicativos Rails voltados para o público. Ele mantém a localização explícita em cada URL, o que permite que os mecanismos de busca indexem cada idioma de forma independente.
As estratégias de subdomínios e TLDs funcionam bem quando se busca uma identidade regional mais forte, mas acarretam uma sobrecarga na configuração do DNS e nos certificados SSL.
Parâmetros de URL, como /products?locale=fr, são adequados para ferramentas internas nas quais o SEO não é relevante.
As preferências armazenadas no banco de dados funcionam para aplicativos autenticados nos quais a configuração regional acompanha o usuário, e não a URL.
Seja qual for a estratégia escolhida, há um erro de implementação que causa pequenos erros de produção: usar `I18n.locale =` diretamente.
# Don't do this
before_action { I18n.locale = params[:locale] }I18n.locale = grava em Thread.current.
Se você estiver usando um servidor web Puma, ele reutiliza threads entre as solicitações. Se uma solicitação não definir explicitamente a localidade, ela herdará a configuração deixada pela solicitação anterior.
No desenvolvimento local com um único segmento de execução, isso nunca ocorre. Em produção, isso causa respostas intermitentes em idioma incorreto, difíceis de reproduzir e ainda mais difíceis de rastrear.
Em vez disso, use `I18n.with_locale` dentro de uma ação `around_action`:
around_action :switch_localed
ef switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
endwith_locale restaura a localidade anterior após o bloqueio ser encerrado, independentemente de como a solicitação termine.
Defina suas rotas na pasta /:locale e substitua o arquivo default_url_options para que o Rails insira automaticamente a localidade atual no início de cada helper de URL:
# config/routes.rb
scope "/:locale" do
resources :products
root "home#index"
end
# app/controllers/application_controller.rb
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale]
# Fallback to default if the param is missing or unsupported
valid_locale = I18n.available_locales.map(&:to_s).include?(locale) ? locale : I18n.default_locale
I18n.with_locale(valid_locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
Com a opção `default_url_options` definida, a URL `products_path` é renderizada automaticamente como `/fr/products ` quando a localização atual é :fr.
Se você quiser que a localidade padrão omita o prefixo, torne o segmento opcional com o escopo "(:locale)". Isso direciona para /products em inglês e para /fr/products em francês, mas cria ambiguidade. Um caminho como /about poderia corresponder tanto a uma localidade ausente quanto a um controlador chamado about, dependendo da ordem das rotas.
Para a detecção de subdomínios, leia o valor de `request.subdomains.first` e verifique se ele está de acordo com `available_locales` antes de definir qualquer coisa:
def extrair_localização_do_subdomínio
subdomain = request.subdomains.first
retornar nil se subdomain.blank? || subdomain == "www"
subdomínio se I18n.available_locales.map(&:to_s).include?(subdomain)
end
A verificação do www impede que ele seja tratado como uma configuração regional. No localhost, request.subdomains retorna uma matriz vazia, portanto, a detecção de subdomínios falha no ambiente de desenvolvimento, a menos que você use um servidor Pow ou adicione entradas ao arquivo /etc/hosts.
Uma coisa a ser configurada separadamente: a variável `default_url_options` definida no `ApplicationController` não é aplicada aos mailers nem às tarefas em segundo plano. Defina-a explicitamente para esses contextos:
Rails.application.routes.default_url_options = { host: "example.com", locale: :en }As rotas localizadas fornecem a estrutura de URL, mas as tags hreflang, os slugs traduzidos e os mapas do site multilíngues continuam sendo totalmente manuais após essa configuração.
A integração de proxy reverso Weglot lida com essa camada automaticamente, gerando tags hreflang e URLs específicas para cada idioma sem necessidade de configuração adicional.
A formação do plural em inglês é simples. Uma forma para o singular e outra para todos os demais casos.
en:
messages:
one: "%{count} message"
other: "%{count} messages"
A maioria das línguas não é assim tão simples.
O búlgaro, por exemplo, possui um plural regular e um “plural de contagem” especial para algumas palavras na forma masculina. Assim, ден (dia) torna-se дни (muitos dias) normalmente, mas два дена (dois dias) quando se trata de contagem.
bg:
cities:
one: "%{count} ден"
few: "%{count} дена"
many: "%{count} дни"
Sem o rails-i18n, o Rails não tem conhecimento dessas regras. Suas traduções para o búlgaro gerariam erros ou voltariam à outra forma em todos os casos.
O gem implementa a lógica de pluralização para mais de 100 configurações regionais, de modo que tudo é resolvido corretamente em búlgaro, russo, árabe, polonês e qualquer outro idioma com regras de plural diferentes das do inglês.
Os fallbacks são uma questão à parte e estão desativados por padrão. Sem eles, qualquer chave de tradução ausente resulta em uma mensagem de “tradução ausente” no ambiente de produção. É o tipo de coisa que acaba sendo lançada e aparece nas capturas de tela.
Habilite os fallbacks e defina um destino padrão no arquivo config/application.rb:
config.i18n.fallbacks = [I18n.default_locale]Com fallbacks = true, uma chave ausente na localidade atual recorre à default_locale. Para variantes regionais, é possível definir cadeias explícitas:
config.i18n.fallbacks = { "fr-CA": :fr, "en-GB": :en }Configure isso desde o início. Implementar um comportamento alternativo em um aplicativo que já possui traduções parciais em produção é mais difícil do que configurá-lo desde o início.
O JavaScript não tem acesso ao pipeline de internacionalização do Rails.
Não há nenhum helper `t`, carregador YAML ou ponte integrada entre seus arquivos de tradução e seus controladores Stimulus. Você precisa passar as traduções explicitamente do lado do servidor.
A abordagem mais simples no Stimulus é a API de valores. Defina a tradução como um valor no controlador, insira-a no HTML e leia-a no JavaScript:
# app/views/products/index.html.erb
<div data-controller="notification"
data-notification-message-value="<%= t('.success_message') %>">
O valor é renderizado no servidor usando o helper t padrão, portanto, ele respeita automaticamente a localização atual. No JavaScript, declare-o como um valor estático e acesse-o por meio da API de valores:
// app/javascript/controllers/notification_controller.js
export default class extends Controller {
static values = { message: String }
show() {
alert(this.messageValue)
}
}
Cada tradução é explícita e restrita ao controlador que precisa dela. Nada vaza para o escopo global.
Se um controlador precisar de várias traduções ao mesmo tempo, agrupá-las como um atributo de dados JSON é mais simples do que adicionar definições de valor individuais para cada string:
<div data-controller="cart"
data-cart-i18n-value="<%= { add: t('.add'), remove: t('.remove') }.to_json %>">
No JavaScript, declare i18n como um objeto e acesse as chaves individualmente:
static values = { i18n: Object }
add() {
console.log(this.i18nValue.add)
}
Para aplicativos em que o JavaScript precisa de amplo acesso à tradução em vários controladores, a gem i18n-js exporta seus arquivos YAML para um objeto JavaScript que você pode consultar como I18n.t("chave").
Funciona, mas acrescenta uma etapa à compilação e envia todo o conjunto de traduções para o cliente. Use-o quando os dois primeiros padrões se tornarem repetitivos, e não como padrão.
Ao usar Turbo Frames, se a URL de origem de um Turbo Frame omitir o prefixo de localidade, a solicitação geralmente falhará com um erro de roteamento (404), pois não corresponderá ao padrão do seu escopo "/:locale". Sempre use auxiliares de caminho sensíveis à localidade para as origens dos frames.
Isso não é um bug do Turbo. Trata-se, na verdade, de um descuido no roteamento, e a função `around_action` da seção de detecção de localização impede que isso ocorra, desde que todas as URLs no frame utilizem um helper de caminho sensível à localização.
O proxy reverso Weglot adota uma abordagem totalmente diferente. Ele traduz o DOM renderizado após o carregamento da página, de modo que o conteúdo renderizado pelo Stimulus e as sequências inseridas dinamicamente são suportados sem a necessidade de nenhuma configuração adicional.
Existem três categorias que estão totalmente fora do âmbito do que o Rails I18n lida:
Weglot trata tudo isso na camada de saída, em vez de na camada de código:
Dito isso, há algumas limitações que vale a pena conhecer antes de avaliá-lo:
Toda implementação de i18n no Rails se resume às mesmas decisões, tomadas aproximadamente na mesma ordem.
Primeiro, defina sua estratégia de URL antes de escrever qualquer código de detecção de localidade. Segmentos de caminho funcionam para a maioria dos aplicativos voltados para o público. Subdomínios fazem sentido quando a identidade regional é importante. Preferências armazenadas no banco de dados são adequadas para aplicativos autenticados, nos quais a localidade acompanha o usuário, e não a URL.
Em segundo lugar, decida como o JavaScript obtém as traduções. A API de valores do Stimulus cobre a maioria dos casos. Os atributos JSON são úteis quando um controlador precisa de várias strings de uma só vez.
Em terceiro lugar, decida o que você vai desenvolver manualmente. O Rails lida bem com strings estáticas. A camada de SEO, o fluxo de trabalho do tradutor e o conteúdo do banco de dados exigem decisões específicas. Você pode desenvolver cada um deles ou delegar a camada de saída a uma ferramenta como Weglot concentrar o tempo de desenvolvimento na própria aplicação.
Ignore completamente a camada de saída. Experimente Weglot por 14 dias e tenha SEO/GEO multilíngue, URLs traduzidas e detecção automática de conteúdo sem precisar mexer no seu código de internacionalização.
A melhor maneira de compreender o poder do Weglot experimentá-lo você mesmo. Teste-o gratuitamente e sem qualquer compromisso.
Um site de demonstração está disponível no seu painel de controle, caso ainda não esteja pronto para conectar o seu site.