Right out of the box, Rails makes setting up your site for different languages very easy. Here’s how to setup I18n
internationalization for your emails as well.
Introduction
Now that I’m working in Europe again, I’ve been getting used to building multi-lingual sites. Of course it’s always best practice to your keep your site’s static content in language specific files, but when I worked in the US, this rarely happened. Setting this up is quite straight forward, even for emails.
Setting up the locales
Set up the the default and available locales for the application.
# application.rb
# set the default locale to English
config.i18n.default_locale = :en
# if a locale isn't found fall back to this default locale
config.i18n.fallbacks = true
# set the possible locales to English and Brazilian-Portuguese
config.i18n.available_locales = [:en, :'pt-BR']
When setting the available locales, we need to add the corresponding I18n yml
files:
config/locale/en.yml
config/locale/pt-BR.yml
Adding locale to the user
We’ll need to add a locale
attribute to the user object that can be used to store the user’s preferred language - this is what we’ll use to determine what language to pick for our email.
# 20150722104103_add_locale_to_users.rb
class AddLocaleToUsers < ActiveRecord::Migration
def change
add_column :users, :locale, :string, null: false, default: :en
end
end
Setting the user locale
There’s quite a few different ways get the user’s locale, we can:
- use the
HTTP_ACCEPT_LANGUAGE
HTTP header - use GeoIP database lookup on their IP address
- allow the user to set their locale themselves
The best option is generally a mix of the above. Try to detect the users language using the Accept Language HTTP header or GeoIP lookup, and then allow the user to override this, setting their own preferred locale if they wish to change.
The rails guides has some more information here: Setting the Locale from the Client Supplied Information, including some good gems for making the task easier.
Translate your email
Let’s say we’ve got a UserMailer
to notify a user they’ve got a new follower.
Here’s how our email template looks without any internationalization support:
# app/views/user_mailer/new_follower.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Hi <%= @user.name %></h1>
<p>
<%= @follower.name %> is now following you!
</p>
</body>
</html>
Instead of using static strings for our copy, we should pull the strings out and place them in yml
files for their respective languages.
Here’s what our yml
files will look like:
# config/locales/en.yml
en:
user_mailer:
new_follower:
subject: 'You have a new follower'
greeting: 'Hi %{name},'
content: '%{name} is now following you!'
# config/locales/pt-BR.yml
pt-BR:
user_mailer:
new_follower:
subject: 'Você tem um novo seguidor'
greeting: 'Olá %{name},'
content: '%{name} está te seguindo!'
Then we can swap in those translated strings to the email template. If we name the keys correctly - user_mailer
(after the UserMailer
) and new_follower
(after the mailer method and template name), the translation helper picks up the greeting
and content
keys nicely.
# app/views/user_mailer/new_follower.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>
<%= t('.greeting, name: @user.name) %>
</h1>
<p>
<%= t('.content', name: @follow.name) %>
</p>
</body>
</html>
Sending email
No that we’ve got our email template translated we can send the user an email translated into their preferred language.
We do this using the I18n.with_locale method. It executes a block of code in a given locale. In this instance, our block of code is sending the user an email and the locale is stored on our @user
instance.
# app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
def new_follower(user, follower)
@user = user
@project = follower
I18n.with_locale(@user.locale) do
mail(
to: @user.email,
subject: I18n.t('user_mailer.new_follower.subject')
)
end
end
end
Sending email with Devise
If you use devise for user authentication, then you know devise has it’s own emails as well.
To translate these you can override the send_devise_notification
method, which is called every time devise sends an email/notification. We override the method, set the locale to the user’s locale, and then call the original method in the block using super
.
# app/models/user.rb
def send_devise_notification(notification, *args)
I18n.with_locale(self.locale) { super(notification, *args) }
end
Alternatively you can set the locale for a specific email by overriding the method for that mail. Here’s an example with the send_confirmation_instructions
method, which sends the user’s confirmation email.
# app/models/user.rb
def send_confirmation_instructions
I18n.with_locale(self.locale) { super }
end
You can find translations in many different locales for all the devise-related copy in the Devise Wiki.
Conclusion
And there you have it, sending emails to users in their language of choice. While it make take a bit more work putting the I18n framework in place at the start, getting a project properly setup for localization at the beginning is definitely well worth it. As well as the obvious gains of making the site multi-lingual, all your copy lives one place, becoming much easier for non-tech folks to manage.