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
|