Sooner or later you'll face with javascript internationalization if you are developing a multilingual application. There is a wonderful i18n-js gem in Ruby on Rails for that. In spite of existing manuals, it took some time for me to figure out how it works.
Let's localize select2 messages from the example in the previous article.
First, we should configure internationalization according to Rails manual.
Set options in the config/initializers/locale.rb
config
# config/initializers/locale.rb # dictionaries will be searched recursivly in config/locales config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] config.i18n.default_locale = :en config.i18n.available_locales = [:ru, :en]
We will get the current locale from page url and will set it in ApplicationController
# app/controllers/application_controller.rb before_action :set_locale def set_locale I18n.locale = params[:locale] || I18n.default_locale end
Let's add locale to all generated links
# app/controllers/application_controller.rb def default_url_options(options={}) { locale: I18n.locale } end
And finally, set up locale in routes
# config/routes.rb scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do resources :posts root to: redirect("/%{locale}/posts", status: 302) end root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false
All the magic of redirects above is described in a separate blog post.
Let's create special helper to switch locales in layout
# app/helpers/application_helper.rb module ApplicationHelper def lang_switcher content_tag(:ul, class: 'lang-switcher clearfix') do I18n.available_locales.each do |loc| locale_param = request.path == root_path ? root_path(locale: loc) : params.merge(locale: loc) concat content_tag(:li, (link_to loc, locale_param), class: (I18n.locale == loc ? "active" : "")) end end end end
# app/views/layouts/application.html.erb <%= lang_switcher %>
I usually use rails-i18n gem for some basic localization, but it's not required in this case. We should just add i18n-js gem to our Gemfile and install gems via bundler by running bundle install
Now we should add client scripts to provide javascript internationalization
# app/assets/javascripts/application.js //= require i18n
We can create layout partial where server-side locale settings will be passed to client side javascript
# app/views/layouts/_js_locales_info.html.erb <%= javascript_tag do %> I18n.defaultLocale = "<%= I18n.default_locale %>"; I18n.locale = "<%= I18n.locale %>"; I18n.fallbacks = true; <% end %>
And then we just include this partial in our main layout's head
section right after common js includes.
# app/views/layouts/application.html.erb <%= render 'layouts/js_locales_info' %>
I should note, that in this case we are able to use internationalization only when page is fully loaded or in those scripts, which are included after that partial. In case if you'd like to use internationalization anywhere, you'll have to include i18n
javascript separately and include all other javascript code only after it.
Now we should configure javascript locales. We will create config file for that:
# config/i18n-js.yml translations: - file: "app/assets/javascripts/application/i18n/translations.js" only: '*.js.*'
In this file «- file
» options says where to place compiled dictionaries for javascript (it is done manually, but we will discuss it little later). Note, that compilation is configured to use app/assets/javascripts/application
folder in this example. This is because javascript assets are located there in our case. «only
» option points us what keys to use for compilation. In our case these are all keys which have js
sub-key, e.g.
en: admin: js: title: 'Sample' js: copyright: 'Another sample' post: js: name: 'One more sample'
Now, let's prepare localization files for our case:
# config/locales/js/en.yml en: js: posts: select2: placeholder: 'Please, select tags' no-matches: 'No tags found'
# config/locales/js/ru.yml ru: js: posts: select2: placeholder: 'Пожалуйста, укажите теги' no-matches: 'Тегов не найдено'
And the most interesting moment which was difficult for me to understand: you should manually run bin/rake i18n:js:export
to export all dictionaries to javascript. We will get app/assets/javascripts/application/i18n/translations.js
file as an output where all translations will be stored. And it'll work because it'll be automatically included with assets pipeline. So now we can easily localize our client-side code. Let's extend select2 initialization code to make it use locales
# app/assets/specific/posts/_form.js.coffee $ -> $('#post_tag_list').select2 tags: if gon? then gon.tags else [] tokenSeparators: [","] placeholder: I18n.t('js.posts.select2.placeholder') formatNoMatches: (term) -> I18n.t('js.posts.select2.no-matches') width: '200'
That is pretty much all!
By the way, i18n-js is pretty independant, thus we can use it with other server-side languages.