Ruby-GetText-Package HOWTO

ruby-gettext-howto

Install

$ gem install gettext

First step: hello.rb

begin
  require 'rubygems'
rescue LoadError
  $stderr.puts "RubyGems is not found."
end

require 'gettext'

class Test
  include GetText

  bindtextdomain("hello")

  def say_hello
    puts _("Hello World")
  end
end

Test.new.say_hello  # => Localized to your locale.

bindtextdomain binds "hello" textdomain to Test class. And _() returns localized message which is defined in the message catalog file(po-file).

Note: You shouldn't call _() outside of methods like as:

class Test
  include GetText

  bindtextdomain("hello")

  MSG = _("Hello World")  #(1)

  def say_hello
    puts _("Hello World") #(2)
  end
end

In this sample, (1) is called when this script is loaded. So MSG is set the localized strings once(from the computer environment variables) and will never be changed.

On the other hand, (2) returns the localized strings in the current locale when it's called. You can change the locale to use GetText.locale=() anywhere/anytime.

If you want to define MSG like (1), Try GetText.N_() method which appears later.

Extract strings in the script (create a po-file)

Create a po-file with rgettext.

$rgettext hello.rb -o hello.pot

Then translate hello.pot files. See Documents for Translators

Make a mo-file.

Finally, you make a mo-file and put it into the proper path. In this example, you don't set the path in bindtextdomain(), GetText searches for mo-file in /usr/share/locale/#{lang}/LC_MESSAGES/ or /usr/local/share/locale/#{lang}/LC_MESSAGES/, so you put it into one of them. Here, you put it into /usr/local/share/locale/ja/LC_MESSAGES/.

$rmsgfmt hello.po -o /usr/local/share/locale/ja/LC_MESSAGES/hello.mo

You can use msgfmt(from GNU GetText) instead of rmsgfmt.

Using GETTEXT_PATH, you can change the mo-files path.

$export GETTEXT_PATH="locale"
$rmsgfmt ja.po -o ./locale/ja/LC_MESSAGES/hoge.mo

Run the script

$ruby hello.rb

Is it OK? Congratulations!

If you can't see the messages in your language, execute it with -d option.

$ruby -d hello.rb

bind the domain 'hello' to 'test.rb'. locale is #<Locale::Object:0x2c66de8 @modifier=nil, @language="ja", @variant=nil, @orig_str="ja-JP", @script=nil, @charset="CP932", @country="JP"> MO file is not found in

locale/ja_JP.UTF-8/LC_MESSAGES/hello.mo
locale/ja_JP/LC_MESSAGES/hello.mo
locale/ja/LC_MESSAGES/hello.mo
locale/ja_JP.UTF-8/hello.mo
locale/ja_JP/hello.mo
locale/ja/hello.mo

Check hello.mo is in one of those pathes.

How to find the mo-file on your locale

If the locale is "ja_JP.eucJP", the search path(See previous debug messages: it includes locale) finds the order below:

  1. "ja_JP.eucJP"
  2. "ja_JP"
  3. "ja"

So, (3) is the most general locale. If you don't need to specify the locale, (3) style is recommanded.

How to use APIs.

String with arguments

You can give the arguments to the string with String#% method.

_("%s was not found") % "foo.rb"
_("%s and %s is not same file") % ["foo.rb", "bar.rb"]
_("%s is %d byte") % ["foo.rb", 100]

Also you can write as:

_("%{filename} was not found") % {:filename => "foo.rb"}
_("%{filename1} and %{filename2} is not same file") % {:filename1 => "foo.rb", 
                                                       :filename2 => "bar.rb"}
_("%{filename} is %{filesize} byte") % {:filename => "foo.rb", 
                                        :filesize => 100}

Translators can understand what the msgid mean easily, so the second way is recommanded.

GetText.n_(msgid, msgid_plural, n)

For plural forms, there is GetText.n_(). This is just like ngettext family of GNU GetText Package.

require 'gettext'

module Test
  include GetText
  bindtextdomain("plural")

  module_function
  def show
    (0..10).each do |n|
      puts n_("A file was removed", "%{num} files were removed", n) % {:num => n}
    end
  end
end

Test.show

The information about the plural form selection has to be stored in the header entry of the po-file. The plural form information looks like this:

"Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;\n"

The nplurals value is number of msgstr. The string following plural is an expression using Ruby syntax.

GetText.N_(msgid)

If you want to define some localized constants in your module/class, you need to use GetText.N_() then call GetText._() in the methods. (See "Create hello.rb" section for more details.)

require 'gettext'

module Test
  include GetText

  bindtextdomain("hello")

  MSG = N_("Hello World")

  module_function
  def show
    puts _(MSG)
  end
end

Test.show  

In this case, rgettext does not recognize _(msg) as msgid. But since you use GetText.N_(msgid) to define the messages, it is recognized as a msgid by rgettext.

GetText.Nn_(msgid, msgid_plural)

This method is the same as GetText.N_() of GetText.n_().

require 'gettext'

module Test
  include GetText

  bindtextdomain("hello")

  PLURAL = Nn_("A file was removed , "%{num} files were removed")

  module_function
  def show
    puts n_(PLURAL, 1)
    puts n_(PLURAL, 2)
  end
end

Test.show  

GetText.s_(msgid, div = "|")

In English, if a msgid has some meanings in some functions, you need to use GetText.s_(msgid, div = ""). Especially, some GUI menus need this feature.

puts s_("File|Open")          #(1)
puts s_("File|Printer|Open")  #(2)

In this case, both (1) and (2) return "Open" in C locale but they are defference text in other locales. That is, the GetText.s_() returns the last part of the text which separates div("|").

If you need, you can set the div as the 2nd parameter.

GetText.ns_(msgid, msgid_plural, n, div = "|")

Since 1.10.0 Like s_(), you can separate plural messages.

puts ns_("File|There is an item", "There are %{num} items", n) % {:num => n}          #(1)
puts ns_("Printer|There is an item\n", "There are %{num} items", n) % {:num => n}          #(2)

You can use "File", "Printer" like s_() as the msgid. You don't need to set them to the msgid_plural.

In English, "There is an item", "There are %{num} items" are returned.

GetText.p_(msgctxt, msgid), GetText.pn_(msgctxt, msgid, msgid_plural)

Since 1.93.0 Similer with GetText.s_, msgctxt is the better way to separate msgids.

puts s_("File|Open")
puts s_("Printer|Open")

puts ns_("File|There is an item", "There are %{num} items", n) % {:num => n}
puts ns_("Printer|There is an item", "There are %{num} items", n) % {:num => n}

Can be replaced to:

puts p_("File", "Open") 
puts p_("Printer", "Open")

puts np_("File", "There is an item", "There are %{num} items", n) % {:num => n} 
puts np_("Printer", "There is an item", "There are %{num} items", n) % {:num => n}

In this case, "File" and "Printer" become msgctxt. msgctxts won't be translated.

#: foo.rb:1
msgctxt "File"
msgid "Open"
msgstr "" 

#: foo.rb:2
msgctxt "Printer"
msgid "Open"
msgstr ""

#: foo.rb:4
msgctxt "File"
msgid "There is an item"
msgid_plural "There are %{num} items"
msgstr[0] ""
msgstr[1] ""

#: foo.rb:5
msgctxt "Printer"
msgid "There is an item"
msgid_plural "There are %{num} items"
msgstr[0] ""
msgstr[1] ""

Comments for translators.

Since 2.1.0 For the msgid which translator can't understand well, the comments are helpful.

-- foo.rb
def foo
  # TRANSLATORS: "file" is xxxxxx.
  # "lang" is ..... 
  _("%{file} %{lang} ...") % {:file => file, :lang => lang}
end

The result is:

-- foo.pot
#. "file" is xxxxxx.
#. "lang" is ..... 
#: foo.rb:4
msgid "foo"
msgstr ""

ChangeLogs

  • 2009-11-14 Add p_(), np_(),TRANSLATORS for 1.93.0, 2.1.0. - Masao
  • 2007-07-08 Add ns_() for 1.10.0. - Masao
  • 2006-04-26 Add a sample with rubygems. - Masao
  • 2006-02-13 Append "String with arguments" section. - Masao
  • 2005-12-24 Updated. - Masao
  • 2005-08-05 Append an explanation of msginit. Modified. - Masao
  • 2005-01-26 Some english fixes. - Jeff Williams
  • 2004-10-25 Separated some files. - Masao