Translate a site into other languages through internationalization (I18n). Here I show how to store a locale in the URL and manage translated text through YAML files.
Any idea, why localizing nested attributes is unbelievable painful? Consider this setup, where you need a lot of repetition to create correctly localized error messages.
I've just seen the old I18n episode and discovered on of the globalize3 authors (svenfuchs) is even an rails-i18n author. It would be cool an episode about globalize3 (I wonder if some way to build translation interface on it exists...)
Using the t() method, it can sometimes be confusing which key in the i18n file Rails is trying to look up.. there doesn't seem to be a consistent naming convention in Rails.
e.g it uses different locations for form labels, buttons, helpers, titles, strings in controllers or models, etc..
The following page provides a table with examples, as well as a tip on how to monkey-patch the i18n Gem in development mode, so Rails will tell you the i18n-keys it tries to look up.
The screencasts are so perfectly made, I am trying to do the same at my http://happycasts.net (One Linux Tip per Week), to make sth useful and beautiful at the same time.
match '*path', to: redirect("/#{I18n.default_locale}/%{path}")
to be something along the lines of
ruby
match '*path', to: redirect({|params, request| "/#{I18n.default_locale}#{request.fullpath}"})
If you want to maintain the request format sent in /posts.json, also it should be noted that a 302 redirect will not resubmit post data, possibly a 307. This method of redirection you need to pay close attention that any urls placed in ajax calls have the locale otherwise there may be some unforeseen bugs, especially with POST requests
No need for YML files, full support for language context rules and language cases. Uses crowd-sourced or professional translator provided translations.
Here is what some of the translations would look like. t method is replaced with tr.
<%= tr("Hello World!") %>
<%= tr("Name:", "User name label") %>
uses description to provide context for the label
<%= tr("You have {count|| message} in your mailbox.", nil, {count: 5}) %>
will use numeric rules and inflectors to provide the right form for message/messages
<%= tr("{actor} sent {target::dat} a message.", nil, {actor:user1, target:user2}) %>
will use gender of {actor} user to provide the right form for word "sent" and apply Dative language case to the name of target user token.
similar to the above example, but also adds a decoration token around the number of messages. Here is an example in English:
Michael sent Anna 5 messages.
In Russian this would need 9 translations based on the gender of actor (male, female, unknown) as well as value of count: 3 cases as well. It can translate names as well, if you so desire.
Михаил послал Анне 5 сообщений.
The technology is very powerful and has been used by Geni and Yammer and a few other companies and is now open sourced.
I managed to get rid of the redirect loop mentioned, and make the routing a bit more robust and friendly-forwarding-ish, by doing the following (I used this example when I attempted to internationalize the Rails Tutorial:
config/routes.rb
ruby
SampleApp::Application.routes.draw do
scope ":locale", locale:/#{I18n.available_locales.join("|")}/do# ...
match '/about', to:'static_pages#about'# ...# handles /valid-locale
root to:'static_pages#home', as:"locale_root"# handles /valid-locale/fake-path
match '*path', to: redirect { |params, request| "/#{params[:locale]}" }
end# handles /
root to: redirect("/#{I18n.default_locale}")
# handles /bad-locale|anything/valid-path
match '/*locale/*path', to: redirect("/#{I18n.default_locale}/%{path}")
# handles /anything|valid-path-but-no-locale
match '/*path', to: redirect("/#{I18n.default_locale}/%{path}")
end
Since there are two root_paths, I renamed the one inside the :locale scope so there would be no conflicts in the app and tests. I tested the routes using RSpec as follows:
spec/routing/routing_spec.rb
ruby
require 'spec_helper'
describe "Routes"do
describe "locale scoped paths"doI18n.available_locales.each do |locale|
describe "routing"do
it "should route /:locale to the root path"do
get("/#{locale.to_s}").
should route_to("static_pages#home", locale: locale.to_s)
endend
describe "redirecting", type::requestdo
subject { response }
context "fake paths"do
let(:fake_path) { "fake_path" }
before { get "/#{locale.to_s}/#{fake_path}" }
it { should redirect_to(locale_root_path(locale)) }
endendendend
describe "non-locale scoped paths"do
describe "redirecting", type::requestdo
subject { response }
context "no path given"do
before { get "/" }
it { should redirect_to(locale_root_path(I18n.default_locale)) }
end
context "a valid action"do
let(:action) { "about" }
let!(:default_locale_action_path) { about_path(I18n.default_locale) }
context "with a valid but unsupported locale"do
let(:unsupported_locale) { "fr" }
before { get "/#{unsupported_locale}/#{action}" }
it { should redirect_to(default_locale_action_path) }
end
context "with invalid information for the locale"do
let(:invalid_locale) { "invalid" }
before { get "/#{invalid_locale}/#{action}" }
it { should redirect_to(default_locale_action_path) }
end
context "with no locale information"do
before { get "/#{action}" }
it { should redirect_to(default_locale_action_path) }
endend
context "invalid information"do
let(:invalid_info) { "invalid" }
before { get "/#{invalid_info}" }
it { should redirect_to("/#{I18n.default_locale}/#{invalid_info}") }
# This will then get caught by the "redirecting fake paths" condition# and hence be redirected to locale_root_path with I18n.default_localeendendendend
I know that redirects are technically requests and their tests should really be under the spec/requests/ directory, but I figured they'd be more appropriate under routing since they're really just tests for exceptions and catch-alls. Anyway, hope this helps or is of interest to someone.
I want to set the locale by the clients browserlocale request.env['HTTP_ACCEPT_LANGUAGE'] and by the url.
If the url has no params[:locale] it should use the browserlocale set the url.
Here is my code:
application_controller.rb
classApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
private
# set the languagedefset_localeif params[:locale].blank?
I18n.locale = extract_locale_from_accept_language_header
elseI18n.locale = params[:locale]
endend# pass in language as a default url parameterdefdefault_url_options(options = {})
{locale:I18n.locale}
end# extract the language from the clients browserdefextract_locale_from_accept_language_header
browser_locale = request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
ifI18n.available_locales.include? browser_locale
browser_locale
elseI18n.default_locale
endendend
routes.rb
Myapp::Application.routes.draw do# set language path
scope ":locale", locale:/#{I18n.available_locales.join("|")}/do
root :to => "mycontrollers#new"# set RESTful routes for the locations Controller
resources :mycontrollersend
match '*path', to: redirect("/#{I18n.locale}/%{path}"), constraints: lambda { |req| !req.path.starts_with? "/#{I18n.default_locale}/" }
match '', to: redirect("/#{I18n.locale}")
end
The Problem is that the routesfile gets executed first so the controller-actions won't grab anymore.
I have followed all instructions and even copy/paste-d to make sure I made no mistakes. I'm at 6:16, right after adding default_url_options in the application controller and keep getting the same error when restarting the server.
Nevermind, simple mistake. Just had to change get "dashboard/index" to get "dashboard/index" => 'dashboard#index'. I would like to delete my comment but I can't access a delete button.
Hey Leonel, thanks for not deleting your comment - it helped me after a couple of hours scratching my head. Seems the 'get' calls need to explicitly point to the controller#action when doing things this way. Thanks dude!
Hi, anybody knows why when i type long urls it redirected to an escaped url?
http://localhost:3000/users/login becomes http://localhost:3000/id/users%2Flogin
no worries, i was able to fix my problem by changing last two line of code on the route
ruby
match '*path', to: redirect {|params| "/#{I18n.default_locale}/#{params[:path]}"}, constraints: lambda { |req| !req.path.starts_with? "/#{I18n.default_locale}/" }
match '', to: redirect("/#{I18n.default_locale}")
Great screencast on basic I18n use in ruby!
Another unmentioned tool for managing your YAML files is Locale, check it out.
(Disclaimer : I'm a Locale developer)
Any idea, why localizing nested attributes is unbelievable painful? Consider this setup, where you need a lot of repetition to create correctly localized error messages.
Am I doing it wrong?
Maybe we can have an episode about globalize3 next?
+1 (x10^99999)
I've just seen the old I18n episode and discovered on of the globalize3 authors (svenfuchs) is even an rails-i18n author. It would be cool an episode about globalize3 (I wonder if some way to build translation interface on it exists...)
Thank you so much for this. I did not know we can use Model attributes in a more simpler than i used to do it all the time.
excellent revised episode!
Using the t() method, it can sometimes be confusing which key in the i18n file Rails is trying to look up.. there doesn't seem to be a consistent naming convention in Rails.
e.g it uses different locations for form labels, buttons, helpers, titles, strings in controllers or models, etc..
The following page provides a table with examples, as well as a tip on how to monkey-patch the i18n Gem in development mode, so Rails will tell you the i18n-keys it tries to look up.
http://www.unixgods.org/~tilo/Rails/where_is_Rails_trying_to_lookup_L10N_strings.html
I hope you'll find it useful.
The screencasts are so perfectly made, I am trying to do the same at my http://happycasts.net (One Linux Tip per Week), to make sth useful and beautiful at the same time.
Railscasts Rocks!!!
Managing i18n and l10n can be very painful, especially in large projects with multiple persons working on translations.
That is why we built a product to make things a lot easier: phrase
If you have a rails/ruby project that uses the i18n gem, this could be the right thing for you to use.
And since we are on Railscasts, we have a screencast, too ;-)
Nice one, for dealing with locales in the URLs Routing Filter Unless API might be useful.
you may want to update
to be something along the lines of
If you want to maintain the request format sent in /posts.json, also it should be noted that a 302 redirect will not resubmit post data, possibly a 307. This method of redirection you need to pay close attention that any urls placed in ajax calls have the locale otherwise there may be some unforeseen bugs, especially with POST requests
Has anyone tried tr8n?
http://github.com/berk/tr8n
http://wiki.tr8n.org/slides
No need for YML files, full support for language context rules and language cases. Uses crowd-sourced or professional translator provided translations.
Here is what some of the translations would look like. t method is replaced with tr.
<%= tr("Hello World!") %>
<%= tr("Name:", "User name label") %>
<%= tr("You have {count|| message} in your mailbox.", nil, {count: 5}) %>
<%= tr("{actor} sent {target::dat} a message.", nil, {actor:user1, target:user2}) %>
<%= tr("{actor} sent {target::dat} [strong: {count|| message}].", nil, {actor:user1, target:user2, count:5}) %>
Michael sent Anna 5 messages.
In Russian this would need 9 translations based on the gender of actor (male, female, unknown) as well as value of count: 3 cases as well. It can translate names as well, if you so desire.
Михаил послал Анне 5 сообщений.
The technology is very powerful and has been used by Geni and Yammer and a few other companies and is now open sourced.
(Disclaimer: I am a tr8n developer)
For some strange reason. It sends me to an redirect loop
http://0.0.0.0:3000/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/en/assets
Am using twitter bootstrap
Not sure what could be going wrong
Sorry about this issue. I will add a note and update the code to explain this.
Hi, have you found any solution to this issue?
I managed to get rid of the redirect loop mentioned, and make the routing a bit more robust and friendly-forwarding-ish, by doing the following (I used this example when I attempted to internationalize the Rails Tutorial:
config/routes.rb
Since there are two
root_paths, I renamed the one inside the:localescope so there would be no conflicts in the app and tests. I tested the routes using RSpec as follows:spec/routing/routing_spec.rb
I know that redirects are technically requests and their tests should really be under the spec/requests/ directory, but I figured they'd be more appropriate under routing since they're really just tests for exceptions and catch-alls. Anyway, hope this helps or is of interest to someone.
I want to set the locale by the clients browserlocale
request.env['HTTP_ACCEPT_LANGUAGE']and by the url.If the url has no
params[:locale]it should use the browserlocale set the url.Here is my code:
The Problem is that the routesfile gets executed first so the controller-actions won't grab anymore.
Does anyone has a solution for that?
Thank you, Ryan!
+1
This is awesome, works great on development but when I pushed to production I get this error in the log...
URI::InvalidComponentError (bad component(expected host component):
Thanks in advance for any help out there
I have followed all instructions and even copy/paste-d to make sure I made no mistakes. I'm at 6:16, right after adding default_url_options in the application controller and keep getting the same error when restarting the server.
whyyy?
Nevermind, simple mistake. Just had to change
get "dashboard/index"toget "dashboard/index" => 'dashboard#index'. I would like to delete my comment but I can't access a delete button.Hey Leonel, thanks for not deleting your comment - it helped me after a couple of hours scratching my head. Seems the 'get' calls need to explicitly point to the controller#action when doing things this way. Thanks dude!
Hi, does anyone knows how to accomplish that routing process in Rails 2.3.X ?
Thank you.
Hi, anybody knows why when i type long urls it redirected to an escaped url?
http://localhost:3000/users/login becomes http://localhost:3000/id/users%2Flogin
no worries, i was able to fix my problem by changing last two line of code on the route
Hi,
The routes seetings affects the assets path in production.
Ain't working as is.
nice approach rayn ,,I didnot test it in production yet ,,but it seems that its not working with with Endless page approach you showed before .
It works like a charm :) Thank you very much !
Just a question, how can I use I18n in javascript confirmation, for example to delete an item
<%= link_to raw(''), user, confirm: 'Are you sure?', method: :delete %>
Thanks :)
I did it like this
confirm: t("sure")
but in fact my question is more for links like the one below :
First sign in through GitHub to post a comment.