Module: Air18n::SmartCount

Defined in:
lib/air18n/smart_count.rb

Constant Summary collapse

INTERPOLATION_VARIABLE_NAME =

Option to translate() that holds number which controls which plural form is used.

:smart_count
DELIMITER =

Delimiter between forms of translation.

This delimiter must be unique and rare because we assume that any phrase that includes it uses smart counting.

'||||'
PLURAL_TYPES =
{
  :chinese_like => {
    :num_forms => 1,
    :doc => nil,
    :rule => lambda { |n| 0 } },

  :german_like => {
    :num_forms => 2,
    :doc => ['When count is 1', 'Everything else (0, 2, 3, ...)'],
    :rule => lambda { |n| n != 1 ? 1 : 0 } },

  :french_like => {
    :num_forms => 2,
    :doc => ['When count is 0 or 1', 'Everything else (2, 3, 4, ...)'],
    :rule => lambda { |n| n > 1 ? 1 : 0 } },

  :russian_like => {
    :num_forms => 3,
    :doc => ['When count ends in 1, excluding 11 (1, 21, 31, ...)', 'When count ends in 2-4, excluding 12-14 (2, 3, 4, 22, ...)', 'Everything else (0, 5, 6, ...)'],
    :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2 } },

  :czech_like => {
    :num_forms => 3,
    :doc => ['When count is 1', 'When count is 2, 3, 4', 'Everything else (0, 5, 6, ...)'],
    :rule => lambda { |n| (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2 } },

  :polish_like => {
    :num_forms => 3,
    :doc => ['When count is 1', 'When count ends in 2-4, excluding 12-14 (2, 3, 4, 22, ...)', 'Everything else (0, 5, 6, ...)'],
    :rule => lambda { |n| (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2) } },

  :icelandic_like => {
    :num_forms => 2,
    :doc => ['When count ends in 1, excluding 11 (1, 21, 31, ...)', 'Everything else (0, 2, 3, ...)'],
    :rule => lambda { |n| (n % 10 != 1 || n % 100 == 11) ? 1 : 0 } },
}
PLURAL_TYPE_NAME_TO_LANGUAGES =
{
  :chinese_like => [:id, :ja, :ko, :ms, :th, :tr, :zh, ],
  :german_like => [:da, :de, :en, :es, :fi, :el, :he, :hu, :it, :nl, :no, :pt, :sv, ],
  :french_like => [:fr, :tl, ],
  :russian_like => [:hr, :ru, ],
  :czech_like => [:cs],
  :polish_like => [:pl],
  :icelandic_like => [:is],
}
LANGUAGE_TO_PLURAL_TYPE_NAME =
PLURAL_TYPE_NAME_TO_LANGUAGES.inject({}) do |carry, (type, languages)|
  languages.each { |l| carry[l] = type }
  carry
end

Class Method Summary collapse

Class Method Details

.applies?(text) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
# File 'lib/air18n/smart_count.rb', line 137

def applies?(text)
  !!text.index(DELIMITER)
end

.choose(text, locale, count) ⇒ Object

Chooses the right version of ‘text’ for the value of the ‘smart_count’ in ‘locale’.

Examples:

choose("%{smart_count} bewertung |||| %{smart_count} bewertungen",
       :de, 0)
  => "%{smart_count} bewertungen"
choose("%{smart_count} bewertung |||| %{smart_count} bewertungen",
       :de, 1)
  => "%{smart_count} bewertung"
choose("%{smart_count} bewertung |||| %{smart_count} bewertungen",
       :de, 2)
  => "%{smart_count} bewertungen"
choose("%{smart_count} bewertung |||| %{smart_count} bewertungen",
       :de, nil)
  => "%{smart_count} bewertung |||| %{smart_count} bewertungen"

If ‘count’ is nil or ‘text’ contains too few alternative versions, this method degrades gracefully by returning the first version of text.

Returns if nil if text is blank.



105
106
107
108
109
110
111
112
113
# File 'lib/air18n/smart_count.rb', line 105

def choose(text, locale, count)
  if count.present? && text.present?
    texts = text.split(DELIMITER)
    chosen_text = texts[plural_type_index(locale, count)] || texts.first
    chosen_text.strip
  else
    text
  end
end

.dedupe_things_like_tags_or_variables(locale, things) ⇒ Object

Takes a list of things (for example tags or variables), counts up how many of each thing there are, then returns a new list that has each thing but with a possibly different count.

The count of each thing in the returned list is determined by:

  • If the count is a multiple of the number of plural forms for ‘locale’,

count is divided by the number of plural forms.

  • Otherwise count is set to 0.

The returned list is reordered so that all instances of the same thing are consecutive, and are ordered in the same order as the first of each thing in the original list.

This method is useful for ensuring that HTML tags or interpolation variables match in smart-count phrases between languages that might have different numbers of plural forms.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/air18n/smart_count.rb', line 161

def dedupe_things_like_tags_or_variables(locale, things)
  return things if things.blank?

  num_forms = num_forms(locale)
  grouped_things = things.group_by { |t| t }
  deduped = grouped_things.inject([]) do |carry, (thing, list_of_things)|
    count_of_thing = list_of_things.size
    if count_of_thing % num_forms == 0
      deduped_count_of_thing = count_of_thing / num_forms
    else
      deduped_count_of_thing = 0
    end
    deduped_count_of_thing.times { carry << thing }
    carry
  end
end

.hint(source_text, target_locale) ⇒ Object

Returns a hint for translators translating source_text to target_locale, or nil if there is no smart-count-related hint.



126
127
128
129
130
131
132
133
134
135
# File 'lib/air18n/smart_count.rb', line 126

def hint(source_text, target_locale)
  if applies?(source_text)
    doc_array = PLURAL_TYPES[plural_type_name(target_locale)][:doc]
    if doc_array
      doc_string = doc_array.join(' ' + DELIMITER + ' ')
      n = num_forms(target_locale)
      "Must specify #{n} forms separated by \"#{DELIMITER}\": \"#{doc_string}\"" 
    end
  end
end

.num_forms(locale) ⇒ Object



141
142
143
# File 'lib/air18n/smart_count.rb', line 141

def num_forms(locale)
  PLURAL_TYPES[plural_type_name(locale)][:num_forms]
end

.plural_type_index(locale, count) ⇒ Object



120
121
122
# File 'lib/air18n/smart_count.rb', line 120

def plural_type_index(locale, count)
  PLURAL_TYPES[plural_type_name(locale)][:rule].call(count)
end

.plural_type_name(locale) ⇒ Object



115
116
117
118
# File 'lib/air18n/smart_count.rb', line 115

def plural_type_name(locale)
  LANGUAGE_TO_PLURAL_TYPE_NAME[I18n.language_from_locale(locale)] ||
    LANGUAGE_TO_PLURAL_TYPE_NAME[I18n.default_language]
end

.valid?(source_text, target_text, source_locale, target_locale) ⇒ Boolean

Returns:

  • (Boolean)


178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/air18n/smart_count.rb', line 178

def valid?(source_text, target_text, source_locale, target_locale)
  if applies?(source_text) || applies?(target_text)
    size = target_text.split(DELIMITER).size
    num_forms = num_forms(target_locale)
    if size == num_forms
      { :valid => true }
    else
      { :valid => false,
        :reason => "Must specify #{num_forms} forms separated by \"#{DELIMITER}\" (#{size} #{size == 1? 'was' : 'were'} specified)" }
    end
  else
    { :valid => true }
  end
end