NOTA: O framework Ruby I18n fornece todos os meios necessários para internacionalização/localização de sua aplicação Rails. Você também pode usar vários gems disponíveis para adicionar funcionalidades ou recursos adicionais. Consulte o gem rails-i18n para obter mais informações.
1 Como o I18n funciona no Ruby on Rails
A internacionalização é um problema complexo. As línguas naturais diferem de várias maneiras (por exemplo, nas regras de pluralização) que é difícil fornecer ferramentas para resolver todos os problemas de uma vez. Por esse motivo, a API I18n do Rails se concentra em:
- fornecer suporte para inglês e idiomas semelhantes por padrão
- tornar fácil personalizar e estender tudo para outros idiomas
Como parte dessa solução, todas as strings estáticas no framework Rails - por exemplo, mensagens de validação do Active Record, formatos de data e hora - foram internacionalizadas. Localizar uma aplicação Rails significa definir valores traduzidos para essas strings nos idiomas desejados.
Para localizar, armazenar e atualizar conteúdo em sua aplicação (por exemplo, traduzir postagens de blog), consulte a seção Traduzindo conteúdo do modelo.
1.1 A Arquitetura Geral da Biblioteca
Assim, o gem Ruby I18n é dividido em duas partes:
- A API pública do framework I18n - um módulo Ruby com métodos públicos que definem como a biblioteca funciona
- Um backend padrão (intencionalmente chamado de backend Simple) que implementa esses métodos
Como usuário, você deve sempre acessar apenas os métodos públicos no módulo I18n, mas é útil conhecer as capacidades do backend.
NOTA: É possível trocar o backend Simple fornecido por um mais poderoso, que armazenaria os dados de tradução em um banco de dados relacional, dicionário GetText ou similar. Consulte a seção Usando backends diferentes abaixo.
1.2 A API Pública do I18n
Os métodos mais importantes da API I18n são:
translate # Procurar traduções de texto
localize # Localizar objetos de data e hora em formatos locais
Eles têm os aliases #t e #l, então você pode usá-los assim:
I18n.t 'store.title'
I18n.l Time.now
Também existem leitores e escritores de atributos para os seguintes atributos:
load_path # Anunciar seus arquivos de tradução personalizados
locale # Obter e definir a localidade atual
default_locale # Obter e definir a localidade padrão
available_locales # Localidades permitidas disponíveis para a aplicação
enforce_available_locales # Aplicar permissão de localidade (verdadeiro ou falso)
exception_handler # Usar um manipulador de exceção diferente
backend # Usar um backend diferente
Portanto, vamos internacionalizar uma aplicação Rails simples desde o início nos próximos capítulos!
2 Configurando a Aplicação Rails para Internacionalização
Existem alguns passos para começar a usar o suporte I18n em uma aplicação Rails.
2.1 Configurar o Módulo I18n
Seguindo a filosofia de convenção sobre configuração, o Rails I18n fornece strings de tradução padrão razoáveis. Quando são necessárias diferentes strings de tradução, elas podem ser substituídas.
O Rails adiciona todos os arquivos .rb
e .yml
do diretório config/locales
ao caminho de carregamento de traduções, automaticamente.
O arquivo de localização padrão en.yml
neste diretório contém um par de strings de tradução de exemplo:
en:
hello: "Hello world"
Isso significa que, na localização :en
, a chave hello será mapeada para a string Hello world. Todas as strings dentro do Rails são internacionalizadas dessa maneira, veja por exemplo as mensagens de validação do Active Model no arquivo activemodel/lib/active_model/locale/en.yml
ou os formatos de data e hora no arquivo activesupport/lib/active_support/locale/en.yml
. Você pode usar YAML ou hashes Ruby padrão para armazenar traduções no backend padrão (Simple).
A biblioteca I18n usará o inglês como localização padrão, ou seja, se uma localização diferente não for definida, :en
será usada para buscar traduções.
NOTA: A biblioteca i18n adota uma abordagem pragmática para as chaves de localização (após algumas discussões), incluindo apenas a parte de localização ("idioma"), como :en
, :pl
, e não a parte de região, como :"en-US"
ou :"en-GB"
, que são tradicionalmente usadas para separar "idiomas" e "configurações regionais" ou "dialetos". Muitos aplicativos internacionais usam apenas o elemento "idioma" de uma localização, como :cs
, :th
ou :es
(para tcheco, tailandês e espanhol). No entanto, também existem diferenças regionais dentro de diferentes grupos de idiomas que podem ser importantes. Por exemplo, na localização :"en-US"
, você teria $ como símbolo de moeda, enquanto na :"en-GB"
, você teria £. Nada impede que você separe configurações regionais e outras dessa maneira: você só precisa fornecer a localização completa "Inglês - Reino Unido" em um dicionário :"en-GB"
.
O caminho de carregamento de traduções (I18n.load_path
) é uma matriz de caminhos para arquivos que serão carregados automaticamente. Configurar esse caminho permite a personalização da estrutura do diretório de traduções e do esquema de nomenclatura de arquivos.
NOTA: O backend carrega as traduções sob demanda quando uma tradução é buscada pela primeira vez. Esse backend pode ser substituído por outro mesmo depois que as traduções já foram anunciadas.
Você pode alterar a localização padrão e configurar os caminhos de carregamento de traduções em config/application.rb
da seguinte forma:
config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
config.i18n.default_locale = :de
O caminho de carregamento deve ser especificado antes de qualquer busca de traduções. Para alterar a localização padrão a partir de um inicializador em vez de config/application.rb
:
# config/initializers/locale.rb
# Onde a biblioteca I18n deve procurar arquivos de tradução
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
# Localizações permitidas disponíveis para o aplicativo
I18n.available_locales = [:en, :pt]
# Definir localização padrão para algo diferente de :en
I18n.default_locale = :pt
Observe que anexar diretamente a I18n.load_path
em vez de usar a configuração do I18n do aplicativo não substituirá as traduções de gems externas.
2.2 Gerenciando a Localização em Requisições
Um aplicativo localizado provavelmente precisará fornecer suporte para várias localizações. Para isso, a localização deve ser definida no início de cada requisição para que todas as strings sejam traduzidas usando a localização desejada durante a vida útil dessa requisição.
A localização padrão é usada para todas as traduções, a menos que I18n.locale=
ou I18n.with_locale
seja usado.
I18n.locale
pode vazar para requisições subsequentes atendidas pela mesma thread/processo se não for definido consistentemente em cada controlador. Por exemplo, executar I18n.locale = :es
em uma requisição POST terá efeitos em todas as requisições posteriores para controladores que não definem a localização, mas apenas nessa thread/processo específico. Por esse motivo, em vez de I18n.locale =
, você pode usar I18n.with_locale
, que não tem esse problema de vazamento.
A localização pode ser definida em uma around_action
no ApplicationController
:
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
Este exemplo ilustra isso usando um parâmetro de consulta de URL para definir a localização (por exemplo, http://example.com/books?locale=pt
). Com essa abordagem, http://localhost:3000?locale=pt
renderiza a localização em português, enquanto http://localhost:3000?locale=de
carrega uma localização em alemão.
A localização pode ser definida usando uma das muitas abordagens diferentes.
2.2.1 Definindo a Localização a partir do Nome de Domínio
Uma opção que você tem é definir a localização a partir do nome de domínio onde seu aplicativo é executado. Por exemplo, queremos que www.example.com
carregue a localização em inglês (ou padrão) e www.example.es
carregue a localização em espanhol. Assim, o nome de domínio de nível superior é usado para a definição da localização. Isso tem várias vantagens:
* A localidade é uma parte óbvia da URL.
* As pessoas intuitivamente entendem em qual idioma o conteúdo será exibido.
* É muito trivial de implementar no Rails.
* Os mecanismos de busca parecem gostar que o conteúdo em diferentes idiomas esteja em domínios diferentes e interligados.
Você pode implementar da seguinte forma no seu ApplicationController
:
around_action :switch_locale
def switch_locale(&action)
locale = extract_locale_from_tld || I18n.default_locale
I18n.with_locale(locale, &action)
end
# Obtenha a localidade do domínio de nível superior ou retorne +nil+ se essa localidade não estiver disponível
# Você precisa adicionar algo como:
# 127.0.0.1 aplicacao.com
# 127.0.0.1 aplicacao.it
# 127.0.0.1 aplicacao.pl
# no seu arquivo /etc/hosts para testar isso localmente
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Também podemos definir a localidade a partir do subdomínio de forma muito semelhante:
# Obtenha o código de localidade a partir do subdomínio da requisição (como http://it.aplicacao.local:3000)
# Você precisa adicionar algo como:
# 127.0.0.1 gr.aplicacao.local
# no seu arquivo /etc/hosts para testar isso localmente
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
Se a sua aplicação incluir um menu de troca de localidade, você teria algo assim:
link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")
supondo que você defina APP_CONFIG[:deutsch_website_url]
para algum valor como http://www.aplicacao.de
.
Essa solução tem as vantagens mencionadas anteriormente, no entanto, você pode não ser capaz ou não querer fornecer diferentes localizações ("versões de idioma") em domínios diferentes. A solução mais óbvia seria incluir o código de localidade nos parâmetros da URL (ou caminho da requisição).
2.2.2 Definindo a Localidade a partir dos Parâmetros da URL
A forma mais comum de definir (e passar) a localidade seria incluí-la nos parâmetros da URL, como fizemos no I18n.with_locale(params[:locale], &action)
around_action no primeiro exemplo. Gostaríamos de ter URLs como www.exemplo.com/livros?locale=ja
ou www.exemplo.com/ja/livros
nesse caso.
Essa abordagem tem quase o mesmo conjunto de vantagens de definir a localidade a partir do nome de domínio: ou seja, é RESTful e está de acordo com o restante da World Wide Web. No entanto, requer um pouco mais de trabalho para implementar.
Obter a localidade dos params
e defini-la adequadamente não é difícil; incluí-la em cada URL e, assim, passá-la pelas requisições é que é complicado. Incluir uma opção explícita em cada URL, por exemplo, link_to(books_url(locale: I18n.locale))
, seria tedioso e provavelmente impossível.
O Rails contém uma infraestrutura para "centralizar decisões dinâmicas sobre as URLs" em seu ApplicationController#default_url_options
, que é útil exatamente nesse cenário: ele nos permite definir "padrões" para url_for
e métodos auxiliares dependentes dele (implementando/sobrescrevendo default_url_options
).
Podemos incluir algo assim no nosso ApplicationController
então:
# app/controllers/application_controller.rb
def default_url_options
{ locale: I18n.locale }
end
Todo método auxiliar dependente de url_for
(por exemplo, auxiliares para rotas nomeadas como root_path
ou root_url
, rotas de recursos como books_path
ou books_url
, etc.) agora incluirá automaticamente a localidade na string de consulta, assim: http://localhost:3001/?locale=ja
.
Você pode ficar satisfeito com isso. No entanto, isso afeta a legibilidade das URLs quando a localidade "fica" no final de cada URL da sua aplicação. Além disso, do ponto de vista arquitetural, a localidade geralmente está hierarquicamente acima das outras partes do domínio da aplicação: e as URLs devem refletir isso.
Provavelmente você quer que as URLs se pareçam com isso: http://www.exemplo.com/en/livros
(que carrega a localidade em inglês) e http://www.exemplo.com/nl/livros
(que carrega a localidade em holandês). Isso é possível com a estratégia de "sobrescrever default_url_options
" mencionada acima: você só precisa configurar suas rotas com scope
:
# config/routes.rb
scope "/:locale" do
resources :books
end
Agora, quando você chamar o método books_path
, você deve obter "/en/livros"
(para a localidade padrão). Uma URL como http://localhost:3001/nl/livros
deve carregar a localidade holandesa e, em seguida, chamadas subsequentes para books_path
devem retornar "/nl/livros"
(porque a localidade mudou).
ATENÇÃO. Como o valor de retorno de default_url_options
é armazenado em cache por requisição, as URLs em um seletor de localidade não podem ser geradas invocando auxiliares em um loop que define a I18n.locale
correspondente em cada iteração. Em vez disso, deixe I18n.locale
intocado e passe uma opção :locale
explícita para o auxiliar ou edite request.original_fullpath
.
Se você não quiser forçar o uso de uma localidade em suas rotas, você pode usar um escopo de caminho opcional (indicado pelos parênteses), assim:
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
resources :books
end
Com essa abordagem, você não receberá um Routing Error
ao acessar seus recursos, como http://localhost:3001/books
, sem um local definido. Isso é útil quando você deseja usar o local padrão quando nenhum for especificado.
É claro que você precisa ter cuidado especial com a URL raiz (geralmente "homepage" ou "dashboard") do seu aplicativo. Uma URL como http://localhost:3001/nl
não funcionará automaticamente, porque a declaração root to: "dashboard#index"
no seu routes.rb
não leva em consideração o local. (E com razão: há apenas uma URL "root".)
Provavelmente, você precisará mapear URLs como estas:
# config/routes.rb
get '/:locale' => 'dashboard#index'
Tenha cuidado especial com a ordem das suas rotas, para que essa declaração de rota não "interfira" em outras. (Você pode querer adicioná-la diretamente antes da declaração root :to
.)
NOTA: Dê uma olhada em várias gems que simplificam o trabalho com rotas: routing_filter, route_translator.
2.2.3 Definindo o Local a partir das Preferências do Usuário
Um aplicativo com usuários autenticados pode permitir que os usuários definam uma preferência de local através da interface do aplicativo. Com essa abordagem, a preferência de local selecionada pelo usuário é persistida no banco de dados e usada para definir o local para solicitações autenticadas por esse usuário.
around_action :switch_locale
def switch_locale(&action)
locale = current_user.try(:locale) || I18n.default_locale
I18n.with_locale(locale, &action)
end
2.2.4 Escolhendo um Local Implícito
Quando um local explícito não foi definido para uma solicitação (por exemplo, através de um dos métodos acima), um aplicativo deve tentar inferir o local desejado.
2.2.4.1 Inferindo o Local a partir do Cabeçalho de Idioma
O cabeçalho HTTP Accept-Language
indica o idioma preferido para a resposta da solicitação. Os navegadores definem esse valor de cabeçalho com base nas configurações de preferência de idioma do usuário, tornando-o uma boa primeira escolha ao inferir um local.
Uma implementação trivial do uso de um cabeçalho Accept-Language
seria:
def switch_locale(&action)
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
locale = extract_locale_from_accept_language_header
logger.debug "* Locale set to '#{locale}'"
I18n.with_locale(locale, &action)
end
private
def extract_locale_from_accept_language_header
request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
end
Na prática, é necessário um código mais robusto para fazer isso de forma confiável. A biblioteca http_accept_language de Iain Hecker ou o middleware Rack locale de Ryan Tomayko fornecem soluções para esse problema.
2.2.4.2 Inferindo o Local a partir da Geolocalização do IP
O endereço IP do cliente que faz a solicitação pode ser usado para inferir a região do cliente e, assim, seu local. Serviços como GeoLite2 Country ou gems como geocoder podem ser usados para implementar essa abordagem.
Em geral, essa abordagem é muito menos confiável do que usar o cabeçalho de idioma e não é recomendada para a maioria dos aplicativos da web.
2.2.5 Armazenando o Local na Sessão ou Cookies
ATENÇÃO: Você pode ser tentado a armazenar o local escolhido em uma sessão ou um cookie. No entanto, não faça isso. O local deve ser transparente e fazer parte da URL. Dessa forma, você não quebrará as suposições básicas das pessoas sobre a web em si: se você enviar uma URL para um amigo, eles devem ver a mesma página e conteúdo que você. Uma palavra elegante para isso seria que você está sendo RESTful. Leia mais sobre a abordagem RESTful nos artigos de Stefan Tilkov. Às vezes, há exceções a essa regra e elas são discutidas abaixo.
3 Internacionalização e Localização
OK! Agora você inicializou o suporte I18n para o seu aplicativo Ruby on Rails e informou qual local usar e como preservá-lo entre as solicitações.
Agora precisamos internacionalizar nosso aplicativo, abstraindo cada elemento específico do local. Por fim, precisamos localizá-lo, fornecendo as traduções necessárias para essas abstrações.
Dado o seguinte exemplo:
# config/routes.rb
Rails.application.routes.draw do
root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
flash[:notice] = "Hello Flash"
end
end
<!-- app/views/home/index.html.erb -->
<h1>Hello World</h1>
<p><%= flash[:notice] %></p>
3.1 Abstraindo Código Localizado
Em nosso código, há duas strings escritas em inglês que serão renderizadas em nossa resposta ("Hello Flash" e "Hello World"). Para internacionalizar esse código, essas strings precisam ser substituídas por chamadas ao helper #t
do Rails com uma chave apropriada para cada string:
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
flash[:notice] = t(:hello_flash)
end
end
<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
Agora, quando essa visualização for renderizada, ela mostrará uma mensagem de erro que informa que as traduções para as chaves :hello_world
e :hello_flash
estão faltando.
NOTA: O Rails adiciona um método auxiliar t
(translate
) às suas visualizações para que você não precise escrever I18n.t
o tempo todo. Além disso, esse auxiliar capturará as traduções ausentes e envolverá a mensagem de erro resultante em uma <span class="translation_missing">
.
3.2 Fornecendo Traduções para Strings Internacionalizadas
Adicione as traduções ausentes nos arquivos de dicionário de tradução:
# config/locales/en.yml
en:
hello_world: Olá mundo!
hello_flash: Olá flash!
# config/locales/pirate.yml
pirate:
hello_world: Ahoy Mundo
hello_flash: Ahoy Flash
Como o default_locale
não foi alterado, as traduções usam a localidade :en
e a resposta renderiza as strings em inglês:
Se a localidade for definida via URL para a localidade pirata (http://localhost:3000?locale=pirate
), a resposta renderizará as strings piratas:
NOTA: Você precisa reiniciar o servidor quando adicionar novos arquivos de localidade.
Você pode usar arquivos YAML (.yml
) ou Ruby simples (.rb
) para armazenar suas traduções no SimpleStore. YAML é a opção preferida entre os desenvolvedores do Rails. No entanto, ele tem uma grande desvantagem. O YAML é muito sensível a espaços em branco e caracteres especiais, então a aplicação pode não carregar corretamente o dicionário. Arquivos Ruby farão com que sua aplicação falhe na primeira solicitação, então você pode facilmente encontrar o que está errado. (Se você encontrar algum problema estranho com dicionários YAML, tente colocar a parte relevante do dicionário em um arquivo Ruby.)
Se suas traduções estiverem armazenadas em arquivos YAML, certas chaves devem ser escapadas. Elas são:
- true, on, yes
- false, off, no
Exemplos:
# config/locales/en.yml
en:
success:
'true': 'Verdadeiro!'
'on': 'Ligado!'
'false': 'Falso!'
failure:
true: 'Verdadeiro!'
off: 'Desligado!'
false: 'Falso!'
I18n.t 'success.true' # => 'Verdadeiro!'
I18n.t 'success.on' # => 'Ligado!'
I18n.t 'success.false' # => 'Falso!'
I18n.t 'failure.false' # => Tradução Ausente
I18n.t 'failure.off' # => Tradução Ausente
I18n.t 'failure.true' # => Tradução Ausente
3.3 Passando Variáveis para Traduções
Uma consideração importante para internacionalizar com sucesso uma aplicação é evitar fazer suposições incorretas sobre regras gramaticais ao abstrair código localizado. Regras gramaticais que parecem fundamentais em uma localidade podem não ser verdadeiras em outra.
A abstração incorreta é mostrada no exemplo a seguir, onde suposições são feitas sobre a ordem das diferentes partes da tradução. Observe que o Rails fornece um auxiliar number_to_currency
para lidar com o caso a seguir.
<!-- app/views/products/show.html.erb -->
<%= "#{t('currency')}#{@product.price}" %>
# config/locales/en.yml
en:
currency: "$"
# config/locales/es.yml
es:
currency: "€"
Se o preço do produto for 10, a tradução correta para o espanhol é "10 €" em vez de "€10", mas a abstração não pode fornecer isso.
Para criar uma abstração correta, a gem I18n vem com um recurso chamado interpolação de variáveis que permite usar variáveis nas definições de tradução e passar os valores dessas variáveis para o método de tradução.
A abstração correta é mostrada no exemplo a seguir:
<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
# config/locales/en.yml
en:
product_price: "$%{price}"
# config/locales/es.yml
es:
product_price: "%{price} €"
Todas as decisões gramaticais e de pontuação são feitas na própria definição, para que a abstração possa fornecer uma tradução correta.
NOTA: As palavras-chave default
e scope
são reservadas e não podem ser usadas como nomes de variáveis. Se forem usadas, uma exceção I18n::ReservedInterpolationKey
será lançada. Se uma tradução espera uma variável de interpolação, mas esta não foi passada para #translate
, uma exceção I18n::MissingInterpolationArgument
será lançada.
3.4 Adicionando Formatos de Data/Hora
OK! Agora vamos adicionar um carimbo de data/hora à visualização, para que possamos demonstrar também o recurso de localização de data/hora. Para localizar o formato de hora, você passa o objeto Time para I18n.l
ou (preferencialmente) usa o auxiliar #l
do Rails. Você pode escolher um formato passando a opção :format
- por padrão, o formato :default
é usado.
<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
<p><%= l Time.now, format: :short %></p>
E em nosso arquivo de traduções piratas, vamos adicionar um formato de hora (já está presente nos padrões do Rails para o inglês):
# config/locales/pirate.yml
pirate:
time:
formats:
short: "arrrround %H'ish"
Então, isso resultaria em:
DICA: Agora você pode precisar adicionar mais alguns formatos de data/hora para fazer o backend do I18n funcionar como esperado (pelo menos para a localidade 'pirate'). Claro, há uma grande chance de que alguém já tenha feito todo o trabalho traduzindo os padrões do Rails para sua localidade. Consulte o repositório rails-i18n no GitHub para um arquivo de vários arquivos de localidade. Quando você colocar esses arquivos no diretório config/locales/
, eles estarão automaticamente prontos para uso.
3.5 Regras de Inflexão para Outras Localidades
O Rails permite que você defina regras de inflexão (como regras para singularização e pluralização) para localidades diferentes do inglês. No arquivo config/initializers/inflections.rb
, você pode definir essas regras para várias localidades. O inicializador contém um exemplo padrão para especificar regras adicionais para o inglês; siga esse formato para outras localidades conforme necessário.
3.6 Visualizações Localizadas
Digamos que você tenha um BooksController em sua aplicação. Sua ação index renderiza conteúdo no modelo app/views/books/index.html.erb
. Quando você coloca uma variante localizada deste modelo: index.es.html.erb
no mesmo diretório, o Rails irá renderizar o conteúdo neste modelo quando a localidade estiver definida como :es
. Quando a localidade estiver definida como a localidade padrão, a visualização genérica index.html.erb
será usada. (Versões futuras do Rails podem trazer essa localização automágica para ativos em public
, etc.)
Você pode aproveitar esse recurso, por exemplo, ao trabalhar com uma grande quantidade de conteúdo estático, que seria difícil de colocar em dicionários YAML ou Ruby. No entanto, tenha em mente que qualquer alteração que você deseje fazer posteriormente no modelo deve ser propagada para todos eles.
3.7 Organização de Arquivos de Localidade
Quando você está usando o SimpleStore padrão fornecido pela biblioteca i18n, os dicionários são armazenados em arquivos de texto simples no disco. Colocar traduções para todas as partes de sua aplicação em um único arquivo por localidade pode ser difícil de gerenciar. Você pode armazenar esses arquivos em uma hierarquia que faça sentido para você.
Por exemplo, seu diretório config/locales
pode ser assim:
|-defaults
|---es.yml
|---en.yml
|-models
|---book
|-----es.yml
|-----en.yml
|-views
|---defaults
|-----es.yml
|-----en.yml
|---books
|-----es.yml
|-----en.yml
|---users
|-----es.yml
|-----en.yml
|---navigation
|-----es.yml
|-----en.yml
Dessa forma, você pode separar os nomes de modelos e atributos de modelos do texto dentro das visualizações, e tudo isso dos "padrões" (por exemplo, formatos de data e hora). Outros armazenamentos para a biblioteca i18n podem fornecer meios diferentes de separação.
NOTA: O mecanismo de carregamento de localidade padrão no Rails não carrega arquivos de localidade em dicionários aninhados, como temos aqui. Portanto, para que isso funcione, devemos informar explicitamente ao Rails para procurar mais:
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
4 Visão Geral dos Recursos da API I18n
Você deve ter um bom entendimento de como usar a biblioteca i18n agora e saber como internacionalizar uma aplicação Rails básica. Nos próximos capítulos, abordaremos seus recursos com mais profundidade.
Esses capítulos mostrarão exemplos usando tanto o método I18n.translate
quanto o método auxiliar de visualização translate
(observando os recursos adicionais fornecidos pelo método auxiliar de visualização).
Serão abordados recursos como estes:
- busca de traduções
- interpolação de dados nas traduções
- pluralização de traduções
- uso de traduções HTML seguras (apenas método auxiliar de visualização)
- localização de datas, números, moedas, etc.
4.1 Busca de Traduções
4.1.1 Busca Básica, Escopos e Chaves Aninhadas
As traduções são buscadas por chaves que podem ser tanto símbolos quanto strings, então essas chamadas são equivalentes:
I18n.t :message
I18n.t 'message'
O método translate
também aceita uma opção :scope
que pode conter uma ou mais chaves adicionais que serão usadas para especificar um "namespace" ou escopo para uma chave de tradução:
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
Isso busca a mensagem :record_invalid
nas mensagens de erro do Active Record.
Além disso, tanto a chave quanto os escopos podem ser especificados como chaves separadas por ponto, como em:
I18n.translate "activerecord.errors.messages.record_invalid"
Assim, as seguintes chamadas são equivalentes:
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
4.1.2 Padrões
Quando uma opção :default
é fornecida, seu valor será retornado se a tradução estiver ausente:
I18n.t :missing, default: 'Not here'
# => 'Not here'
Se o valor :default
for um símbolo, ele será usado como uma chave e traduzido. É possível fornecer vários valores como padrão. O primeiro que resultar em um valor será retornado.
Por exemplo, o seguinte primeiro tenta traduzir a chave :missing
e depois a chave :also_missing
. Como ambos não produzem um resultado, a string "Not here" será retornada:
I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'
4.1.3 Busca em Massa e em Namespace
Para buscar várias traduções de uma só vez, um array de chaves pode ser passado: ```ruby I18n.t [:odd, :even], scope: 'errors.messages'
=> ["deve ser ímpar", "deve ser par"]
Além disso, uma chave pode ser traduzida para um hash agrupado de traduções aninhadas. Por exemplo, é possível receber **todas** as mensagens de erro do Active Record como um Hash com:
```ruby
I18n.t 'errors.messages'
# => {:inclusion=>"não está incluído na lista", :exclusion=> ... }
Se você deseja realizar interpolação em um hash de traduções em massa, é necessário passar deep_interpolation: true
como parâmetro. Quando você tem o seguinte dicionário:
en:
welcome:
title: "Bem-vindo!"
content: "Bem-vindo ao %{app_name}"
então a interpolação aninhada será ignorada sem a configuração:
I18n.t 'welcome', app_name: 'livraria'
# => {:title=>"Bem-vindo!", :content=>"Bem-vindo ao %{app_name}"}
I18n.t 'welcome', deep_interpolation: true, app_name: 'livraria'
# => {:title=>"Bem-vindo!", :content=>"Bem-vindo à livraria"}
4.1.4 Busca "Preguiçosa"
O Rails implementa uma maneira conveniente de buscar a localidade dentro de views. Quando você tem o seguinte dicionário:
es:
books:
index:
title: "Título"
você pode buscar o valor de books.index.title
dentro do template app/views/books/index.html.erb
assim (observe o ponto):
<%= t '.title' %>
NOTA: A busca automática de escopo por partial só está disponível a partir do método auxiliar de visualização translate
.
A busca "preguiçosa" também pode ser usada em controladores:
en:
books:
create:
success: Livro criado!
Isso é útil para definir mensagens flash, por exemplo:
class BooksController < ApplicationController
def create
# ...
redirect_to books_url, notice: t('.success')
end
end
4.2 Pluralização
Em muitos idiomas - incluindo o inglês - existem apenas duas formas, singular e plural, para uma determinada string, por exemplo, "1 mensagem" e "2 mensagens". Outros idiomas (Árabe, Japonês, Russo e muitos outros) têm gramáticas diferentes que possuem formas plurais adicionais ou menos formas plurais. Assim, a API do I18n fornece um recurso de pluralização flexível.
A variável de interpolação :count
tem um papel especial, pois ela é interpolada na tradução e usada para escolher uma pluralização das traduções de acordo com as regras de pluralização definidas no backend de pluralização. Por padrão, apenas as regras de pluralização em inglês são aplicadas.
I18n.backend.store_translations :en, inbox: {
zero: 'nenhuma mensagem', # opcional
one: 'uma mensagem',
other: '%{count} mensagens'
}
I18n.translate :inbox, count: 2
# => '2 mensagens'
I18n.translate :inbox, count: 1
# => 'uma mensagem'
I18n.translate :inbox, count: 0
# => 'nenhuma mensagem'
O algoritmo para pluralizações em :en
é simplesmente:
lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]
A tradução denominada :one
é considerada singular, e o :other
é usado como plural. Se a contagem for zero e uma entrada :zero
estiver presente, ela será usada em vez de :other
.
Se a busca pela chave não retornar um Hash adequado para pluralização, uma exceção I18n::InvalidPluralizationData
será lançada.
4.2.1 Regras Específicas de Localidade
A gem I18n fornece um backend de pluralização que pode ser usado para habilitar regras específicas de localidade. Inclua-o no backend Simple e adicione os algoritmos de pluralização localizados ao armazenamento de tradução, como i18n.plural.rule
.
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }
I18n.backend.store_translations :pt, apples: { one: 'um ou nenhum', other: 'mais de um' }
I18n.t :apples, count: 0, locale: :pt
# => 'um ou nenhum'
Alternativamente, a gem separada rails-i18n pode ser usada para fornecer um conjunto mais completo de regras de pluralização específicas de localidade.
4.3 Definindo e Passando uma Localidade
A localidade pode ser definida pseudo-globalmente para I18n.locale
(que usa Thread.current
da mesma forma que, por exemplo, Time.zone
) ou pode ser passada como uma opção para #translate
e #localize
.
Se nenhuma localidade for passada, I18n.locale
será usada:
I18n.locale = :de
I18n.t :foo
I18n.l Time.now
Passando explicitamente uma localidade:
I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de
O I18n.locale
tem como padrão I18n.default_locale
, que tem como padrão :en
. A localidade padrão pode ser definida assim:
I18n.default_locale = :de
4.4 Usando Traduções HTML Seguras
As chaves com o sufixo '_html' e as chaves chamadas 'html' são marcadas como seguras em HTML. Quando você as usa em visualizações, o HTML não será escapado.
# config/locales/en.yml
en:
welcome: <b>bem-vindo!</b>
hello_html: <b>olá!</b>
title:
html: <b>título!</b>
<!-- app/views/home/index.html.erb -->
<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>
A interpolação é escapada conforme necessário. Por exemplo, dado:
en:
welcome_html: "<b>Bem-vindo %{username}!</b>"
você pode passar com segurança o nome de usuário definido pelo usuário:
<%# Isso é seguro, será escapado se necessário. %>
<%= t('welcome_html', username: @current_user.username) %>
As strings seguras, por outro lado, são interpoladas literalmente.
NOTA: A conversão automática para texto de tradução seguro em HTML está disponível apenas a partir do método auxiliar translate
(ou t
). Isso funciona em visualizações e controladores.
4.5 Traduções para Modelos Active Record
Você pode usar os métodos Model.model_name.human
e Model.human_attribute_name(attribute)
para procurar traduções para o nome do seu modelo e atributos.
Por exemplo, ao adicionar as seguintes traduções:
en:
activerecord:
models:
user: Cliente
attributes:
user:
login: "Nome de usuário"
# irá traduzir o atributo "login" do modelo User como "Nome de usuário"
Então, User.model_name.human
retornará "Cliente" e User.human_attribute_name("login")
retornará "Nome de usuário".
Você também pode definir uma forma no plural para os nomes dos modelos, adicionando da seguinte forma:
en:
activerecord:
models:
user:
one: Cliente
other: Clientes
Então, User.model_name.human(count: 2)
retornará "Clientes". Com count: 1
ou sem parâmetros, retornará "Cliente".
Caso precise acessar atributos aninhados dentro de um determinado modelo, você deve aninhá-los em model/attribute
no nível do modelo do arquivo de tradução:
en:
activerecord:
attributes:
user/role:
admin: "Administrador"
contributor: "Contribuidor"
Então, User.human_attribute_name("role.admin")
retornará "Administrador".
NOTA: Se você estiver usando uma classe que inclui ActiveModel
e não herda de ActiveRecord::Base
, substitua activerecord
por activemodel
nos caminhos das chaves acima.
4.5.1 Escopos de Mensagens de Erro
As mensagens de erro de validação do Active Record também podem ser traduzidas facilmente. O Active Record oferece alguns namespaces onde você pode colocar suas traduções de mensagens para fornecer mensagens e traduções diferentes para determinados modelos, atributos e/ou validações. Ele também leva em conta a herança de tabela única de forma transparente.
Isso oferece meios bastante poderosos para ajustar flexivelmente suas mensagens às necessidades de sua aplicação.
Considere um modelo User com uma validação para o atributo name assim:
class User < ApplicationRecord
validates :name, presence: true
end
A chave para a mensagem de erro neste caso é :blank
. O Active Record procurará essa chave nos namespaces:
activerecord.errors.models.[nome_do_modelo].attributes.[nome_do_atributo]
activerecord.errors.models.[nome_do_modelo]
activerecord.errors.messages
errors.attributes.[nome_do_atributo]
errors.messages
Assim, em nosso exemplo, ele tentará as seguintes chaves nesta ordem e retornará o primeiro resultado:
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
Quando seus modelos também estão usando herança, as mensagens são procuradas na cadeia de herança.
Por exemplo, você pode ter um modelo Admin herdando de User:
class Admin < User
validates :name, presence: true
end
Então, o Active Record procurará as mensagens nesta ordem:
activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank
Dessa forma, você pode fornecer traduções especiais para várias mensagens de erro em diferentes pontos na cadeia de herança do seu modelo e nos escopos de atributos, modelos ou padrão.
4.5.2 Interpolação de Mensagens de Erro
O nome do modelo traduzido, o nome do atributo traduzido e o valor estão sempre disponíveis para interpolação como model
, attribute
e value
, respectivamente.
Portanto, por exemplo, em vez da mensagem de erro padrão "não pode ficar em branco"
, você pode usar o nome do atributo assim: "Por favor, preencha o(a) %{attribute}"
.
count
, quando disponível, pode ser usado para pluralização, se presente:
validação | com opção | mensagem | interpolação |
---|---|---|---|
confirmation | - | :confirmation | attribute |
acceptance | - | :accepted | - |
presence | - | :blank | - |
absence | - | :present | - |
length | :within, :in | :too_short | count |
length | :within, :in | :too_long | count |
length | :is | :wrong_length | count |
length | :minimum | :too_short | count |
length | :maximum | :too_long | count |
uniqueness | - | :taken | - |
format | - | :invalid | - |
inclusion | - | :inclusion | - |
exclusion | - | :exclusion | - |
associated | - | :invalid | - |
non-optional association | - | :required | - |
numericality | - | :not_a_number | - |
numericality | :greater_than | :greater_than | count |
numericality | :greater_than_or_equal_to | :greater_than_or_equal_to | count |
numericality | :equal_to | :equal_to | count |
numericality | :less_than | :less_than | count |
numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
numericality | :other_than | :other_than | count |
numericality | :only_integer | :not_an_integer | - |
numericality | :in | :in | count |
numericality | :odd | :odd | - |
numericality | :even | :even | - |
comparison | :greater_than | :greater_than | count |
comparison | :greater_than_or_equal_to | :greater_than_or_equal_to | count |
comparison | :equal_to | :equal_to | count |
comparison | :less_than | :less_than | count |
comparison | :less_than_or_equal_to | :less_than_or_equal_to | count |
comparison | :other_than | :other_than | count |
4.6 Traduções para Assuntos de E-mails do Action Mailer
Se você não passar um assunto para o método mail
, o Action Mailer tentará encontrá-lo em suas traduções. A busca realizada usará o padrão <mailer_scope>.<action_name>.subject
para construir a chave.
# user_mailer.rb
class UserMailer < ActionMailer::Base
def welcome(user)
#...
end
end
en:
user_mailer:
welcome:
subject: "Bem-vindo ao Rails Guides!"
Para enviar parâmetros para interpolação, use o método default_i18n_subject
no mailer.
# user_mailer.rb
class UserMailer < ActionMailer::Base
def welcome(user)
mail(to: user.email, subject: default_i18n_subject(user: user.name))
end
end
en:
user_mailer:
welcome:
subject: "%{user}, bem-vindo ao Rails Guides!"
4.7 Visão geral de outros métodos integrados que fornecem suporte para I18n
O Rails usa strings fixas e outras localizações, como strings de formato e outras informações de formato, em alguns helpers. Aqui está uma breve visão geral.
4.7.1 Métodos Helpers do Action View
distance_of_time_in_words
traduz e pluraliza seu resultado e interpola o número de segundos, minutos, horas, e assim por diante. Veja as traduções em datetime.distance_in_words.datetime_select
eselect_month
usam nomes de meses traduzidos para popular a tag select resultante. Veja as traduções em date.month_names.datetime_select
também procura a opção de ordem em date.order (a menos que você passe a opção explicitamente). Todos os helpers de seleção de data traduzem o prompt usando as traduções no escopo datetime.prompts, se aplicável.Os helpers
number_to_currency
,number_with_precision
,number_to_percentage
,number_with_delimiter
enumber_to_human_size
usam as configurações de formato de número localizadas no escopo number.
4.7.2 Métodos do Active Model
model_name.human
ehuman_attribute_name
usam traduções para nomes de modelos e nomes de atributos, se disponíveis no escopo activerecord.models. Eles também suportam traduções para nomes de classes herdadas (por exemplo, para uso com STI), conforme explicado acima em "Escopos de mensagens de erro".ActiveModel::Errors#generate_message
(que é usado por validações do Active Model, mas também pode ser usado manualmente) usamodel_name.human
ehuman_attribute_name
(veja acima). Ele também traduz a mensagem de erro e suporta traduções para nomes de classes herdadas, conforme explicado acima em "Escopos de mensagens de erro".ActiveModel::Error#full_message
eActiveModel::Errors#full_messages
adicionam o nome do atributo à mensagem de erro usando um formato procurado emerrors.format
(padrão:"%{attribute} %{message}"
). Para personalizar o formato padrão, substitua-o nos arquivos de localização do aplicativo. Para personalizar o formato por modelo ou por atributo, consulteconfig.active_model.i18n_customize_full_message
.
4.7.3 Métodos do Active Support
Array#to_sentence
usa as configurações de formato conforme fornecidas no escopo support.array.
5 Como Armazenar Suas Traduções Personalizadas
O backend Simple fornecido com o Active Support permite que você armazene traduções tanto em formato Ruby simples quanto em formato YAML.2
Por exemplo, um Hash Ruby que fornece traduções pode ser assim:
{
pt: {
foo: {
bar: "baz"
}
}
}
O arquivo YAML equivalente ficaria assim:
pt:
foo:
bar: baz
Como você pode ver, em ambos os casos, a chave do nível superior é a localidade. :foo
é uma chave de namespace e :bar
é a chave para a tradução "baz".
Aqui está um exemplo "real" do arquivo de traduções YAML en.yml
do Active Support:
en:
date:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%B %d, %Y"
Portanto, todas as seguintes consultas equivalentes retornarão o formato de data :short
"%b %d"
:
I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date
I18n.t :short, scope: 'date.formats'
I18n.t :short, scope: [:date, :formats]
Geralmente, recomendamos o uso do formato YAML para armazenar traduções. No entanto, existem casos em que você deseja armazenar lambdas Ruby como parte dos dados de localidade, por exemplo, para formatos de data especiais.
6 Personalize sua Configuração de I18n
6.1 Usando Diferentes Backends
Por várias razões, o backend Simple fornecido com o Active Support faz apenas a "coisa mais simples que poderia funcionar" para o Ruby on Rails3 ... o que significa que ele só é garantido para funcionar em inglês e, como efeito colateral, em idiomas muito semelhantes ao inglês. Além disso, o backend simples é capaz apenas de ler traduções, mas não pode armazená-las dinamicamente em nenhum formato.
Isso não significa que você está preso a essas limitações, no entanto. A gem Ruby I18n torna muito fácil substituir a implementação do backend Simple por algo que se encaixe melhor em suas necessidades, passando uma instância de backend para o setter I18n.backend=
.
Por exemplo, você pode substituir o backend Simple pelo backend Chain para encadear vários backends juntos. Isso é útil quando você deseja usar traduções padrão com um backend Simple, mas armazenar traduções personalizadas do aplicativo em um banco de dados ou outros backends. Com o backend Chain, você pode usar o backend Active Record e voltar para o backend Simple (padrão):
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
6.2 Usando manipuladores de exceção diferentes
A API do I18n define as seguintes exceções que serão lançadas pelos backends quando ocorrerem as condições inesperadas correspondentes:
Exceção | Motivo |
---|---|
I18n::MissingTranslationData |
nenhuma tradução foi encontrada para a chave solicitada |
I18n::InvalidLocale |
o locale definido em I18n.locale é inválido (por exemplo, nil ) |
I18n::InvalidPluralizationData |
uma opção de contagem foi passada, mas os dados de tradução não são adequados para pluralização |
I18n::MissingInterpolationArgument |
a tradução espera um argumento de interpolação que não foi passado |
I18n::ReservedInterpolationKey |
a tradução contém um nome de variável de interpolação reservada (ou seja, um dos: scope , default ) |
I18n::UnknownFileType |
o backend não sabe como lidar com um tipo de arquivo que foi adicionado a I18n.load_path |
6.2.1 Personalizando como I18n::MissingTranslationData
é tratado
Se config.i18n.raise_on_missing_translations
for true
, erros de I18n::MissingTranslationData
serão lançados. É uma boa ideia ativá-lo em seu ambiente de teste, para que você possa identificar os locais onde as traduções ausentes são solicitadas.
Se config.i18n.raise_on_missing_translations
for false
(o padrão em todos os ambientes), a mensagem de erro da exceção será impressa. Ela contém a chave/escopo ausente para que você possa corrigir seu código.
Se você quiser personalizar ainda mais esse comportamento, você deve definir config.i18n.raise_on_missing_translations = false
e, em seguida, implementar um I18n.exception_handler
. O manipulador de exceção personalizado pode ser um proc ou uma classe com um método call
:
# config/initializers/i18n.rb
module I18n
class RaiseExceptForSpecificKeyExceptionHandler
def call(exception, locale, key, options)
if key == "special.key"
"tradução ausente!" # retorne isso, não lance
elsif exception.is_a?(MissingTranslation)
raise exception.to_exception
else
raise exception
end
end
end
end
I18n.exception_handler = I18n::RaiseExceptForSpecificKeyExceptionHandler.new
Isso lançaria todas as exceções da mesma maneira que o manipulador padrão faria, exceto no caso de I18n.t("special.key")
.
7 Traduzindo o conteúdo do modelo
A API do I18n descrita neste guia é principalmente destinada a traduzir strings de interface. Se você deseja traduzir o conteúdo do modelo (por exemplo, postagens de blog), você precisará de uma solução diferente para ajudar com isso.
Vários gems podem ajudar com isso:
- Mobility: Fornece suporte para armazenar traduções em vários formatos, incluindo tabelas de tradução, colunas JSON (PostgreSQL), etc.
- Traco: Colunas traduzíveis armazenadas na própria tabela do modelo
8 Conclusão
Neste ponto, você deve ter uma boa visão geral de como o suporte ao I18n no Ruby on Rails funciona e está pronto para começar a traduzir seu projeto.
9 Contribuindo para o Rails I18n
O suporte ao I18n no Ruby on Rails foi introduzido na versão 2.2 e ainda está evoluindo. O projeto segue a boa tradição de desenvolvimento do Ruby on Rails de evoluir soluções em gems e aplicativos reais primeiro e, em seguida, selecionar as melhores e mais úteis funcionalidades para inclusão no núcleo.
Portanto, incentivamos todos a experimentar novas ideias e recursos em gems ou outras bibliotecas e disponibilizá-los para a comunidade. (Não se esqueça de anunciar seu trabalho em nossa lista de discussão!)
Se você perceber que seu próprio locale (idioma) está faltando em nosso repositório de exemplos de traduções para o Ruby on Rails, por favor fork o repositório, adicione seus dados e envie uma solicitação de pull.
10 Recursos
- Grupo do Google: rails-i18n - Lista de discussão do projeto.
- GitHub: rails-i18n - Repositório de código e rastreador de problemas para o projeto rails-i18n. Mais importante, você pode encontrar muitas traduções de exemplo para o Rails que devem funcionar para sua aplicação na maioria dos casos.
- GitHub: i18n - Repositório de código e rastreador de problemas para a gem i18n.
11 Autores
- Sven Fuchs (autor inicial)
- Karel Minařík
12 Notas de rodapé
1 Ou, para citar a Wikipedia: "A internacionalização é o processo de projetar um aplicativo de software para que ele possa ser adaptado a vários idiomas e regiões sem alterações de engenharia. A localização é o processo de adaptar o software para uma região ou idioma específico, adicionando componentes específicos do local e traduzindo o texto."
2 Outros backends podem permitir ou exigir o uso de outros formatos, por exemplo, um backend GetText pode permitir a leitura de arquivos GetText.
3 Uma das razões para isso é que não queremos implicar qualquer carga desnecessária para aplicativos que não precisam de recursos de I18n, então precisamos manter a biblioteca I18n o mais simples possível para o inglês. Outra razão é que é praticamente impossível implementar uma solução única para todos os problemas relacionados ao I18n para todos os idiomas existentes. Portanto, uma solução que nos permita trocar facilmente toda a implementação é apropriada de qualquer maneira. Isso também facilita muito a experimentação com recursos e extensões personalizadas.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.
Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.