26

How can I send mails in a mailer using the recipient's locale. I have the preferred locale for each user in the database. Notice this is different from the current locale (I18n.locale), as long as the current user doesn't have to be the recipient. So the difficult thing is to use the mailer in a different locale without changing I18n.locale:

def new_follower(user, follower)
  @follower = follower
  @user = user
  mail :to=>@user.email
end

Using I18n.locale = @user.profile.locale before mail :to=>... would solve the mailer issue, but would change the behaviour in the rest of the thread.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Jose
  • 609
  • 1
  • 7
  • 15
  • Moreover, I get a "SystemStackError: stack level too deep" error if attempt to change locale through I18n.locale = whatever – Jose Aug 23 '10 at 02:19
  • I've got the same problem; and there's an active Rails bug: https://rails.lighthouseapp.com/projects/8994/tickets/5329-using-i18nwith_locale-in-actionmailer-raises-systemstackerror – Don Werve Sep 23 '10 at 23:35

7 Answers7

46

I believe the best way to do this is with the great method I18n.with_locale, it allows you to temporarily change the I18n.locale inside a block, you can use it like this:

def new_follower(user, follower)
  @follower = follower
  @user = user
  I18n.with_locale(@user.profile.locale) do
    mail to: @user.email
  end
end

And it'll change the locale just to send the email, immediately changing back after the block ends.

Source: http://www.rubydoc.info/docs/rails/2.3.8/I18n.with_locale

Miguelgraz
  • 4,376
  • 1
  • 21
  • 16
6

This answer was a dirty hack that ignored I18n's with_locale method, which is in another answer. The original answer (which works but you shouldn't use it) is below.

Quick and dirty:

class SystemMailer < ActionMailer::Base
  def new_follower(user, follower)
    @follower = follower
    @user = user
    using_locale(@user.profile.locale){mail(:to=>@user.email)}
  end

  protected
  def using_locale(locale, &block)
    original_locale = I18n.locale
    I18n.locale = locale
    return_value = yield
    I18n.locale = original_locale
    return_value
  end
end
jimworm
  • 2,731
  • 18
  • 26
4

in the most resent version of rails at this time it's sufficient to use "I18n.locale = account.locale" in the controller and make multiple views with the following naming strategy welcome.html.erb, welcome.it.html.erb and e.g. welcome.fr.html.erb

koen
  • 41
  • 2
2

None of the above is really working since the version 3 to translate both subject and content and be sure that the locale is reseted back to the original one... so I did the following (all mailer inherit from that class:

class ResourceMailer < ActionMailer::Base

  def mail(headers={}, &block)
    I18n.locale = mail_locale
    super
  ensure
    reset_locale
  end

  def i18n_subject(options = {})
    I18n.locale = mail_locale
    mailer_scope = self.class.mailer_name.gsub('/', '.')
    I18n.t(:subject, options.merge(:scope => [mailer_scope, action_name], :default => action_name.humanize))
  ensure
    reset_locale
  end  

  def set_locale(locale)
    @mail_locale = locale
  end

  protected

  def mail_locale
    @mail_locale || I18n.locale
  end

  def reset_locale
    I18n.locale = I18n.default_locale
  end

end

You just need to set the locale before you call the mail() method:

set_locale @user.locale

You can use the i18n_subject method which scope the current path so everything is structured:

mail(:subject => i18n_subject(:name => @user.name)
Vincent Peres
  • 1,003
  • 1
  • 13
  • 28
  • 2
    in my opinion, the `reset_locale` should set I18n.locale to the initial value and not the default value. everything else seems ok. thanks – Dorian Oct 03 '11 at 15:45
1

This simple plugin was developed for rails 2 but seems to work in rails 3 too.

http://github.com/Bertg/i18n_action_mailer

With it you can do the following:

def new_follower(user, follower)
  @follower = follower
  @user = user
  set_locale user.locale
  mail :to => @user.email, :subject => t(:new_follower_subject)
end

The subject and mail templates are then translated using the user's locale.

Kusti
  • 1,279
  • 1
  • 10
  • 18
  • Thanks! But which plugin do you mean? :) – Jose Aug 30 '10 at 19:12
  • Oops, forgot the link! :D Now it's there, it's i18n_action_mailer plugin. – Kusti Aug 31 '10 at 08:11
  • Unfortunately, it doesn't fill my purpose, as I have different views for each locale: welcome.es.erb, welcome.en.erb, etc. The plugin only overloads t and l methods. – Jose Sep 02 '10 at 21:47
  • True, if I need that I need to use an extra partial to handle them that just says: = render :partial => "template_#{I18n.locale}" etc., and then name the views like "welcome_es.erb" etc., a bit uglier than the way you mentioned but still just one additional line. – Kusti Nov 24 '10 at 12:17
  • ...and if you're using layouts that need to be translated you need to add 'layout "email_#{I18n.locale}"' on every mailer method, which is a bit ugly too, so a cleaner solution would definitely be welcomed. – Kusti Nov 24 '10 at 15:06
1

Here's an updated version that also supports the '.key' short-hand notation, so you don't have to spell out each key in its entirety.

http://github.com/larspind/i18n_action_mailer

Calvin Correli
  • 173
  • 2
  • 6
  • Thanks for the plugin. However, it still fails to select the appropriate view according to the locale under Rails 3 – Jose Sep 28 '10 at 08:03
1

The problem with the mentioned plugins are that they don't work in all situations, for example doing User.human_name or User.human_attribute_name(...) will not translate correctly. The following is the easiest and guaranteed method to work:

stick this somewhere (in initializers or a plugin):

  module I18nActionMailer
    def self.included(base)
      base.class_eval do
        include InstanceMethods
        alias_method_chain :create!, :locale
      end
    end

    module InstanceMethods
      def create_with_locale!(method_name, *parameters)
        original_locale = I18n.locale
        begin
          create_without_locale!(method_name, *parameters)
        ensure
          I18n.locale = original_locale
        end
      end
    end
  end

  ActionMailer::Base.send(:include, I18nActionMailer)

and then in your mailer class start your method by setting the desired locale, for example:

  def welcome(user)
    I18n.locale = user.locale
    # etc.
  end
Jim Soho
  • 2,018
  • 2
  • 21
  • 25
  • This code renders the following error "`alias_method': undefined method `create!' for class `ActionMailer::Base' (NameError)" when I tried to run it on rails 3.2.8 – wael34218 Sep 14 '12 at 11:39