Translate: New Rails I18n Plugin with a Nice Web UI

Here at Newsdesk we are in the midst of internationalizing a fairly big Ruby on Rails application. Sven Fuchs’s I18n Textmate bundle has been a great help in extracting thousands of texts away from our source code and into YAML files. As we were getting ready to start the actual translation work we figured that a nice web interface would be helpful. Since we couldn’t find a web UI out there we decided to roll our own and thus the translate plugin was born.

The plugin requires that you run Rails 2.2 or later. It lets you mount a web UI at /translate where you can view and translate all your I18n texts. You can select which locales you are translating from and to, search for keys and texts, and choose to view only untranslated texts. The plugin reads and writes all translations to YAML files under config/locales. For more details, please refer to the README file.

Screenshot of the User Interface

Newsdesk translate I18n screenshot

Newsdesk translate I18n screenshot

36 Comments

  1. Posted January 21, 2009 at 11:29 pm | Permalink

    Now that looks like a really great addition to our toolset.

    I’ve linked your plugin on the Rails I18n wiki (http://rails-i18n.org/wiki). Care to announce this on the rails-i18n mailing list as well?

    Thanks a lot for this!

  2. Posted January 22, 2009 at 1:39 pm | Permalink

    Awesome!

  3. Posted January 22, 2009 at 2:12 pm | Permalink

    This looks really useful! Thanks.

  4. Peco
    Posted January 23, 2009 at 2:48 pm | Permalink

    How do you enable support fro utf-8 in yaml? Installing ya2yaml doesn’t seem to finish the job because non-latin characters are written as binary (?!). I am working on Windows, haven’t tried on my linux machine yet.

    Other from the problem, really useful plugin.

  5. Posted January 23, 2009 at 3:01 pm | Permalink

    Peco,
    I don’t know what the issue with ya2yaml is. We use it successfully on Linux and Mac OSX to write swedish chars in UTF8. I don’t know how ya2yaml works so I can’t really offer much help there. Sorry.

    Peter

  6. Posted January 27, 2009 at 11:02 am | Permalink

    Lövely! :) Great tool Peter!

  7. Posted January 28, 2009 at 1:51 pm | Permalink

    Does it work with the Google AJAX Language API for suggesting translations automagically?

    http://code.google.com/apis/ajaxlanguage/

  8. Peco
    Posted January 28, 2009 at 4:41 pm | Permalink

    Hi Peter,

    the problem was the usual suspect called Windows, I tried it on my Ubuntu and it works fine. It seems that ya2yaml doesn’t recognize utf-8 characters on windows (?!) for some reason.

    Thanks

  9. Posted January 29, 2009 at 7:58 am | Permalink

    if you also need to translate countries / langages, they can be grabbed here: http://github.com/grosser/i18n_data

  10. Posted January 29, 2009 at 9:42 am | Permalink

    Looks nice, i have one problem with it though, i have an app with several keys as strings with spaces. The get all messed up once i save:

    “Location / gérance”: “Location / Gérance”

    becomes

    ? “Location / gérance”
    : “Location / Gérance”

  11. Posted January 30, 2009 at 12:56 am | Permalink

    This looks very promising! Thanks for the plugin!

  12. Posted January 30, 2009 at 5:04 am | Permalink

    In the Spree project (a Rails commerce platform) we have a cool little rake task for both stubbing out new translation files. It also keeps the non en-US ones in sync with en-US by adding placeholder entries in the other locales and also resorting everything alphabetically.

    http://github.com/schof/spree/blob/b80dbfaabcb4645469b6357a40070caf5b5f1485/lib/tasks/translation.rake

  13. Posted February 4, 2009 at 10:31 pm | Permalink

    Peter, thx for nice plugin !

    Is there a chance for small addition?
    I think it would be very useful to group keys by key.pattern. For example i use txt.meetings.incoming as a key and it would be nice if all txt.meetings keys would be grouped under link like those “all | untranslated | translated”. In this case link would be f.e. “txt.meetings”
    Seem like not too difficult to implement through some magical ruby hackery…

  14. Posted February 6, 2009 at 9:26 pm | Permalink

    Why didn’t you tell us about rake lost_in_translation!

  15. lucapette
    Posted February 8, 2009 at 5:24 pm | Permalink

    Wow. Awesome and really promising. At this very moment i find it really useful in a new project of mine. Good job and thank you.

  16. Posted February 20, 2009 at 2:13 pm | Permalink

    WoW - Guys thats just what I dreamed of! Thank you so much!

    - René

  17. Tim Nilson
    Posted February 20, 2009 at 11:21 pm | Permalink

    Very nice work! Hope you don’t mind one tweak: If you would like to add a simple Ajax google translate link below each form Element, you can add this:

    In the plugin’s layout file, insert this on line #8:
    —-

    google.load(”language”, “1″);

    function getGoogleTranslation(id, text, from_language, to_language) {
    google.language.translate(text, from_language, to_language, function(result) {
    if (!result.error) { $(id).writeAttribute({ value : result.translation }); }
    });

    }

    —-
    In the index.html view file: Add this on line #90
    —-
    ‘padding: 0; margin: 0;’ %>

    —-

    This passes the english text to the Google translation’s engine and fills the return into the form field so you can clean it up further before saving. Bug: if the string has a single quote, this will not work yet.

  18. Tim Nilson
    Posted February 20, 2009 at 11:23 pm | Permalink

    Sorry — the above does not work as the comment will not allow tags.

  19. Tim Nilson
    Posted February 20, 2009 at 11:33 pm | Permalink

    To see instructions for the auto google ajax link, I posted a page on the GitHub wiki here: http://wiki.github.com/newsdesk/translate/add-automatic-google-translation

  20. marten
    Posted February 23, 2009 at 7:52 am | Permalink

    @tim Thanks for your contribution, it certainly looks useful. The easiest way to pull it in to translate i probably for you to fork the project and send us a pull request.
    Regards, Marten

  21. Posted March 1, 2009 at 5:31 pm | Permalink

    I’ve put up a locale YMAL auto translator. It understand YAML formate and can automatically translate your YMAL into othere languages. Saves a lot of time for i18n projects.
    http://github.com/yi/rails-localisation-yaml-auto-translator/tree/master

  22. Posted March 24, 2009 at 5:28 pm | Permalink

    Awesome plugin! Exactly what we need right now.

    One question: The plugin writes changes directly to the yaml files. Do you commit these changes manually or what is your best practice?

  23. Simon IONG
    Posted March 25, 2009 at 9:03 am | Permalink

    After install translate, I got a problem with Rails 2.3.2

    Showing vendor/plugins/translate/views/translate/index.rhtml where line # raised:
    private method `gsub' called for ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]:Array
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/javascript_helper.rb:143:in `escape_javascript’
    /home/simoniong/app/test_goldberg/vendor/plugins/translate/views/translate/index.rhtml:95:in `_run_rhtml_vendor47plugins47translate47views47translate47index46rhtml’
    /home/simoniong/app/test_goldberg/vendor/plugins/translate/views/translate/index.rhtml:73:in `each’
    /home/simoniong/app/test_goldberg/vendor/plugins/translate/views/translate/index.rhtml:73:in `_run_rhtml_vendor47plugins47translate47views47translate47index46rhtml’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/capture_helper.rb:36:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/capture_helper.rb:36:in `capture’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/capture_helper.rb:129:in `with_output_buffer’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/capture_helper.rb:36:in `capture’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/form_tag_helper.rb:460:in `form_tag_in_block’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/helpers/form_tag_helper.rb:39:in `form_tag’
    /home/simoniong/app/test_goldberg/vendor/plugins/translate/views/translate/index.rhtml:59:in `_run_rhtml_vendor47plugins47translate47views47translate47index46rhtml’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/renderable.rb:34:in `send’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/renderable.rb:34:in `render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/base.rb:301:in `with_template’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/renderable.rb:30:in `render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/template.rb:194:in `render_template’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/base.rb:260:in `render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/base.rb:343:in `_render_with_layout’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_view/base.rb:257:in `render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:1241:in `render_for_file’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:937:in `render_without_benchmark’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/benchmarking.rb:51:in `render’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:17:in `ms’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:10:in `realtime’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:17:in `ms’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/benchmarking.rb:51:in `render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:1317:in `default_render’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:1323:in `perform_action_without_filters’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/filters.rb:617:in `call_filters’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/filters.rb:610:in `perform_action_without_benchmark’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:17:in `ms’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:10:in `realtime’
    /home/simoniong/.gem/ruby/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/benchmark.rb:17:in `ms’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/rescue.rb:160:in `perform_action_without_flash’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/flash.rb:141:in `perform_action’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:523:in `send’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:523:in `process_without_filters’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/filters.rb:606:in `process’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:391:in `process’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/base.rb:386:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/routing/route_set.rb:433:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/dispatcher.rb:88:in `dispatch’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/dispatcher.rb:111:in `_call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/dispatcher.rb:82:in `initialize’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/query_cache.rb:29:in `call’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/query_cache.rb:29:in `call’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/query_cache.rb:34:in `cache’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/query_cache.rb:9:in `cache’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/query_cache.rb:28:in `call’
    /home/simoniong/.gem/ruby/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:361:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/head.rb:9:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb:24:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/params_parser.rb:15:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/rewindable_input.rb:25:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/session/cookie_store.rb:93:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/reloader.rb:9:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/failsafe.rb:11:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/lock.rb:11:in `call’
    /usr/lib/ruby/1.8/thread.rb:135:in `synchronize’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/lock.rb:11:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/dispatcher.rb:106:in `call’
    /usr/lib/ruby/gems/1.8/gems/rails-2.3.2/lib/rails/rack/static.rb:31:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb:46:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb:40:in `each’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb:40:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/content_length.rb:13:in `call’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb:46:in `service’
    /usr/lib/ruby/1.8/webrick/httpserver.rb:104:in `service’
    /usr/lib/ruby/1.8/webrick/httpserver.rb:65:in `run’
    /usr/lib/ruby/1.8/webrick/server.rb:173:in `start_thread’
    /usr/lib/ruby/1.8/webrick/server.rb:162:in `start’
    /usr/lib/ruby/1.8/webrick/server.rb:162:in `start_thread’
    /usr/lib/ruby/1.8/webrick/server.rb:95:in `start’
    /usr/lib/ruby/1.8/webrick/server.rb:92:in `each’
    /usr/lib/ruby/1.8/webrick/server.rb:92:in `start’
    /usr/lib/ruby/1.8/webrick/server.rb:23:in `start’
    /usr/lib/ruby/1.8/webrick/server.rb:82:in `start’
    /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb:13:in `run’
    /usr/lib/ruby/gems/1.8/gems/rails-2.3.2/lib/commands/server.rb:111
    /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require’
    /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `require’
    script/server:3

    Any Hint ?

  24. peter
    Posted March 25, 2009 at 10:03 am | Permalink

    Simon,
    thanks for reporting the bug related to non-string translations. It’s been fixed and committed to Github now.

    Cheers

    Peter

  25. Richard
    Posted March 25, 2009 at 10:56 am | Permalink

    Mirko,
    Yes we manually commit the translated files. If you translated your texts with Translate in the first place, we write a translation log that allows you to track changes that you pull from your colleagues. (”Changed” link in Translate). Might come in handy for you.

  26. Sascha
    Posted April 7, 2009 at 1:03 pm | Permalink

    Hi,

    thanks for this nice plugin, but I get an error if I visit the /translate section:

    Showing vendor/plugins/translate/views/translate/index.rhtml where line #59 raised:

    No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store).

    My protect_from_forgery setting in the environment.rb is (in my opinion) correct (I use the active_record_store) and all forms works fine on my site. Has anyone else this problem?

    Regards, Sascha

  27. Posted April 16, 2009 at 10:42 am | Permalink

    Sascha,
    try adding this line to translate controller:

    skip_before_filter :verify_authenticity_token

    Does that solve the problem?

    Peter

  28. Posted April 28, 2009 at 9:33 am | Permalink

    I was also getting binary data in the translation file but solve this with require ya2yaml gem in development environment.

    require ‘ya2yaml’

    Translate plug-in calls the ya2yaml method if it exists, but previously doesn’t try to require ya2yaml gem in its init and then falls back to to_yaml method.

    keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml

  29. wojtek
    Posted May 18, 2009 at 11:41 am | Permalink

    There is a problem with UTF-8 encoding in ruby 1.9. ya2yaml gem is incompatible. Do you know any workaround to write yaml file in human format with that ruby version?

  30. Posted August 5, 2009 at 12:14 am | Permalink

    This is a very neat plugin, but we’ve noticed that when new translations are added, it seems to do some reordering of the .yml files. This is causing some painful git merges when multiple people update the translation files in separate branches.

    Is there an intentional reordering of the keys being done, or is this a side effect of something else? If maintaining the key ordering would be desirable, I’d be willing to take a crack at a patch.

  31. Jonathan Chambers
    Posted August 28, 2009 at 2:14 am | Permalink

    Hi, I’m wondering how this would work for multiple app-servers on different machines. For instance, if I have three app-servers, does each server just write to it’s own local copy of config/locales/fr.yml? This would mean that the same text would be translated 3 times right? Unless I synchronised the yaml files periodically. Or can I configure the i18n loadpath to point at a shared fr.yml on a static asset server?

  32. Marco Canderle
    Posted September 24, 2009 at 3:11 am | Permalink

    This plugin is great!! Thanks for building it!

    I have a question. Is there any extension for your plugin that would allow us to combine it with the model-driven translation provided by the Globalize2 plugin (http://github.com/joshmh/globalize2), so that we could use the Translate plugin´s UI, to store information on the database tables created by Globalize2?

    Thanks again!

  33. Posted September 27, 2009 at 11:56 am | Permalink

    I agree with u. I need to imedeatly add it in my RSS

  34. Juergen
    Posted October 4, 2009 at 6:38 pm | Permalink

    Hi, I would love to use the rake task for automated google translation. I have the translate plugin installed and the web-interface including single google translations do work. For the rake task I did install the latest httparty gem (I have version 0.4.5 ob my system).

    When I run the rake task to translate from en to fr:

    rake translate:google FROM=en TO=fr

    I do get the following error:

    Constant HTTParty from htt_party.rb not found

    The include “HTTParty” is described in the httparty readmes so I don’t understand the error here. Do I miss something obvious?

    Any help would be appreciated.

  35. Chris
    Posted November 3, 2009 at 5:09 pm | Permalink

    Could this be used with I18N DB to give it a database backend? I deploy on heroku, which is a read only filesystem.

  36. Ivan
    Posted January 6, 2010 at 1:01 am | Permalink

    Hi, thanks for this great plugin…and sorry for my english…

    I have a really weird problem, suddenly the plugin stop working right. Now when i press “save translations” button, and the plugin save the translations in the yml but skip the quotes (”).
    So my file looks like this.

    en:

    site:
    hello: Hello World

    instead of:

    en:

    site:
    hello: “Hello World”

    Any hint??

    Regards..

3 Trackbacks

  1. By Make I18n Simple with the Translate Plugin on January 29, 2009 at 6:49 am

    [...] Translate (or Github repository) is a Rails plugin (for 2.2 and above) that makes internationalizing your Rails apps ridiculously easy. [...]

  2. By Using I18n to stay DRY - JeeViDee.nl on February 14, 2009 at 1:28 pm

    [...] One of the new tools in Rails to stay DRY is the new I18n functionality. With this new tool you can keep away of customizing messages, text in your views etc. Especially with the this new plugin [...]

  3. [...] moving on to Varnish, Squid, Akamai. Other innovations: CloudKit JSON web services, i18ln & translate, Metric_fu, [...]

Post a Comment

Your email is never shared. Required fields are marked *

*
*