Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/enriclluelles/route_translator
Translate your rails app route to various languages without the hassle
https://github.com/enriclluelles/route_translator
Last synced: 3 months ago
JSON representation
Translate your rails app route to various languages without the hassle
- Host: GitHub
- URL: https://github.com/enriclluelles/route_translator
- Owner: enriclluelles
- License: mit
- Created: 2012-07-10T19:25:11.000Z (over 12 years ago)
- Default Branch: master
- Last Pushed: 2024-07-27T14:36:03.000Z (4 months ago)
- Last Synced: 2024-07-27T15:55:15.715Z (4 months ago)
- Language: Ruby
- Size: 2.26 MB
- Stars: 855
- Watchers: 17
- Forks: 144
- Open Issues: 16
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# RouteTranslator
[![Gem Version](https://badge.fury.io/rb/route_translator.svg)](https://badge.fury.io/rb/route_translator)
[![Build Status](https://github.com/enriclluelles/route_translator/actions/workflows/ruby.yml/badge.svg)](https://github.com/enriclluelles/route_translator/actions)
[![Maintainability](https://api.codeclimate.com/v1/badges/1c369ce6147724b353fc/maintainability)](https://codeclimate.com/github/enriclluelles/route_translator/maintainability)
[![Coverage Status](https://coveralls.io/repos/github/enriclluelles/route_translator/badge.svg?branch=master)](https://coveralls.io/github/enriclluelles/route_translator?branch=master)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](https://github.com/raul/translate_routes) plugin by [Raúl Murciano](https://github.com/raul).
Right now it works with Rails 6.1 and 7.x
## Quick Start
1. If you have this `routes.rb` file originally:
```ruby
Rails.application.routes.draw do
namespace :admin do
resources :cars
endresources :cars
end
```The output of `bundle exec rails routes` would 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
```2. Add the gem to your `Gemfile`:
```ruby
gem 'route_translator'
```And execute `bundle install`
3. Generate the default initializer:
```sh
bundle exec rails g route_translator:install
```4. Wrap the groups of routes that you want to translate inside a `localized` block:
```ruby
Rails.application.routes.draw do
namespace :admin do
resources :cars
endlocalized do
resources :carsget 'pricing', to: 'home#pricing', as: :pricing
end
end
```And add the translations to your locale files, for example:
```yml
es:
routes:
cars: coches
new: nuevo
pricing: precios
fr:
routes:
cars: voitures
new: nouveau
pricing: prix
```5. Your routes are translated! Here's the output of your `bundle exec rails routes` now:
```
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 `localized` block 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 (`cars` and `new` in this example).In :production environment, you should either set `config.i18n.fallbacks = false` or set up translations for your routes in every languages.
6. If you want to set `I18n.locale` from the url parameter locale, add
the following line in your `ApplicationController` or in the controllers
that have translated content:```ruby
around_action :set_locale_from_url
```Note: you might be tempted to use `before_action` instead of `around_action`: just don't. That could lead to [thread-related issues](https://github.com/enriclluelles/route_translator/issues/44).
### Changing the Language
To change the language and reload the appropriate route while staying on the same page, use the following code snippet:
```ruby
link_to url_for(locale: 'es'), hreflang: 'es', rel: 'alternate'
```Although locales are stored by Rails as a symbol (`:es`), when linking to a page in a different locale you need to use a string (`'es'`). Otherwise, instead of a namespaced route (`/es/my-route`) you will get a parameterized route (`/my-route?locale=es`).
If the page contains a localized slug, the above snippet does not work and a custom implementation is needed.
More information at [Generating translated URLs](https://github.com/enriclluelles/route_translator/wiki/Generating-translated-URLs)
### Namespaces
You can translate a namespace route by either its `name` or `path` option:
1. Wrap the namespaces that you want to translate inside a `localized` block:
```ruby
Rails.application.routes.draw do
localized do
namespace :admin do
resources :cars, only: :index
endnamespace :sold_cars, path: :sold do
resources :cars, only: :index
end
end
end
```And add the translations to your locale files, for example:
```yml
es:
routes:
admin: administrador
cars: coches
new: nuevo
pricing: precios
sold: vendidos
fr:
routes:
admin: administrateur
cars: voitures
new: nouveau
pricing: prix
sold: vendues
```4. Your namespaces are translated! Here's the output of your `bundle exec rails routes` now:
```
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"}
```### Inflections
At the moment inflections are not supported, but you can use the following workaround:
```ruby
localized do
resources :categories, path_names: { new: 'new_category' }
end
``````yml
en:
routes:
category: category
new_category: newes:
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"}
```## Configuration
You can configure RouteTranslator via an initializer or using the different environment config files.
```ruby
RouteTranslator.config do |config|
config.force_locale = true
config.locale_param_key = :my_locale
end
```### Available Configurations
| Option | Description | Default |
| ------ | ----------- |-------- |
| `available_locales` | Limit the locales for which URLs should be generated for. Accepts an array of strings or symbols. When empty, translations will be generated for all `I18n.available_locales` | `[]` |
| `disable_fallback` | Create routes only for locales that have translations. For example, if we have `/examples` and a translation is not provided for `es`, the route helper of `examples_es` will not be created. Useful when one uses this with a locale route constraint, so non-`es` routes can return a `404` on a Spanish website | `false` |
| `force_locale` | Force the locale to be added to all generated route paths, even for the default locale | `false` |
| `generate_unlocalized_routes` | Add translated routes without deleting original unlocalized versions. **Note:** Autosets `force_locale` to `true` | `false` |
| `generate_unnamed_unlocalized_routes` | Add the behavior of `force_locale`, but with a named default route which behaves as if `generate_unlocalized_routes` was `true`. `root_path` will redirect to `/en` or `/es`, depending on the value of `I18n.locale` | `false` |
| `hide_locale` | Force the locale to be hidden on generated route paths | `false` |
| `host_locales` | Set `I18n.locale` based on `request.host`. Useful for apps accepting requests from more than one domain. See below for more details | `{}` |
| `locale_param_key` | The param key used to set the locale to the newly generated routes | `:locale` |
| `locale_segment_proc` | The locale segment of the url will by default be `locale.to_s.downcase`. You can supply your own mechanism via a Proc that takes `locale` as an argument, e.g. `->(locale) { locale.to_s.upcase }` | `false` |### Host-based Locale
If you have an application serving requests from more than one domain, you might want to set `I18n.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 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:
```ruby
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:
```ruby
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 :en
```If `host_locales` option is set, the following options will be forced:
```ruby
@config.force_locale = false
@config.generate_unlocalized_routes = false
@config.generate_unnamed_unlocalized_routes = false
@config.hide_locale = true
```This is to avoid odd behaviour brought about by route conflicts and because `host_locales` forces and hides the host-locale dynamically.
NOTE: locale from parameters has priority over the one from hosts.
### Translations for similar routes with different namespaces
If you have routes that (partially) share names in one locale, but must be translated differently in another locale, for example:
```ruby
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:```yml
es:
routes:
favourites: favoritos
controllers:
people/products:
favourites: fans
```Routes 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"}
```It is also possible to translated resources scoped into a namespace. Example:
```ruby
namespace :people do
resources :products, only: :index
end
``````yml
es:
routes:
controllers:
people/products:
products: productos_favoritos
```Routes will be translated as in:
```
people_products_es GET /people/productos_favoritos(.:format) people/products#index {:locale=>"es"}
```The gem will lookup translations under `controllers` scope first and then lookup translations under `routes` scope.
### Change locale parameter position in the path
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:
```rb
scope ':country/:locale' do
localized do
root to: 'content#homepage'
end
end
```## Testing
Testing your controllers with routes-translator is easy, just add a locale parameter as `String` for your localized routes. Otherwise, an `ActionController::UrlGenerationError` will raise.```ruby
describe 'GET index' do
it 'should respond with success' do
# Remember to pass the locale param as String
get :index, locale: 'fr'expect(response).to be_success
end
end
```## Contributing
Please read through our [contributing guidelines](CONTRIBUTING.md). 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.