slang 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.md +20 -0
- data/README.md +242 -0
- data/Rakefile +12 -0
- data/lib/slang.rb +144 -0
- data/lib/slang/internal.rb +139 -0
- data/lib/slang/railtie.rb +16 -0
- data/lib/slang/snapshot.rb +131 -0
- data/lib/slang/snapshot/locale.rb +35 -0
- data/lib/slang/snapshot/rules.rb +239 -0
- data/lib/slang/snapshot/template.rb +58 -0
- data/lib/slang/snapshot/translation.rb +135 -0
- data/lib/slang/snapshot/warnings.rb +102 -0
- data/lib/slang/updater/abstract.rb +74 -0
- data/lib/slang/updater/development.rb +88 -0
- data/lib/slang/updater/http_helpers.rb +49 -0
- data/lib/slang/updater/key_reporter.rb +92 -0
- data/lib/slang/updater/production.rb +218 -0
- data/lib/slang/updater/shared_state.rb +59 -0
- data/lib/slang/updater/squelchable.rb +45 -0
- data/lib/slang/version.rb +3 -0
- data/slang.gemspec +20 -0
- data/test/data/snapshot.json +64 -0
- data/test/helper.rb +4 -0
- data/test/test_locale.rb +47 -0
- data/test/test_rules.rb +133 -0
- data/test/test_snapshot.rb +132 -0
- data/test/test_template.rb +49 -0
- data/test/test_translation.rb +94 -0
- data/test/test_warnings.rb +123 -0
- metadata +99 -0
| @@ -0,0 +1,139 @@ | |
| 1 | 
            +
            require "bundler/setup"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "logger"
         | 
| 4 | 
            +
            require "set"
         | 
| 5 | 
            +
            require "time"
         | 
| 6 | 
            +
            require "uri"
         | 
| 7 | 
            +
            require "zlib"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require "oj"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require "slang/snapshot"
         | 
| 12 | 
            +
            require "slang/snapshot/locale"
         | 
| 13 | 
            +
            require "slang/snapshot/template"
         | 
| 14 | 
            +
            require "slang/snapshot/translation"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            require "slang/updater/development"
         | 
| 17 | 
            +
            require "slang/updater/key_reporter"
         | 
| 18 | 
            +
            require "slang/updater/production"
         | 
| 19 | 
            +
            require "slang/updater/shared_state"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            require "slang/version"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            module Slang
         | 
| 24 | 
            +
              USER_AGENT = "slang-rb/#{VERSION}"
         | 
| 25 | 
            +
              KEY_REGEX = /\A[0-9a-zA-Z_\-]{20}\z/
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              class ConfigurationError < SlangError; end
         | 
| 28 | 
            +
              class SnapshotError      < SlangError; end
         | 
| 29 | 
            +
              class NetworkError       < SlangError
         | 
| 30 | 
            +
                attr_reader :retry_in
         | 
| 31 | 
            +
                def initialize(retry_in=nil)
         | 
| 32 | 
            +
                  @retry_in = retry_in
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def self.internal_translate(key, variable_hash)
         | 
| 37 | 
            +
                snapshot.translate(locale_code, key, variable_hash)
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def self.snapshot
         | 
| 41 | 
            +
                raise SlangError, "not initialized." unless @initialized
         | 
| 42 | 
            +
                @updater.snapshot
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def self.key_reporter
         | 
| 46 | 
            +
                @key_reporter
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def self.env
         | 
| 50 | 
            +
                @env
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def self.log_error(message)
         | 
| 54 | 
            +
                if @logger
         | 
| 55 | 
            +
                  @logger.error("[SLANG] #{message}")
         | 
| 56 | 
            +
                elsif !defined?(Slang::TEST)
         | 
| 57 | 
            +
                  warn(message)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def self.log_warn(message)
         | 
| 62 | 
            +
                if @logger
         | 
| 63 | 
            +
                  @logger.warn("[SLANG] #{message}")
         | 
| 64 | 
            +
                elsif !defined?(Slang::TEST)
         | 
| 65 | 
            +
                  warn(message)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              def self.log_info(message)
         | 
| 70 | 
            +
                if @logger
         | 
| 71 | 
            +
                  @logger.info("[SLANG] #{message}")
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              def self.log_debug(message)
         | 
| 76 | 
            +
                if @logger
         | 
| 77 | 
            +
                  @logger.debug("[SLANG] #{message}")
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              def self.env_var(var)
         | 
| 82 | 
            +
                value = ENV[var]
         | 
| 83 | 
            +
                value if value && !value.empty?
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              def self.init(config={})
         | 
| 87 | 
            +
                return false if @initialized
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                api_base          = config[:api_base] ? config[:api_base].chomp("/") : nil
         | 
| 90 | 
            +
                data_dir          = config[:data_dir] || env_var("SLANG_DATA_DIR") || File.join(defined?(Rails) ? Rails.root : Dir.pwd, "slang_data")
         | 
| 91 | 
            +
                developer_key     = config[:developer_key] || env_var("SLANG_DEVELOPER_KEY")
         | 
| 92 | 
            +
                embedded_snapshot = config[:embedded_snapshot] || env_var("SLANG_EMBEDDED_SNAPSHOT")
         | 
| 93 | 
            +
                env               = config[:env] || env_var("SLANG_ENV") || (defined?(Rails) ? Rails.env : nil) || (config[:developer_key] ? "development" : "production")
         | 
| 94 | 
            +
                project_key       = config[:project_key] || env_var("SLANG_PROJECT_KEY")
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                @env = env
         | 
| 97 | 
            +
                dev_mode = @env =~ /\Adev/i
         | 
| 98 | 
            +
                @logger ||= defined?(Rails) ? Rails.logger : nil
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                Slang.log_info("Slang v#{Slang::VERSION} starting in #{dev_mode ? 'development' : 'production'} mode (#{env} environment)")
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                begin
         | 
| 103 | 
            +
                  FileUtils.mkdir_p(data_dir, mode: 0755)
         | 
| 104 | 
            +
                rescue
         | 
| 105 | 
            +
                  raise ConfigurationError, "unable to create data dir - #{data_dir}"
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
                raise ConfigurationError, "missing/malformed project key (project_key=#{project_key})" unless project_key =~ KEY_REGEX
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                if dev_mode
         | 
| 110 | 
            +
                  raise ConfigurationError, "missing/malformed developer key (developer_key=#{developer_key})" unless developer_key =~ KEY_REGEX
         | 
| 111 | 
            +
                  api_base ||= "https://dev.getslang.com"
         | 
| 112 | 
            +
                  @updater = Updater::Development.new(env, "#{api_base}/snapshot/#{project_key}", data_dir, embedded_snapshot)
         | 
| 113 | 
            +
                  @updater.dev_key = developer_key
         | 
| 114 | 
            +
                  @key_reporter = Updater::KeyReporter.new("#{api_base}/keys/#{project_key}", developer_key)
         | 
| 115 | 
            +
                else
         | 
| 116 | 
            +
                  api_base ||= "https://slang-a.akamaihd.net"
         | 
| 117 | 
            +
                  @updater = Updater::Production.new(env, "#{api_base}/m/#{project_key}", data_dir, embedded_snapshot)
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                Slang.log_info("Slang initialized. All systems go!")
         | 
| 121 | 
            +
                @initialized = true
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                if puma_preload?
         | 
| 124 | 
            +
                  Puma.cli_config.options[:before_worker_boot] << Proc.new { Slang.snapshot } # delay to worker start
         | 
| 125 | 
            +
                  # TODO: unicorn check
         | 
| 126 | 
            +
                else
         | 
| 127 | 
            +
                  Slang.snapshot # trigger background fetch immediately
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
                nil
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            private
         | 
| 133 | 
            +
             | 
| 134 | 
            +
              def self.puma_preload?
         | 
| 135 | 
            +
                defined?(Puma) && defined?(Puma.cli_config) && Puma.cli_config && Puma.cli_config.options[:preload_app] && (Puma.cli_config.options[:workers] > 0)
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            end
         | 
| 139 | 
            +
            require "slang/railtie" if defined?(Rails)
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module Slang
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class Railtie < Rails::Railtie
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                initializer "slang" do
         | 
| 6 | 
            +
                  ActiveSupport.on_load(:action_view) do
         | 
| 7 | 
            +
                    include Slang::Helpers
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
                  ActiveSupport.on_load(:action_controller) do
         | 
| 10 | 
            +
                    include Slang::Helpers
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,131 @@ | |
| 1 | 
            +
            require "slang/snapshot/warnings"
         | 
| 2 | 
            +
            module Slang
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # A snapshot is a point-in-time state of all known keys and their associated translations.
         | 
| 5 | 
            +
              #
         | 
| 6 | 
            +
              class Snapshot
         | 
| 7 | 
            +
                include Warnings
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_reader :id             # String
         | 
| 10 | 
            +
                attr_reader :timestamp      # Integer unix-time
         | 
| 11 | 
            +
                attr_reader :default_locale # Locale
         | 
| 12 | 
            +
                attr_reader :locales        # Hash of :locale_code => Locale
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                EMPTY_SNAPSHOT_JSON = '[6,"00000000000000000000",0,"en",[["en",null,"one_other",null]],[]]'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Create a snapshot from a serialized JSON string.
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                def self.from_json(json)
         | 
| 19 | 
            +
                  Snapshot.new(array_from_json(json))
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # Create a snapshot array from a serialized JSON string.
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                def self.array_from_json(json)
         | 
| 25 | 
            +
                  Oj.strict_load(json.force_encoding(Encoding::UTF_8))
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                # Create a Snapshot object.
         | 
| 29 | 
            +
                #
         | 
| 30 | 
            +
                # @param [Array]         deserialized snapshot array
         | 
| 31 | 
            +
                # @raise [SnapshotError] if a corrupt snapshot is detected
         | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                def initialize(array)
         | 
| 34 | 
            +
                  snapshot_format, @id, @timestamp, default_locale_code, locales_array, translations_array = *array.to_a
         | 
| 35 | 
            +
                  @locales = {}
         | 
| 36 | 
            +
                  locales_array.each_with_index do |locale_array, i|
         | 
| 37 | 
            +
                    locale = Locale.new(locale_array, i)
         | 
| 38 | 
            +
                    @locales[locale.code] = locale
         | 
| 39 | 
            +
                    locale.aliases.each { |a| @locales[a] = locale }
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                  @default_locale = @locales[default_locale_code.to_sym] || @locales.first[1]
         | 
| 42 | 
            +
                  raise SnapshotError, "default locale does not exist" unless @default_locale
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  @translations = {}
         | 
| 45 | 
            +
                  translations_array.each do |translation_array|
         | 
| 46 | 
            +
                    t = Translation.new(translation_array)
         | 
| 47 | 
            +
                    @translations[t.key] = t if t.rules
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                  super() # invoke Warnings#initialize
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
             | 
| 53 | 
            +
                # Fetch all the translation keys.
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # @return [Array<String>] known keys.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                def keys
         | 
| 58 | 
            +
                  @translations.keys
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Perform a translation for the given key/locale, with an optional variable map.
         | 
| 62 | 
            +
                #
         | 
| 63 | 
            +
                # @param [Symbol] locale code
         | 
| 64 | 
            +
                # @param [#to_s]  key
         | 
| 65 | 
            +
                # @param [Hash]   variable hash
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                # @return [String] always returns a String (typically a translation)
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                def translate(locale_code, key, vm)
         | 
| 70 | 
            +
                  key = key.to_s.downcase
         | 
| 71 | 
            +
                  variable_map = {}
         | 
| 72 | 
            +
                  vm.each { |k, v| variable_map[k.to_s.downcase.freeze] = v }
         | 
| 73 | 
            +
                  if Slang.key_reporter
         | 
| 74 | 
            +
                    raise ArgumentError, "Slang key name malformed: #{key}" unless key =~ Translation::KEY_REGEX
         | 
| 75 | 
            +
                    variable_map.each do |k, v|
         | 
| 76 | 
            +
                      raise ArgumentError, "Slang variable name malformed: #{k}" unless k =~ Template::VARIABLE_NAME_REGEX
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                  internal_translate(locale_code, key, variable_map)
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              private
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                # Internal translate method.
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @param [Symbol] locale code
         | 
| 87 | 
            +
                # @param [String] downcased string key
         | 
| 88 | 
            +
                # @param [Hash{String => #to_s}] variable map with all keys as downcased strings
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # @return [String] translation
         | 
| 91 | 
            +
                #
         | 
| 92 | 
            +
                def internal_translate(locale_code, key, variable_map)
         | 
| 93 | 
            +
                  locale = @locales[locale_code] || @default_locale
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  # lookup translation
         | 
| 96 | 
            +
                  translation = @translations[key]
         | 
| 97 | 
            +
                  result, string, missing_names, selector = translation.evaluate(locale, variable_map) if translation
         | 
| 98 | 
            +
                  case result
         | 
| 99 | 
            +
                  when :success
         | 
| 100 | 
            +
                    # all is good
         | 
| 101 | 
            +
                  when :missing_interpolation_variables
         | 
| 102 | 
            +
                    missing_interpolation_variables!(locale, key, selector, missing_names, variable_map)
         | 
| 103 | 
            +
                  when :missing_rule_variables
         | 
| 104 | 
            +
                    missing_rule_variables!(locale, key, missing_names, variable_map)
         | 
| 105 | 
            +
                  when :missing_rule_templates
         | 
| 106 | 
            +
                    missing_rule_templates!(locale, key, missing_names, variable_map)
         | 
| 107 | 
            +
                  else # :missing_template or nil
         | 
| 108 | 
            +
                    missing_key!(locale, key, variable_map)
         | 
| 109 | 
            +
                    fallback = internal_translate(locale.fallback, key, variable_map) if locale.fallback
         | 
| 110 | 
            +
                    return fallback if fallback
         | 
| 111 | 
            +
                    if Slang.key_reporter
         | 
| 112 | 
            +
                      Slang.log_info("reporting unknown key.. #{key}")
         | 
| 113 | 
            +
                      Slang.key_reporter.unknown_key(key, variable_map)
         | 
| 114 | 
            +
                      # development string placeholder
         | 
| 115 | 
            +
                      var_names = variable_map.keys.sort!
         | 
| 116 | 
            +
                      string = key.split(".").map! do |component|
         | 
| 117 | 
            +
                         component.split("_").map! { |c| c.capitalize }.join
         | 
| 118 | 
            +
                      end.join(" ")
         | 
| 119 | 
            +
                      unless var_names.empty?
         | 
| 120 | 
            +
                        string << " (" << var_names.map { |n| "#{n}=#{variable_map[n]}" }.join(", ") << ")"
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
                    else
         | 
| 123 | 
            +
                      # production string placeholder
         | 
| 124 | 
            +
                      string="{{#{locale.code}:#{key}}}"
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
                  string
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
              end # class
         | 
| 131 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Slang
         | 
| 2 | 
            +
              class Snapshot
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                # A Locale is an immutable object that holds a locale code (and optional aliases), pluralization rule, and fallback
         | 
| 5 | 
            +
                # locale.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                class Locale
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  attr_reader :code
         | 
| 10 | 
            +
                  attr_reader :fallback
         | 
| 11 | 
            +
                  attr_reader :aliases
         | 
| 12 | 
            +
                  attr_reader :pluralization_method
         | 
| 13 | 
            +
                  attr_reader :translation_index
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # Create a locale object from the snapshot locale array.
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # @param [Array]   snapshot locale array
         | 
| 18 | 
            +
                  # @param [Integer] index of this locale's translation in translation array
         | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  def initialize(locale_array, translation_index)
         | 
| 21 | 
            +
                    code, fallback, pluralization, _, *aliases = *locale_array
         | 
| 22 | 
            +
                    @code = code.to_sym
         | 
| 23 | 
            +
                    @fallback = fallback.to_sym if fallback
         | 
| 24 | 
            +
                    @pluralization_method = "pluralization_#{pluralization}".to_sym
         | 
| 25 | 
            +
                    unless Rules.method_defined?(@pluralization_method)
         | 
| 26 | 
            +
                      Slang.log_warn("Ignoring unknown pluralization rule '#{pluralization}' in locale '#{code}'.")
         | 
| 27 | 
            +
                      @pluralization_method = :pluralization_unknown
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                    @aliases = aliases.map(&:to_sym)
         | 
| 30 | 
            +
                    @translation_index = translation_index
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                end # class
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,239 @@ | |
| 1 | 
            +
            module Slang
         | 
| 2 | 
            +
              class Snapshot
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                # Rule-logic, mixed into the Translation class.
         | 
| 5 | 
            +
                #
         | 
| 6 | 
            +
                module Rules
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  RULE_TYPES = {
         | 
| 9 | 
            +
                    "N" => :pluralization,
         | 
| 10 | 
            +
                    "G" => :gender
         | 
| 11 | 
            +
                  }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # Generate rules array from a rule pattern.
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  # @param [String] rule pattern of the form "var1=N" or "var1=N:var2=G:..."
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # @return [Array<Array<(String,Symbol)>>,nil] rules array, nil on error
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  def rules_from_pattern(rule_pattern)
         | 
| 20 | 
            +
                    rule_pattern.to_s.split(":").map! do |rule|
         | 
| 21 | 
            +
                      variable_name, rule_code = rule.split("=")
         | 
| 22 | 
            +
                      rule_type = RULE_TYPES[rule_code]
         | 
| 23 | 
            +
                      return nil unless rule_type
         | 
| 24 | 
            +
                      [ variable_name, rule_type ]
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # Invokes the appropriate rule with the given value. This returns one or more template selectors (which may be
         | 
| 29 | 
            +
                  # combined with other rules) that will specify the appropriate template.
         | 
| 30 | 
            +
                  #
         | 
| 31 | 
            +
                  # @param [Symbol] rule-type
         | 
| 32 | 
            +
                  # @param [Object] rule argument
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @return [Array<String>] template selectors to try, in order
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  def evaluate_rule(locale, rule_type, value)
         | 
| 37 | 
            +
                    case rule_type
         | 
| 38 | 
            +
                    when :pluralization
         | 
| 39 | 
            +
                      send(locale.pluralization_method, value)
         | 
| 40 | 
            +
                    when :gender
         | 
| 41 | 
            +
                      gender(genderize(value))
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  ### Gender rule
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  GENDER_FEMALE = %w(female)
         | 
| 48 | 
            +
                  GENDER_MALE = %w(male)
         | 
| 49 | 
            +
                  GENDER_UNKNOWN = %w(unknown)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # Determine gender rule based on given value.
         | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # @param [Symbol] gender value should be :female, :male, or :unknown
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  # @return [Array(String)] an 1-element array of the gender.
         | 
| 56 | 
            +
                  #
         | 
| 57 | 
            +
                  def gender(value)
         | 
| 58 | 
            +
                    case value
         | 
| 59 | 
            +
                    when :female; GENDER_FEMALE
         | 
| 60 | 
            +
                    when :male;   GENDER_MALE
         | 
| 61 | 
            +
                    else          GENDER_UNKNOWN
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  # Attempts to convert value into :female, :male, or :unknown. Any value
         | 
| 66 | 
            +
                  # whose string form starts with "f" becomes :female, "m" becomes :male,
         | 
| 67 | 
            +
                  # otherwise :unknown.
         | 
| 68 | 
            +
                  #
         | 
| 69 | 
            +
                  # @return [Symbol] :female, :male, or :unknown
         | 
| 70 | 
            +
                  #
         | 
| 71 | 
            +
                  def genderize(value)
         | 
| 72 | 
            +
                    if (value == :female) || (value == :male)
         | 
| 73 | 
            +
                      value
         | 
| 74 | 
            +
                    elsif (value == :unknown) || value.nil?
         | 
| 75 | 
            +
                      :unknown
         | 
| 76 | 
            +
                    else
         | 
| 77 | 
            +
                      case value.to_s
         | 
| 78 | 
            +
                      when /\Af/i; :female
         | 
| 79 | 
            +
                      when /\Am/i; :male
         | 
| 80 | 
            +
                      else         :unknown
         | 
| 81 | 
            +
                      end
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  ### Pluralization rules
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  PLURAL_FEW = %w(few)
         | 
| 88 | 
            +
                  PLURAL_MANY = %w(many)
         | 
| 89 | 
            +
                  PLURAL_ONE = %w(one)
         | 
| 90 | 
            +
                  PLURAL_OTHER = %w(other)
         | 
| 91 | 
            +
                  PLURAL_TWO = %w(two)
         | 
| 92 | 
            +
                  PLURAL_ZERO = %w(zero)
         | 
| 93 | 
            +
                  PLURAL_ZERO_OR_ONE = %w(zero one)
         | 
| 94 | 
            +
                  PLURAL_ZERO_OR_MANY = %w(zero many)
         | 
| 95 | 
            +
                  PLURAL_ZERO_OR_OTHER = %w(zero other)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  def pluralization_unknown(n)
         | 
| 98 | 
            +
                    PLURAL_OTHER
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  # Pluralization rule for languages with non-countable nouns "other",
         | 
| 102 | 
            +
                  # optionally supports the "zero" case.
         | 
| 103 | 
            +
                  #
         | 
| 104 | 
            +
                  # Example languages: Chinese, Japanese, Korean.
         | 
| 105 | 
            +
                  #
         | 
| 106 | 
            +
                  # @param [Number] integer or fraction
         | 
| 107 | 
            +
                  #
         | 
| 108 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  def pluralization_other(n)
         | 
| 111 | 
            +
                    if n == 0
         | 
| 112 | 
            +
                      PLURAL_ZERO_OR_OTHER
         | 
| 113 | 
            +
                    else
         | 
| 114 | 
            +
                      PLURAL_OTHER
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  # Pluralization rule for languages with "one" or "other", optionally
         | 
| 119 | 
            +
                  # supports "zero" case.
         | 
| 120 | 
            +
                  #
         | 
| 121 | 
            +
                  # Example languages: English, German, Italian, Portugeuse, Spanish.
         | 
| 122 | 
            +
                  #
         | 
| 123 | 
            +
                  # @param [Number] integer or fraction
         | 
| 124 | 
            +
                  #
         | 
| 125 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 126 | 
            +
                  #
         | 
| 127 | 
            +
                  def pluralization_one_other(n)
         | 
| 128 | 
            +
                    if n == 0
         | 
| 129 | 
            +
                      PLURAL_ZERO_OR_OTHER
         | 
| 130 | 
            +
                    elsif n == 1
         | 
| 131 | 
            +
                      PLURAL_ONE
         | 
| 132 | 
            +
                    else
         | 
| 133 | 
            +
                      PLURAL_OTHER
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  # Pluralization rule for languages with "one" for zero/one, or "other",
         | 
| 138 | 
            +
                  # optionally supports "zero" case.
         | 
| 139 | 
            +
                  #
         | 
| 140 | 
            +
                  # Example languages: French
         | 
| 141 | 
            +
                  #
         | 
| 142 | 
            +
                  # @param [Number] integer or fraction
         | 
| 143 | 
            +
                  #
         | 
| 144 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 145 | 
            +
                  #
         | 
| 146 | 
            +
                  def pluralization_zero_and_one_other(n)
         | 
| 147 | 
            +
                    if n == 0
         | 
| 148 | 
            +
                      PLURAL_ZERO_OR_ONE
         | 
| 149 | 
            +
                    elsif n == 1
         | 
| 150 | 
            +
                      PLURAL_ONE
         | 
| 151 | 
            +
                    else
         | 
| 152 | 
            +
                      PLURAL_OTHER
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  # Pluralization rule for east slavic languages with "one", "few", "many",
         | 
| 157 | 
            +
                  # "other", optionally supports "zero" case.
         | 
| 158 | 
            +
                  #
         | 
| 159 | 
            +
                  # Example languages: RUssian
         | 
| 160 | 
            +
                  #
         | 
| 161 | 
            +
                  # @param [Number] integer or fraction
         | 
| 162 | 
            +
                  #
         | 
| 163 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 164 | 
            +
                  #
         | 
| 165 | 
            +
                  def pluralization_east_slavic(n)
         | 
| 166 | 
            +
                    if n == 0
         | 
| 167 | 
            +
                      PLURAL_ZERO_OR_MANY
         | 
| 168 | 
            +
                    else
         | 
| 169 | 
            +
                      mod10 = n % 10
         | 
| 170 | 
            +
                      mod100 = n % 100
         | 
| 171 | 
            +
                      if (mod10 == 1) && (mod100 != 11)
         | 
| 172 | 
            +
                        PLURAL_ONE
         | 
| 173 | 
            +
                      elsif ((mod10 >= 2) && (mod10 <= 4)) && !((mod100 >= 12) && (mod100 <= 14))
         | 
| 174 | 
            +
                        PLURAL_FEW
         | 
| 175 | 
            +
                      elsif (mod10 == 0) || ((mod10 >= 5) && (mod10 <= 9)) || ((mod100 >= 11) && (mod100 <= 14))
         | 
| 176 | 
            +
                        PLURAL_MANY
         | 
| 177 | 
            +
                      else
         | 
| 178 | 
            +
                        PLURAL_OTHER
         | 
| 179 | 
            +
                      end
         | 
| 180 | 
            +
                    end
         | 
| 181 | 
            +
                  end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  # Pluralization rule for Arabic with "zero", "one", "two", "few", "many",
         | 
| 184 | 
            +
                  # and "other". Note "zero" case is required.
         | 
| 185 | 
            +
                  #
         | 
| 186 | 
            +
                  # Example languages: Arabic.
         | 
| 187 | 
            +
                  #
         | 
| 188 | 
            +
                  # @param [Number] integer or fraction
         | 
| 189 | 
            +
                  #
         | 
| 190 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 191 | 
            +
                  #
         | 
| 192 | 
            +
                  def pluralization_arabic(n)
         | 
| 193 | 
            +
                    if n == 0
         | 
| 194 | 
            +
                      PLURAL_ZERO
         | 
| 195 | 
            +
                    elsif n == 1
         | 
| 196 | 
            +
                      PLURAL_ONE
         | 
| 197 | 
            +
                    elsif n == 2
         | 
| 198 | 
            +
                      PLURAL_TWO
         | 
| 199 | 
            +
                    else
         | 
| 200 | 
            +
                      mod100 = n % 100
         | 
| 201 | 
            +
                      if (mod100 >= 3) && (mod100 <= 10)
         | 
| 202 | 
            +
                        PLURAL_FEW
         | 
| 203 | 
            +
                      elsif (mod100 >= 11)
         | 
| 204 | 
            +
                        PLURAL_MANY
         | 
| 205 | 
            +
                      else
         | 
| 206 | 
            +
                        PLURAL_OTHER
         | 
| 207 | 
            +
                      end
         | 
| 208 | 
            +
                    end
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  # Pluralization rule for Polish with "one", "few", "many", and "other", optionally supports "zero" case.
         | 
| 212 | 
            +
                  #
         | 
| 213 | 
            +
                  # @param [Number] integer or fraction
         | 
| 214 | 
            +
                  #
         | 
| 215 | 
            +
                  # @return [Array<String>] template selector(s)
         | 
| 216 | 
            +
                  #
         | 
| 217 | 
            +
                  def pluralization_polish(n)
         | 
| 218 | 
            +
                    if Float === n
         | 
| 219 | 
            +
                      PLURAL_OTHER
         | 
| 220 | 
            +
                    elsif n == 0
         | 
| 221 | 
            +
                      PLURAL_ZERO_OR_MANY
         | 
| 222 | 
            +
                    elsif n == 1
         | 
| 223 | 
            +
                      PLURAL_ONE
         | 
| 224 | 
            +
                    else
         | 
| 225 | 
            +
                      mod10 = n % 10
         | 
| 226 | 
            +
                      mod100 = n % 100
         | 
| 227 | 
            +
                      if ((mod10 >= 2) && (mod10 <= 4)) && !((mod100 >= 12) && (mod100 <= 14))
         | 
| 228 | 
            +
                        PLURAL_FEW
         | 
| 229 | 
            +
                      elsif ((mod10 == 0) || (mod10 == 1)) || ((mod10 >= 5) && (mod10 <= 9)) || ((mod100 >= 12) && (mod100 <= 14))
         | 
| 230 | 
            +
                        PLURAL_MANY
         | 
| 231 | 
            +
                      else
         | 
| 232 | 
            +
                        PLURAL_OTHER
         | 
| 233 | 
            +
                      end
         | 
| 234 | 
            +
                    end
         | 
| 235 | 
            +
                  end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                end # module
         | 
| 238 | 
            +
              end
         | 
| 239 | 
            +
            end
         |