Using I18n custom exception handler in the Rails views

Hello,

I have never posted here, neither contributed to any open source project. So I am not sure how to go about this. This is a proposal and a question at the same time. Trying to know if what is my head is valuable for Rails. It is just a tiny change.

In the Rails Internationalisation guide it shows how to set up a customer exception handler for I18n.

In the documentation is explained that Rails TranslationHelper will not use this custom exception handler.

Another example where the default behavior is less desirable is the Rails TranslationHelper which provides the method #t (as well as #translate). When a MissingTranslationData exception occurs in this context, the helper wraps the message into a span with the CSS class translation_missing.

I think it would be useful to be able to have a I18n custom exception handler even for the Rails Views and pass it the default translations.

It would allow to raise exceptions locally while failing silently in production and at the same time returning the default translation. I show this in the following Exception handler:

module I18n
  class JustRaiseExceptionHandler < ExceptionHandler
    def call(exception, locale, key, options)
      raise exception if Rails.env.development?
      Rails.logger.error "#{exception} #{locale} #{key}"
      options[:default_translation]
    end
  end
end

I have added two lines to the ActionView::Helpers::TranslationHelper#translate that would allow this behaviour with a given configuration. The only change is inside the rescue clause, after line 91 in original implementation.

Do you think this is valuable for Rails?

Regards,

David.

def translate(key, options = {})
  options = options.dup
  has_default = options.has_key?(:default)
  remaining_defaults = Array(options.delete(:default)).compact

  if has_default && !remaining_defaults.first.kind_of?(Symbol)
    options[:default] = remaining_defaults
  end

  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
  # Otherwise, tell I18n to raise an exception, which we rescue further in this method.
  # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
  if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
    raise_error = false
    i18n_raise = false
  else
    raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
    i18n_raise = true
  end

  if html_safe_translation_key?(key)
    html_safe_options = options.dup
    options.except(*I18n::RESERVED_KEYS).each do |name, value|
      unless name == :count && value.is_a?(Numeric)
        html_safe_options[name] = ERB::Util.html_escape(value.to_s)
      end
    end
    translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))

    translation.respond_to?(:html_safe) ? translation.html_safe : translation
  else
    I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
  end
rescue I18n::MissingTranslationData => e
  if remaining_defaults.present?
    translate remaining_defaults.shift, options.merge(default: remaining_defaults)
  else
    raise e if raise_error

    keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
    default_translation = content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")

    if custom_exception_handler?
      I18n.exception_handler.call(e, e.locale, e.key, {default_translation: default_translation})
    else
      default_translation
    end
  end
end
alias :t :translate

private

def custom_exception_handler?
  true # ActionView::Base.use_i18n_exception_handler
end