RouteTranslator is a gem to allow you to manage the translations of your app routes with a simple dictionary format.
It started as a fork of the awesome translate_routes plugin by Raúl Murciano.
Right now it works with Rails 5
-
If you have this
routes.rbfile originally:Rails.application.routes.draw do namespace :admin do resources :cars end resources :cars end
The output of
rake routes.rbwould be:admin_cars GET /admin/cars(.:format) admin/cars#index POST /admin/cars(.:format) admin/cars#create new_admin_car GET /admin/cars/new(.:format) admin/cars#new edit_admin_car GET /admin/cars/:id/edit(.:format) admin/cars#edit admin_car GET /admin/cars/:id(.:format) admin/cars#show PUT /admin/cars/:id(.:format) admin/cars#update DELETE /admin/cars/:id(.:format) admin/cars#destroy cars GET /cars(.:format) cars#index POST /cars(.:format) cars#create new_car GET /cars/new(.:format) cars#new edit_car GET /cars/:id/edit(.:format) cars#edit car GET /cars/:id(.:format) cars#show PUT /cars/:id(.:format) cars#update DELETE /cars/:id(.:format) cars#destroy -
Add the gem to your
Gemfile:gem 'route_translator'
And execute
bundle install -
Wrap the groups of routes that you want to translate inside a
localizedblock:Rails.application.routes.draw do namespace :admin do resources :cars end localized do resources :cars get 'pricing', to: 'home#pricing', as: :pricing end end
And add the translations to your locale files, for example:
es: routes: cars: coches new: nuevo pricing: precios fr: routes: cars: voitures new: nouveau pricing: prix
-
Your routes are translated! Here's the output of your
rake routesnow:Prefix Verb URI Pattern Controller#Action admin_cars GET /admin/cars(.:format) admin/cars#index POST /admin/cars(.:format) admin/cars#create new_admin_car GET /admin/cars/new(.:format) admin/cars#new edit_admin_car GET /admin/cars/:id/edit(.:format) admin/cars#edit admin_car GET /admin/cars/:id(.:format) admin/cars#show PATCH /admin/cars/:id(.:format) admin/cars#update PUT /admin/cars/:id(.:format) admin/cars#update DELETE /admin/cars/:id(.:format) admin/cars#destroy cars_fr GET /fr/voitures(.:format) cars#index {:locale=>"fr"} cars_es GET /es/coches(.:format) cars#index {:locale=>"es"} cars_en GET /cars(.:format) cars#index {:locale=>"en"} POST /fr/voitures(.:format) cars#create {:locale=>"fr"} POST /es/coches(.:format) cars#create {:locale=>"es"} POST /cars(.:format) cars#create {:locale=>"en"} new_car_fr GET /fr/voitures/nouveau(.:format) cars#new {:locale=>"fr"} new_car_es GET /es/coches/nuevo(.:format) cars#new {:locale=>"es"} new_car_en GET /cars/new(.:format) cars#new {:locale=>"en"} edit_car_fr GET /fr/voitures/:id/edit(.:format) cars#edit {:locale=>"fr"} edit_car_es GET /es/coches/:id/edit(.:format) cars#edit {:locale=>"es"} edit_car_en GET /cars/:id/edit(.:format) cars#edit {:locale=>"en"} car_fr GET /fr/voitures/:id(.:format) cars#show {:locale=>"fr"} car_es GET /es/coches/:id(.:format) cars#show {:locale=>"es"} car_en GET /cars/:id(.:format) cars#show {:locale=>"en"} PATCH /fr/voitures/:id(.:format) cars#update {:locale=>"fr"} PATCH /es/coches/:id(.:format) cars#update {:locale=>"es"} PATCH /cars/:id(.:format) cars#update {:locale=>"en"} PUT /fr/voitures/:id(.:format) cars#update {:locale=>"fr"} PUT /es/coches/:id(.:format) cars#update {:locale=>"es"} PUT /cars/:id(.:format) cars#update {:locale=>"en"} DELETE /fr/voitures/:id(.:format) cars#destroy {:locale=>"fr"} DELETE /es/coches/:id(.:format) cars#destroy {:locale=>"es"} DELETE /cars/:id(.:format) cars#destroy {:locale=>"en"} pricing_fr GET /fr/prix(.:format) home#pricing {:locale=>"fr"} pricing_es GET /es/precios(.:format) home#pricing {:locale=>"es"} pricing_en GET /pricing(.:format) home#pricing {:locale=>"en"}Note that only the routes inside a
localizedblock are translated.In :development environment, I18n is configured by default to not use fallback language. When a translation is missing, it uses the translation key last segment as fallback (
carsandnewin this example).In :production environment, you should either set
config.i18n.fallbacks = falseor set up translations for your routes in every languages. -
Your I18n.locale will be set up automatically from the url param when it's available.
To disable it add this to your controller:
skip_around_action :set_locale_from_url
You can translate a namespace route by either its name or path option:
-
Wrap the namespaces that you want to translate inside a
localizedblock:Rails.application.routes.draw do localized do namespace :admin do resources :cars, only: :index end namespace :sold_cars, path: :sold do resources :cars, only: :index end end end
And add the translations to your locale files, for example:
es: routes: admin: administrador cars: coches new: nuevo pricing: precios sold: vendidos fr: routes: admin: administrateur cars: voitures new: nouveau pricing: prix sold: vendues
-
Your namespaces are translated! Here's the output of your
rake routesnow:Prefix Verb URI Pattern Controller#Action admin_cars_fr GET /fr/administrateur/voitures(.:format) admin/cars#index {:locale=>"fr"} admin_cars_es GET /es/administrador/coches(.:format) admin/cars#index {:locale=>"es"} admin_cars_en GET /admin/cars(.:format) admin/cars#index {:locale=>"en"} sold_cars_cars_fr GET /fr/vendues/voitures(.:format) sold_cars/cars#index {:locale=>"fr"} sold_cars_cars_es GET /es/vendidos/coches(.:format) sold_cars/cars#index {:locale=>"es"} sold_cars_cars_en GET /sold/cars(.:format) sold_cars/cars#index {:locale=>"en"}
At the moment inflections are not supported, but you can use the following workaround:
localized do
resources :categories, path_names: { new: 'new_category' }
enden:
routes:
category: category
new_category: new
es:
routes:
category: categoria
new_category: nueva Prefix Verb URI Pattern Controller#Action
categories_es GET /es/categorias(.:format) categories#index {:locale=>"es"}
categories_en GET /categories(.:format) categories#index {:locale=>"en"}
POST /es/categorias(.:format) categories#create {:locale=>"es"}
POST /categories(.:format) categories#create {:locale=>"en"}
new_category_es GET /es/categorias/nueva(.:format) categories#new {:locale=>"es"}
new_category_en GET /categories/new(.:format) categories#new {:locale=>"en"}
edit_category_es GET /es/categorias/:id/edit(.:format) categories#edit {:locale=>"es"}
edit_category_en GET /categories/:id/edit(.:format) categories#edit {:locale=>"en"}
category_es GET /es/categorias/:id(.:format) categories#show {:locale=>"es"}
category_en GET /categories/:id(.:format) categories#show {:locale=>"en"}
PATCH /es/categorias/:id(.:format) categories#update {:locale=>"es"}
PATCH /categories/:id(.:format) categories#update {:locale=>"en"}
PUT /es/categorias/:id(.:format) categories#update {:locale=>"es"}
PUT /categories/:id(.:format) categories#update {:locale=>"en"}
DELETE /es/categorias/:id(.:format) categories#destroy {:locale=>"es"}
DELETE /categories/:id(.:format) categories#destroy {:locale=>"en"}
You can configure RouteTranslator via an initializer or using the different environment config files.
RouteTranslator.config do |config|
config.force_locale = true
config.locale_param_key = :my_locale
end- force_locale
Set this options to
trueto force the locale to be added to all generated route paths, even for the default locale. Defaults tofalse. - hide_locale
Set this options to
trueto force the locale to be hidden on generated route paths. Defaults tofalse. - generate_unlocalized_routes
Set this option to
trueto add translated routes without deleting original unlocalized versions. Autosetsforce_locale=true. Defaults tofalse. - generate_unnamed_unlocalized_routes
Set this option to
trueto add the behavior of force_locale, but with a named default route which behaves as if generate_unlocalized_routes wastrue.root_pathwill redirect to/enor/esdepending on the value ofI18n.locale. Defaults tofalse. - locale_param_key
The param key that will be used to set the locale to the newly generated routes.
Defaults to
:locale - host_locales
Optional hash to set
I18n.default_localebased onrequest.host. Useful for apps accepting requests from more than one domain. See below for more details. - disable_fallback
Set this option to
trueto create only the routes for each locale that have translations. For example if we have/examplesand a translation is not provided for ES, a route helper ofexamples_eswill not be created. Defaults tofalse. Useful when one uses this with a locale route constraint, so non-ES routes can 404 on a Spanish website. - available_locales Use this to limit the locales for which URLs should be generated for. Accepts an array of strings or symbols.
- locale_segment_proc
The locale segment of the url will by default be
locale.to_s.downcaseYou can supply your own mechanism via a Proc that takeslocaleas an argument, e.g.config.locale_segment_proc = ->(locale) { locale.to_s.upcase } - verify_host_path_consistency
By default, if you use different hosts to translate your application, all translated paths will work on all hosts. Set this option to
trueto force a matching of the host associated locale with the translated path locale as part of the route definition. Defaults tofalse.
If you have an application serving requests from more than one domain, you might want to set I18n.default_locale dynamically based on which domain the request is coming from.
The host_locales option is a hash mapping hosts to locales, with full wildcard support to allow matching multiple domains/subdomains/tlds.
Host matching is case insensitive.
When a request hits your app from a domain matching one of the wild-card matchers defined in host_locales, the default_locale will be set to the specified locale.
Unless you specified the force_locale configuration option to true, that locale will be hidden from routes (acting like a dynamic hide_locale option).
Here are a few examples of possible mappings:
RouteTranslator.config.host_locales =
{ # Matches:
'*.es' => :es, # TLD: ['domain.es', 'subdomain.domain.es', 'www.long.string.of.subdomains.es'] etc.
'ru.wikipedia.*' => :ru, # Subdomain: ['ru.wikipedia.org', 'ru.wikipedia.net', 'ru.wikipedia.com'] etc.
'*.subdomain.domain.*' => :ru, # Mixture: ['subdomain.domain.org', 'www.subdomain.domain.net'] etc.
'news.bbc.co.uk' => :en, # Exact match: ['news.bbc.co.uk'] only
}In the case of a host matching more than once, the order in which the matchers are defined will be taken into account, like so:
RouteTranslator.config.host_locales = { 'russia.*' => :ru, '*.com' => :en } # 'russia.com' will have locale :ru
RouteTranslator.config.host_locales = { '*.com' => :en, 'russia.*' => :ru } # 'russia.com' will have locale :enIf host_locales option is set, the following options will be forced (even if you set to true):
@config.generate_unlocalized_routes = false
@config.generate_unnamed_unlocalized_routes = false
@config.force_locale = false
@config.hide_locale = falseThis is to avoid odd behaviour brought about by route conflicts and because host_locales forces and hides the host-locale dynamically.
If you have routes that (partially) share names in one locale, but must be translated differently in another locale, for example:
get 'people/favourites', to: 'people/products#favourites'
get 'favourites', to: 'products#favourites'Then it is possible to provide different translations for common parts of those routes by scoping translations by a controller's namespace:
es:
routes:
favourites: favoritos
controllers:
people:
products:
favourites: fansRoutes will be translated as in:
people_products_favourites_es GET /people/products/fans(.:format) people/products#favourites {:locale=>"es"}
products_favourites_es GET /products/favoritos(.:format) products#favourites {:locale=>"es"}
The gem will lookup translations under controllers scope first and then lookup translations under routes scope.
If you need complex routing as /:country/:locale/path/to/some/pages, you can specify the position of your locale parameter in the following way:
scope ':country/:locale' do
localized do
root to: 'content#homepage'
end
endTesting your controllers with routes-translator is easy, just add a locale parameter for your localized routes. Otherwise, an ActionController::UrlGenerationError will raise.
describe 'GET index' do
it 'should respond with success' do
get :index, locale: 'fr'
expect(response).to be_success
end
endPlease read through our contributing guidelines. Included are directions for opening issues, coding standards, and notes on development.
More over, if your pull request contains patches or features, you must include relevant unit tests.