lib/gettext.rb in gettext-1.93.0 vs lib/gettext.rb in gettext-2.0.0

- old
+ new

@@ -1,247 +1,104 @@ =begin gettext.rb - GetText module - Copyright (C) 2001-2008 Masao Mutoh + Copyright (C) 2001-2009 Masao Mutoh Copyright (C) 2001-2003 Masahiro Sakai - Masao Mutoh <mutoh@highway.ne.jp> + Masao Mutoh <mutomasa at gmail.com> Masahiro Sakai <s01397ms@sfc.keio.ac.jp> You may redistribute it and/or modify it under the same license terms as Ruby. $Id: gettext.rb,v 1.46 2008/09/13 18:23:55 mutoh Exp $ =end -require 'rbconfig' -require 'gettext/version' -require 'gettext/mo' +begin gem 'locale', '>=0.9'; rescue LoadError;end require 'locale' -require 'gettext/textdomainmanager' -require 'gettext/string' +raise "Insall locale as gem or uninstall old gettext" unless Locale.respond_to? :candidates +require 'gettext/version' +require 'gettext/textdomain_manager' + module GetText # If the textdomain isn't bound when calling GetText.textdomain, this error is raised. class NoboundTextDomainError < RuntimeError + def initialize(domainname) + @domainname = domainname + end + def message + "#{domainname} is not bound." + end end + extend self + def self.included(mod) #:nodoc: mod.extend self end - # Max cached object_ids includes dead objects. - CACHE_BOUND_TARGET_MAX_SIZE = 50000 #:nodoc: - - @@__cached = ! $DEBUG - - # Set the value whether cache messages or not. - # true to cache messages, otherwise false. - # - # Default is true. If $DEBUG is false, messages are not checked even if - # this value is true. - def cached=(val) - @@__cached = val - GetText::TextDomain.check_mo = ! val - end - - # Return the cached value. - def cached? - @@__cached - end - - # Clear the cached messages. - def clear_cache - @@__cache_msgids = {} - @@__cache_nmsgids = {} - @@__cache_target_classes = {} - @@__cache_bound_target = {} - @@__cache_bound_targets = {} - end - - @@__textdomainmanagers = {} - - # 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. + # Bind a textdomain(%{path}/%{locale}/LC_MESSAGES/%{domainname}.mo) to + # your program. + # Normally, the texdomain scope becomes the class/module(and parent + # classes/included modules). + # # * 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. + # * :supported_language_tags - an Array of the supported language tags for this textdomain. + # * :output_charset - The output charset. Same with GetText.set_output_charset. Usually, L10n + # library doesn't use this option. Application may use this once. # * 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] = TextDomainManager.output_charset if TextDomainManager.output_charset - opt[:locale].charset = opt[:charset] if opt[:charset] - Locale.set_current(opt[:locale]) - target_key = bound_target - manager = @@__textdomainmanagers[target_key] - if manager - manager.set_locale(opt[:locale]) - else - manager = TextDomainManager.new(target_key, opt[:locale]) - @@__textdomainmanagers[target_key] = manager - end - manager.add_textdomain(domainname, opt) - manager + def bindtextdomain(domainname, options = {}) + bindtextdomain_to(self, domainname, options) 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 + def bindtextdomain_to(klass, domainname, *args) + if args[0].kind_of? Hash + options = args[0] + else + # for backward compatibility. + options = {} + options[:path] = args[0] if args[0] + options[:output_charset] = args[2] if args[2] + end + unless (klass.kind_of? GetText or klass.include? GetText) + klass.__send__(:include, GetText) + end + TextDomainManager.bind_to(klass, domainname, options) 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, + # Note 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 - target_key = bound_target - manager = @@__textdomainmanagers[target_key] - unless manager - manager = TextDomainManager.new(target_key, Locale.get) - @@__textdomainmanagers[target_key] = manager - end - manager.set_locale(Locale.get) - manager.add_textdomain(domainname) - manager + def textdomain(domainname) #:nodoc: + textdomain_to(self, domainname) 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 = self, 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 + def textdomain_to(klass, domainname) #:nodoc: + domain = TextDomainManager.textdomain_pool(domainname) + raise NoboundTextDomainError.new(domainname) unless domain + bindtextdomain_to(klass, domainname) end - @@__cache_target_classes = {} - - def find_targets(klass) #:nodoc: - unless (klass.kind_of? Class or klass.kind_of? Module) - klass = klass.class - end - id = klass.object_id - if cached? - return @@__cache_target_classes[id] if @@__cache_target_classes[id] - end - - ret = [] - ary = klass.name.split(/::/) - while(v = ary.shift) - if ret.size == 0 - if v.kind_of? Class - target = v - else - target = eval(v) - return [Object] unless (target = eval(v)) # For anonymous module - end - else - target = ret[0].const_get(v) - end - ret.unshift(target) if target - end - @@__cache_target_classes[id] = ret.size > 0 ? ret : [klass] - end - - @@__cache_bound_target = {} - - def bound_target(klass = self) # :nodoc: - ret = (klass.kind_of? Module) ? klass : klass.class - id = ret.object_id - if cached? - tgt = @@__cache_bound_target[id] - return tgt if tgt - end - - if ret.name =~ /^\#<|^$/ or ret == GetText - #GC for dead object_ids. - ret = Object - if @@__cache_bound_target.size > CACHE_BOUND_TARGET_MAX_SIZE - @@__cache_bound_target.clear - end - end - @@__cache_bound_target[id] = ret - ret - end - - @@__cache_bound_targets = {} - - def bound_targets(klass) # :nodoc: - id = klass.object_id - if cached? - if @@__cache_bound_targets[id] - return @@__cache_bound_targets[id] - end - end - ret = [] - klass = bound_target(klass) - ary = klass.name.split(/::/) - while(v = ary.shift) - ret.unshift(((ret.size == 0) ? eval(v) : ret[0].const_get(v))) - end - @@__cache_bound_targets[id] = ((ret + klass.ancestors + [Object]) & @@__textdomainmanagers.keys).uniq - end - - @@__cache_msgids = {} - # call-seq: # gettext(msgid) # _(msgid) # # Translates msgid and return the message. @@ -259,51 +116,28 @@ # Because the translator may want to change the position of "world". # # * msgid: the message id. # * Returns: localized text by msgid. If there are not binded mo-file, it will return msgid. def gettext(msgid) - sgettext(msgid, nil) + TextDomainManager.get(self).translate_singluar_message(self, msgid) 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. + # * separator: separator or nil for no seperation. # * Returns: the localized text by msgid. If there are no localized text, - # it returns a last part of msgid separeted "div". + # it returns a last part of the msgid separeted by "seperator". + # <tt>Movie|Location -> Location</tt> # See: http://www.gnu.org/software/gettext/manual/html_mono/gettext.html#SEC151 - def sgettext(msgid, div = '|') - cached_key = [bound_target, Locale.current, msgid] - if cached? - if @@__cache_msgids[cached_key] - return @@__cache_msgids[cached_key] - end - end - msg = nil - - # Use "for"(not "each") to support JRuby 1.1.0. - for target in bound_targets(self) - manager = @@__textdomainmanagers[target] - for textdomain in manager.textdomains - msg = textdomain[1].gettext(msgid) - break if msg - end - break if msg - end - - msg ||= msgid - if div and msg == msgid - if index = msg.rindex(div) - msg = msg[(index + 1)..-1] - end - end - @@__cache_msgids[cached_key] = msg + def sgettext(msgid, seperator = "|") + TextDomainManager.get(self).translate_singluar_message(self, msgid, seperator) end # call-seq: # pgettext(msgctxt, msgid) # p_(msgctxt, msgid) @@ -316,15 +150,13 @@ # * msgid: the message id. # * Returns: the localized text by msgid. If there are no localized text, # it returns msgid. # See: http://www.gnu.org/software/autoconf/manual/gettext/Contexts.html def pgettext(msgctxt, msgid) - sgettext(msgctxt + "\004" + msgid, "\004") + TextDomainManager.get(self).translate_singluar_message(self, "#{msgctxt}\004#{msgid}", "\004") end - @@__cache_nmsgids = {} - # 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] @@ -335,15 +167,34 @@ # * 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) - nsgettext(arg1, arg2, arg3, nil) + def ngettext(msgid, msgid_plural, n = nil) + TextDomainManager.get(self).translate_plural_message(self, msgid, msgid_plural, n) end # call-seq: + # nsgettext(msgid, msgid_plural, n, div = "|") + # nsgettext(msgids, n, div = "|") # msgids = [msgid, msgid_plural] + # ns_(msgid, msgid_plural, n, div = "|") + # ns_(msgids, n, div = "|") # msgids = [msgid, msgid_plural] + # + # The nsgettext is similar to the ngettext. + # But if there are no localized text, + # it returns a last part of msgid separeted "div". + # + # * msgid: the singular form with "div". (e.g. "Special|An apple") + # * msgid_plural: the plural form. (e.g. "%{num} Apples") + # * 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 nsgettext(msgid, msgid_plural, n="|", seperator = "|") + TextDomainManager.get(self).translate_plural_message(self, msgid, msgid_plural, n, seperator) + end + + # call-seq: # npgettext(msgctxt, msgid, msgid_plural, n) # npgettext(msgctxt, msgids, n) # msgids = [msgid, msgid_plural] # np_(msgctxt, msgid, msgid_plural, n) # np_(msgctxt, msgids, n) # msgids = [msgid, msgid_plural] # @@ -353,33 +204,36 @@ # * 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 npgettext(msgctxt, arg1, arg2 = nil, arg3 = nil) - if arg1.kind_of?(Array) - msgid = arg1[0] + def npgettext(msgctxt, msgids, arg2 = nil, arg3 = nil) + if msgids.kind_of?(Array) + msgid = msgids[0] msgid_ctxt = "#{msgctxt}\004#{msgid}" - msgid_plural = arg1[1] + msgid_plural = msgids[1] opt1 = arg2 opt2 = arg3 else - msgid = arg1 + msgid = msgids msgid_ctxt = "#{msgctxt}\004#{msgid}" msgid_plural = arg2 opt1 = arg3 opt2 = nil end - ret = nsgettext(msgid_ctxt, msgid_plural, opt1, opt2) - if ret == msgid_ctxt - ret = msgid + msgstr = TextDomainManager.get(self).translate_plural_message(self, msgid_ctxt, msgid_plural, opt1, opt2) + if msgstr == msgid_ctxt + msgid + else + msgstr end - ret end - - # This function does nothing. But it is required in order to recognize the msgid by rgettext. + + # makes dynamic translation messages readable for the gettext parser. + # <tt>_(fruit)</tt> cannot be understood by the gettext parser. To help the parser find all your translations, + # you can add <tt>fruit = N_("Apple")</tt> which does not translate, but tells the parser: "Apple" needs translation. # * msgid: the message id. # * Returns: msgid. def N_(msgid) msgid end @@ -390,232 +244,48 @@ # * Returns: msgid. def Nn_(msgid, msgid_plural) [msgid, msgid_plural] end - - # call-seq: - # nsgettext(msgid, msgid_plural, n, div = "|") - # nsgettext(msgids, n, div = "|") # msgids = [msgid, msgid_plural] - # n_(msgid, msgid_plural, n, div = "|") - # n_(msgids, n, div = "|") # msgids = [msgid, msgid_plural] - # - # The nsgettext is similar to the ngettext. - # But if there are no localized text, - # it returns a last part of msgid separeted "div". - # - # * msgid: the singular form with "div". (e.g. "Special|An apple") - # * msgid_plural: the plural form. (e.g. "%{num} Apples") - # * 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 nsgettext(arg1, arg2, arg3 = "|", arg4 = "|") - if arg1.kind_of?(Array) - msgid = arg1[0] - msgid_plural = arg1[1] - n = arg2 - if arg3 and arg3.kind_of? Numeric - raise ArgumentError, _("3rd parmeter is wrong: value = %{number}") % {:number => arg3} - end - div = arg3 - else - msgid = arg1 - msgid_plural = arg2 - n = arg3 - div = arg4 - end - - cached_key = [bound_target, Locale.current, msgid + "\000" + msgid_plural] - msgs = nil - if @@__cached - if @@__cache_nmsgids.has_key?(cached_key) - msgs = @@__cache_nmsgids[cached_key] # [msgstr, cond_as_string] - end - end - unless msgs - # Use "for"(not "each") to support JRuby 1.1.0. - for target in bound_targets(self) - manager = @@__textdomainmanagers[target] - for textdomain in manager.textdomains - msgs = textdomain[1].ngettext_data(msgid, msgid_plural) - break if msgs - end - break if msgs - end - msgs = [[msgid, msgid_plural], "n != 1"] unless msgs - @@__cache_nmsgids[cached_key] = msgs - end - msgstrs = msgs[0] - if div and msgstrs[0] == msgid - if index = msgstrs[0].rindex(div) - msgstrs[0] = msgstrs[0][(index + 1)..-1] - end - end - plural = eval(msgs[1]) - if plural.kind_of?(Numeric) - ret = msgstrs[plural] - else - ret = plural ? msgstrs[1] : msgstrs[0] - end - ret - end - - # Sets the current 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 = TextDomainManager.output_charset if TextDomainManager.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, ! cached?) - end - else - each_textdomain {|textdomain| - textdomain.set_locale(ret, ! cached?) - } - end - self - end - - # Sets current locale to the all textdomains. - # - # Note 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 - else - ret = Locale.default - end - ret.charset = TextDomainManager.output_charset if TextDomainManager.output_charset - Locale.set_current(ret) - TextDomainManager.each_all {|textdomain| - textdomain.set_locale(ret, ! cached?) - } - self - end - - # Sets the default/current locale. This method haves the strongest infulence. - # All of the Textdomains are set the new locale. - # - # Note that you shouldn't use this for your own Libraries. - # * locale: a locale string or Locale::Object - # * Returns: a locale string - def locale=(locale) - Locale.default = locale - set_locale_all(locale) - Locale.default - 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 + # * Returns: self 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 || locale.charset + TextDomainManager.output_charset end - # Gets the current locale. - # * Returns: a current Locale::Object - def locale - Locale.current + def set_locale(lang) + Locale.clear + Locale.current = lang 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) + def locale + Locale.current[0] 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. - 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 - - # for testing. - def remove_all_textdomains - clear_cache - @@__textdomainmanagers = {} - end - - alias :setlocale :locale= #:nodoc: + alias :locale= :set_locale #:nodoc: alias :_ :gettext #:nodoc: alias :n_ :ngettext #:nodoc: alias :s_ :sgettext #:nodoc: alias :ns_ :nsgettext #:nodoc: alias :np_ :npgettext #:nodoc: -unless defined? "XX" + alias :output_charset= :set_output_charset #:nodoc: + +unless defined? XX # This is the workaround to conflict p_ methods with the xx("double x") library. # http://rubyforge.org/projects/codeforpeople/ alias :p_ :pgettext #:nodoc: - module_function :p_ end - module_function :bindtextdomain, :textdomain, :each_textdomain, :cached=, :cached?, :clear_cache, - :N_, :gettext, :_, :ngettext, :n_, :sgettext, :s_, :nsgettext, :ns_, :pgettext, :npgettext, :np_, - :bound_target, :bound_targets, :find_targets, - :setlocale, :set_locale, :locale=, :set_locale_all, :locale, - :set_output_charset, :output_charset=, :output_charset, :current_textdomain_info, :remove_all_textdomains + # for backward compatibility + alias :set_locale_all :set_locale #:nodoc: + alias :setlocale :set_locale #:nodoc: end