Integrating Ruby GetText with ActiveScaffold

About half a year ago I wrote a snippet of code to make the GetText package for the Ruby programming language to work with ActiveScaffold, a highly configurable dynamic editing facility for Ruby On Rails applications.

I published this as a contribution on ActiveScaffold's project wiki. Since then there had been at least one supplement by another user. One day the wiki was not availabe, and the link structure changed afterwards and the wiki became read-only. I guess this was the final brake against comment spam, being a plague everywhere on the net by turning community features into an annoying mess of crap.

Today I decided to duplicate my former article here for backup reasons and for getting back the opportunity to have changes added by myself or others, paying the price of redundancy:

(begin of original article)

Update 06-27-07 (added missing varargs handling)
Update 06-28-07 (enforced signature conformance in #[N]_())
Update 09-12-07 (changed to use the Action Controller textdomainname and removed setting single locale: NOTE YOU MUST NOW REQUIRE localize_active_scaffold.rb in the body of your application controller)

Integrating the Active Scaffold Localization Plugin sources, I had consulted for a clue.

The core of the solution is the following module to be placed in your app’s lib subdirectory:

#  localize_active_scaffold.rb
#  Created by ofi on 2007-06-24.
#  Published under MIT license (like RoR).
#
module LocalizeActiveScaffold

  include GetText

  # [09-12-07] changed to use the ActionController textdomainname
  # and removed setting single locale
  bindtextdomain(ActionController::Base.textdomainname, :path =>
    File.join(RAILS_ROOT, "locale"))

  GT_LOG = Logger.new("#{RAILS_ROOT}/log/gettext.log")
  GT_LOG.level = Logger::WARN
  GT_LOG.error "**** Started log at #{Time.new.to_s}"

  # [06-27-07] no varargs parameter anymore
  # [07-12-07]:(anonymous) use GetText.s_ when column name is specified
  def self.translate(string_to_localize)
    if string_to_localize.include?('|')
      res = s_(string_to_localize)
      GT_LOG.debug "translate() called for: #{string_to_localize} => #{res}"
      if GT_LOG.warn? and (res == string_to_localize.split(/\|/).pop)
        GT_LOG.warn "No translation for <#{string_to_localize}>!"
      end
    else
      res = _(string_to_localize)
      GT_LOG.debug "translate() called for: #{string_to_localize} => #{res}"
      if GT_LOG.warn? and (res == string_to_localize)
        GT_LOG.warn "No translation for <#{string_to_localize}>!"
      end
    end
    return res
  end

  # [06-27-07] no varargs parameter anymore
  def self.dont_translate(string_to_localize)
    N_(string_to_localize)
  end

private

  # The following strings contained in the ActiveScaffold plugin in various
  # files serve as a proxy for rgettext (driven by the rake task updatepo)
  # for making them appear in the po files.
  def dummy
    N_('Open')
    N_('Close')
    N_('close')
    N_('Edit')
    N_('Delete')
    N_('Show')
    N_('Search')
    N_('Search Terms')
    N_('No Entries')
    N_('Found')
    N_('Create New')
    N_('Are you sure?')
    N_('Cancel')
  end

end

# The following methods delegate ActiveScaffold's call to as_() as well as
# gettext's calls to _() to the above translate() method:
class Object

  # [06-28-07]: enforce single argument signature
  def _(*args)
    LocalizeActiveScaffold.translate(args[0].to_s)
  end

  # [06-28-07]: enforce single argument signature
  def N_(*args)
    LocalizeActiveScaffold.dont_translate(args[0].to_s)
  end

  # coupling ActiveScaffold to gettext by declaring this
  # method, which will be called by AS:
  # [06-27-07]: now calling the insertion operator on the
  # varargs parameters (if any). CAVEAT: Too few parameters
  # will deliver nil as the method's result!
  # [06-28-07]: less is more: removed redundant cond. expr.
  def as_(loc_string, *args)
    LocalizeActiveScaffold.translate(loc_string) % args
  end

end

# following method replaces column labels into 'ClassName|Attribute name' forms.
# gettext returns column name as that's form.
# [07-12-07]:(anonymous)
module ActiveScaffold::DataStructures
  class Column
    def initialize_with_gettext(name, active_record_class)
      initialize_without_gettext(name, active_record_class)
      self.label = active_record_class.name.demodulize + '|' +  self.label.humanize
    end
    alias_method_chain :initialize, :gettext
  end
end

What it does:

  • The enhancements to Object take care for the appropriate calls at runtime. I first tried without N_(), but saw in the log, that all strings in a “dclarative” context like in arrays or the AS config blocks triggered at least two calls, meaning that translation may happen too early, and in a real multi-language application with possible locale switches between requests, the user will be presented with the wrong translation.
  • The private dummy method only flags terms for rgettext which appear in the AS sources in various files.
  • The logger GT_LOG in the tranlsate() method when set to WARN will note down phrases not translated into log/gettect.log, so you can add these terms to dummy(), do the “rake updatepo; edit <...>; rake makemo” dance (see the Ruby Gettext pages for details), et voilà.

To use the module You will need to “require” it in your application.rb file after init_gettext like:

...
class ApplicationController < ActionController::Base
  init_gettext 'your_app'
  require "localize_active_scaffold"
...

Of course I won’t win a programming-elegance award with this, but as long as it can save someone some time I wish I had spent with something else, this is all I’d like to achieve.

(end of original article)

Go Top