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

Subscribe
36 Comments
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!
Awesome!
This looks really useful! Thanks.
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.
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
Lövely!
Great tool Peter!
Does it work with the Google AJAX Language API for suggesting translations automagically?
http://code.google.com/apis/ajaxlanguage/
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
if you also need to translate countries / langages, they can be grabbed here: http://github.com/grosser/i18n_data
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”
This looks very promising! Thanks for the plugin!
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
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…
Why didn’t you tell us about rake lost_in_translation!
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.
WoW - Guys thats just what I dreamed of! Thank you so much!
- René
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.
Sorry — the above does not work as the comment will not allow tags.
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
@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
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
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?
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 ?
Simon,
thanks for reporting the bug related to non-string translations. It’s been fixed and committed to Github now.
Cheers
Peter
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.
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
Sascha,
try adding this line to translate controller:
skip_before_filter :verify_authenticity_token
Does that solve the problem?
Peter
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
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?
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.
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?
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!
I agree with u. I need to imedeatly add it in my RSS
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.
Could this be used with I18N DB to give it a database backend? I deploy on heroku, which is a read only filesystem.
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
[...] Translate (or Github repository) is a Rails plugin (for 2.2 and above) that makes internationalizing your Rails apps ridiculously easy. [...]
[...] 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 [...]
[...] moving on to Varnish, Squid, Akamai. Other innovations: CloudKit JSON web services, i18ln & translate, Metric_fu, [...]