61

So, we have an existing Rails 2.3.5 app that does not support Internationalization at all. Now, I'm well familiar with Rails I18n stuff, but we have a LOT of output strings inside /javascripts/. I'm not a huge fan of this approach, but unfortunately it is too late to fix it now.

How might we internationalize strings stored in JS files in a Rails app? Rails doesn't even serve the JS files...

I'm thinking I could always have the Rails app serve up the JS files, but that seems pretty gross. Are there plugins to do this?

wpp
  • 7,093
  • 4
  • 33
  • 65
Matt Rogish
  • 24,435
  • 11
  • 76
  • 92

10 Answers10

106

Why not something simple like:

<script type="text/javascript">
  window.I18n = <%= I18n.backend.send(:translations).to_json.html_safe %>
</script>

Then in JS you can do things like:

I18n["en-US"]["alpha"]["bravo"];

I've wrapped mine in an application helper.

def current_translations
  @translations ||= I18n.backend.send(:translations)
  @translations[I18n.locale].with_indifferent_access
end

Then my call in my application.html.erb looks like this:

<script type="text/javascript">
  window.I18n = <%= current_translations.to_json.html_safe %>
</script>

This allows you to avoid having to know the current locale in JavaScript.

I18n["alpha"]["bravo"];

Or

I18n.alpha.bravo;
rmontgomery429
  • 14,660
  • 17
  • 61
  • 66
  • 8
    Works perfect, thanks. I also implemented this `t` method to make it more railsish. https://gist.github.com/6a3f1b3a4cf8de889e34 `t("products.price");` – Linus Oleander Jun 28 '12 at 23:29
  • 7
    I just spent an jour trying to make i18n-js work... your solution is way more simple and worked immediately ! – Emmanuel Dec 17 '12 at 18:45
  • Indeed works perfect. Any suggestions on how to get only part of the file into the javascript? Or separate the javascript translations in a different file? Because now all translations are included when I only need 2 sentences. – rept May 20 '13 at 23:24
  • 14
    You don't need to include the entire file when you "load" the translations. `I18n.backend.send(:translations)` returns a hash so you could scope the translations with something like `I18n.backend.send(:translations)["alpha"]` which would only load the section you need. – rmontgomery429 May 21 '13 at 13:06
  • 1
    I have no idea why i18n-js is so complicated. This works fantastically (even precompiles!), and will be easy to split up and extend when I eventually need to. Thanks! – nornagon Jun 13 '14 at 04:41
  • The only downside I see with this solution is when you have `en.yml`, `en-US.yml`, etc... since if it doesn't find some string in `en-US.yml`, it won't take in count keys defined in `en.yml`. – Oscar Mederos Jun 27 '14 at 20:33
  • @nornagon when you say that it precompiles, I assume you're putting it into a separate .js file and not embedded as shown above? – Tobias J Aug 20 '14 at 14:50
  • Perfect! I've included a 'public' section in my i18n files to avoid exposing all the translations. As simple as changing `@translations[I18n.locale].with_indifferent_access` to `@translations[I18n.locale].with_indifferent_access['public']`. Thanks! – AlexGuti Apr 27 '15 at 10:55
  • I had to add `JSON.parse` around `"#{j current_translations.to_json.html_safe}"` in HAML. – Sergey Alekseev Apr 06 '16 at 10:46
  • 5
    how to use this for translations like this showing_text: "Showing %{entry_name} Choices" – Vipin Verma May 03 '16 at 10:46
  • 1
    Why to load all translations when you just need handful of those in js? Why not just create a new key and load it `window.I18n = #{I18n.t(:js_localizations).to_json }` – Deepak Mahakale Aug 24 '17 at 10:42
20

Balibu is abandoned. Use i18n-js: https://github.com/fnando/i18n-js

9

Ryan's solution above is perfect, except the backend needs to be initialized if it hasn't been already.

I18n.backend.send(:init_translations) unless I18n.backend.initialized?
# now you can safely dump the translations to json
life_like_weeds
  • 131
  • 1
  • 4
6

Why not simply this in your Javascript file:

var a_message = "<%= I18n.t 'my_key' %>"

For this to work, you must add .erb to your Javascript file's extension.

You might also need to add the following line at the top of your Javascript file if you aren't using ruby >= 2.0.

<%# encoding: utf-8 %>

See the last comment of the accepted answer in this thread for more info: Encoding issues in javascript files using rails asset pipeline

Community
  • 1
  • 1
  • 4
    One big reason this will be difficult is asset precompilation. When the asset gets precompiled, the javascript will be put in the public directory with your default directory translations. You'd have to make that asset compile on demand. Not saying it's wrong, but there's some definitely gotchas. – StingeyB Mar 12 '14 at 23:02
6

For rails 3 applications you could do this:

Create a i18n.js.erb file and add it to your application.js. And add this piece of code into the file.

<%
@translator = I18n.backend
@translator.load_translations
@translations ||= @translator.send(:translations)[I18n.locale][:javascript]
%>

window.I18n = <%= @translations.to_json.html_safe %>

I also scope my translations to not have a huge javascript file. My scope is :javascript.

Hope it helps someone!

Vince V.
  • 3,115
  • 3
  • 30
  • 45
  • This solution has two problems: 1. In production locale always will be same because assets compiled 2. You need to clear `tmp/cache` every time you change translations in yml. – Artem P Sep 05 '17 at 21:55
3

Ryan solution is brillant. But to not include the entire file you should use: @translations[I18n.locale].with_indifferent_access["alpha"] instead of I18n.backend.send(:translations)["alpha"]

macler
  • 111
  • 4
3

Another option that could be helpful:

Supposing that you have a model Language (slug) that contains all your available languages. It handles the cases when a there's a missing translation (it's replaced by the default locale version)

assets/javascript/i18n.js.erb

<%
@translator = I18n.backend
@translator.load_translations

translations = {}
Language.all.each do |l|
    translations[l.slug] = @translator.send(:translations)[l.slug.to_sym]
end

@translations = translations

%>
window.I18n = <%= @translations.to_json.html_safe %>

window.I18n.t = function(key){
    if(window.I18n[current_locale]){
        el = eval("I18n['"+current_locale+"']." + key);
    }
    if(window.I18n[default_locale] && typeof(el) == 'undefined'){
        el = eval("I18n['"+default_locale+"']." + key);
    }
    if(typeof(el) == 'undefined'){
        el = key;
    }
    return el;
};

views/layout/application.html.erb

...
<%= javascript_tag "var current_locale = '#{I18n.locale.to_s}';" %>
<%= javascript_tag "var default_locale = '#{I18n.default_locale}';" %>
...

In you javascript code, you can translate like this:

// current_locale:fr , default_locale:en

// existing translation (in french) 
I18n.t('message.hello_world'); // => Bonjour le monde

// non-existing translation (in french) but existing in english 
I18n.t('message.hello_this_world'); // => Hello this world

// non-existing translation (french & english) 
I18n.t('message.hello_this_new_world'); // => message.hello_this_new_world

Hope that it helps!

Simo
  • 464
  • 3
  • 16
2

Babilu is a Rails plugin that does this for you.

sblom
  • 26,911
  • 4
  • 71
  • 95
0

I18n-js is working great for me and i'd recommend it. If you use his rewrite branch then the plugin will include a /assets/i18n/filtered.js file which outputs exactly what @ryan-montgomery answered, without having to do anything yourself manually.

This way, you can use the same translations on the backend with Rails helpers t(:key) and using I18n.t('key') in Javascript on the frontend.

robbie613
  • 675
  • 1
  • 10
  • 8
0

For applications like the one you described "that does not support Internationalization at all" and "is too late to fix it now" I wrote a very quick approach: the jQuery plugin Quick-i18n: https://github.com/katio/Quick-i18n demo (and how to use it): http://johannpaul.net/Quick-i18n/

Johann Echavarria
  • 9,695
  • 4
  • 26
  • 32