=begin gettext.rb - GetText module Copyright (C) 2001-2006 Masao Mutoh Copyright (C) 2001-2003 Masahiro Sakai Masao Mutoh Masahiro Sakai You may redistribute it and/or modify it under the same license terms as Ruby. $Id: gettext.rb,v 1.20 2006/09/10 15:08:32 mutoh Exp $ =end require 'rbconfig' require 'gettext/version' require 'gettext/mo' require 'gettext/locale' require 'gettext/textdomainmanager' require 'gettext/string' module GetText # If the textdomain isn't bound when calling GetText.textdomain, this error is raised. class NoboundTextDomainError < RuntimeError end def self.included(mod) #:nodoc: mod.extend self end @@__textdomainmanagers = Hash.new # call-seq: # bindtextdomain(domainname, options = {}) # # Bind a textdomain(%{path}/%{locale}/LC_MESSAGES/%{domainname}.mo) to your program. # Normally, the texdomain scope becomes a ruby-script-file. # So you need to call this function each ruby-script-files. # On the other hand, if you call this function under GetText::Container # (gettext/container, gettext/erb, gettext/rails), the textdomain scope becomes a Class/Module. # * domainname: the textdomain name. # * options: options as an Hash. # * :path - the path to the mo-files. When the value is nil, it will search default paths such as # /usr/share/locale, /usr/local/share/locale) # * :locale - the locale string such as "ja_JP.UTF-8". Generally, you should use GetText.set_locale instead. # The value is searched order by: # the value of this value > System default language. # * :charset - output charset. This affect the current textdomain only. Generally, you should use GetText.set_output_charset instead. # The value is searched order by: # the value of Locale.set_output_charset > ENV["OUTPUT_CHARSET"] > this value > System default charset. # * Returns: the GetText::TextDomainManager. # Note: Don't use locale_, charset argument(not in options). # They are remained for backward compatibility. # def bindtextdomain(domainname, options = {}, locale_ = nil, charset = nil) opt = {} if options.kind_of? String # For backward compatibility opt = {:path => options, :locale => locale_, :charset => charset} elsif options opt = options end opt[:locale] = opt[:locale] ? Locale::Object.new(opt[:locale]) : Locale.get opt[:charset] = output_charset if output_charset locale.charset = opt[:charset] if opt[:charset] Locale.set_current(opt[:locale]) manager = @@__textdomainmanagers[bound_target] if manager manager.set_locale(opt[:locale]) else manager = TextDomainManager.new(bound_target, opt[:locale]) @@__textdomainmanagers[bound_target] = manager end manager.add_textdomain(domainname, opt) manager end # Includes GetText module and bind a textdomain to a class. # * klass: the target ruby class. # * domainname: the textdomain name. # * options: options as an Hash. See GetText.bindtextdomain. def bindtextdomain_to(klass, domainname, options = {}) ret = nil klass.module_eval { include GetText ret = bindtextdomain(domainname, options) } ret end # Binds a existed textdomain to your program. # This is the same function with GetText.bindtextdomain but simpler(and faster) than bindtextdomain. # Notice that you need to call GetText.bindtextdomain first. If the domainname hasn't bound yet, # raises GetText::NoboundTextDomainError. # * domainname: a textdomain name. # * Returns: the GetText::TextDomainManager. def textdomain(domainname) domain = TextDomainManager.textdomain(domainname) raise NoboundTextDomainError, "#{domainname} is not bound." unless domain manager = @@__textdomainmanagers[bound_target] unless manager manager = TextDomainManager.new(bound_target, Locale.get) @@__textdomainmanagers[bound_target] = manager end manager.set_locale(Locale.get) manager.add_textdomain(domainname) manager end # Includes GetText module and bind an exsited textdomain to a class. # See textdomain for more detail. # * klass: the target ruby class. # * domainname: the textdomain name. def textdomain_to(klass, domainname) ret = nil klass.module_eval { include GetText ret = textdomain(domainname) } ret end # Iterates bound textdomains. # * klass: a class/module to find. Default is the class of self. # * ignore_targets: Ignore tragets. # * Returns: a bound GetText::TextDomain or nil. def each_textdomain(klass = bound_target, ignore_targets = []) #:nodoc: bound_targets(klass).each do |target| unless ignore_targets.include? target manager = @@__textdomainmanagers[target] if manager manager.each{ |textdomain| yield textdomain } end end end self end def bound_target # :nodoc: if self.kind_of? Class or self.kind_of? Module self else self.class end end def bound_targets(klass) # :nodoc: ret = [] ary = klass.name.split(/::/) while(v = ary.shift) ret.unshift(((ret.size == 0) ? eval(v) : ret[0].const_get(v))) end ((ret + klass.ancestors + [GetText]) & @@__textdomainmanagers.keys).uniq end # call-seq: # gettext(msgid) # _(msgid) # # Translates msgid and return the message. # * msgid: the message id. # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid. def gettext(msgid) ret = nil each_textdomain {|textdomain| ret = textdomain.gettext(msgid) break if ret } ret ? ret : msgid end # call-seq: # ngettext(msgid, msgid_plural, n) # ngettext(msgids, n) # msgids = [msgid, msgid_plural] # n_(msgid, msgid_plural, n) # n_(msgids, n) # msgids = [msgid, msgid_plural] # # The ngettext is similar to the gettext function as it finds the message catalogs in the same way. # But it takes two extra arguments for plural form. # # * msgid: the singular form. # * msgid_plural: the plural form. # * n: a number used to determine the plural form. # * Returns: the localized text which key is msgid_plural if n is plural(follow plural-rule) or msgid. # "plural-rule" is defined in po-file. def ngettext(arg1, arg2, arg3 = nil) if arg1.kind_of?(Array) msgid = arg1[0] msgid_plural = arg1[1] n = arg2 else msgid = arg1 msgid_plural = arg2 n = arg3 end ret = nil each_textdomain {|textdomain| ret = textdomain.ngettext(msgid, msgid_plural, n) break if ret } ret ? ret : (n == 1 ? msgid : msgid_plural) end # This function does nothing. But it is required in order to recognize the msgid by rgettext. # * msgid: the message id. # * Returns: msgid. def N_(msgid) msgid end # This is same function as N_ but for ngettext. # * msgid: the message id. # * msgid_plural: the plural message id. # * Returns: msgid. def Nn_(msgid, msgid_plural) [msgid, msgid_plural] end # call-seq: # sgettext(msgid, div = '|') # s_(msgid, div = '|') # # Translates msgid, but if there are no localized text, # it returns a last part of msgid separeted "div". # # * msgid: the message id. # * div: separator or nil. # * Returns: the localized text by msgid. If there are no localized text, # it returns a last part of msgid separeted "div". # See: http://www.gnu.org/software/gettext/manual/html_mono/gettext.html#SEC151 def sgettext(msgid, div = '|') msg = gettext(msgid) if msg == msgid if index = msg.rindex(div) msg = msg[(index + 1)..-1] end end msg end # Sets the default locale to the current class/module # # Notice that you shouldn't use this for your own Libraries. # * locale: a locale string or Locale::Object. # * this_target_only: true if you want to change the current class/module only. # Otherwise, this changes the locale of the current class/module and its ancestors. # Default is false. # * Returns: self def set_locale(locale, this_target_only = false) ret = nil if locale if locale.kind_of? Locale::Object ret = locale else ret = Locale::Object.new(locale.to_s) end ret.charset = output_charset if output_charset Locale.set(ret) else Locale.set(nil) ret = Locale.get end if this_target_only manager = @@__textdomainmanagers[bound_target] if manager manager.set_locale(ret) end else each_textdomain {|textdomain| textdomain.set_locale(ret) } end self end # Sets locale to the all textdomains. # # Notice that you shouldn't use this for your own Libraries. # * locale: a locale string or Locale::Object, otherwise nil to use default locale. # * Returns: self def set_locale_all(locale) ret = nil if locale if locale.kind_of? Locale::Object ret = locale else ret = Locale::Object.new(locale.to_s) end Locale.set_current(ret) else Locale.set_current(nil) ret = Locale.get end ret.charset = output_charset if output_charset TextDomainManager.each_all {|textdomain| textdomain.set_locale(ret) } self end # Same as GetText.set_locale_all. # * locale: a locale string # * Returns: a locale string def locale=(locale) set_locale_all(locale) locale end # Sets charset(String) such as "euc-jp", "sjis", "CP932", "utf-8", ... # You shouldn't use this in your own Libraries. # * charset: an output_charset # * Returns: charset def set_output_charset(charset) TextDomainManager.output_charset = charset self end # Same as GetText.set_output_charset # * charset: an output_charset # * Returns: charset def output_charset=(charset) TextDomainManager.output_charset = charset end # Gets the current output_charset which is set using GetText.set_output_charset. # * Returns: output_charset. def output_charset TextDomainManager.output_charset end # Gets the current locale. # * Returns: a current Locale::Object def locale Locale.current end # Deprecated. Now this function do nothing. Use GetText.output_charset= instead. def set_charset(cs) #:nodoc: $stderr.puts "Deprecated. Now this function do nothing. Use GetText.output_charset= instead." if $DEBUG self end def charset=(cs) #:nodoc: set_charset(cs) cs end # Add default locale path. # * path: a new locale path. (e.g.) "/usr/share/locale/%{locale}/LC_MESSAGES/%{name}.mo" # ('locale' => "ja_JP", 'name' => "textdomain") # * Returns: the new DEFAULT_LOCALE_PATHS def add_default_locale_path(path) TextDomain.add_default_locale_path(path) end # Show the current textdomain information. This function is for debugging. # * options: options as a Hash. # * :with_messages - show informations with messages of the current mo file. Default is false. # * :out - An output target. Default is STDOUT. # * :with_paths - show the load paths for mo-files. # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid. def current_textdomain_info(options = {}) opts = {:with_messages => false, :with_paths => false, :out => STDOUT}.merge(options) ret = nil each_textdomain {|textdomain| opts[:out].puts "TextDomain name: \"#{textdomain.name}\"" opts[:out].puts "TextDomain current locale: \"#{textdomain.current_locale}\"" opts[:out].puts "TextDomain current mo filename: \"#{textdomain.current_mo.filename}\"" if opts[:with_paths] opts[:out].puts "TextDomain locale file paths:" textdomain.locale_paths.each do |v| opts[:out].puts " #{v}" end end if opts[:with_messages] opts[:out].puts "The messages in the mo file:" textdomain.current_mo.each{|k, v| opts[:out].puts " \"#{k}\": \"#{v}\"" } end } end alias :setlocale :locale= #:nodoc: alias :_ :gettext #:nodoc: alias :n_ :ngettext #:nodoc: alias :s_ :sgettext #:nodoc: module_function :bindtextdomain, :textdomain, :each_textdomain, :N_, :gettext, :_, :ngettext, :n_, :sgettext, :s_, :bound_target, :bound_targets, :setlocale, :set_locale, :locale=, :set_locale_all, :locale, :charset=, :set_charset, :set_output_charset, :output_charset=, :output_charset, :current_textdomain_info end