001/*
002 * Copyright 2017-2022 Product Mog LLC, 2022-2025 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.lokalized;
018
019import com.lokalized.Maps.MapEntry;
020
021import javax.annotation.Nonnull;
022import javax.annotation.Nullable;
023import java.math.BigDecimal;
024import java.math.BigInteger;
025import java.math.RoundingMode;
026import java.text.Collator;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.Locale;
031import java.util.Map;
032import java.util.Optional;
033import java.util.SortedMap;
034import java.util.SortedSet;
035import java.util.TreeSet;
036import java.util.function.Function;
037import java.util.stream.Collectors;
038
039import static com.lokalized.NumberUtils.equal;
040import static com.lokalized.NumberUtils.inRange;
041import static com.lokalized.NumberUtils.inSet;
042import static com.lokalized.NumberUtils.notEqual;
043import static com.lokalized.NumberUtils.notInRange;
044import static com.lokalized.NumberUtils.notInSet;
045import static java.lang.String.format;
046import static java.util.Objects.requireNonNull;
047
048/**
049 * Language plural cardinality forms.
050 * <p>
051 * For example, English has two: {@code 1 dog, 2 dogs}, while Welsh has many: {@code 0 cŵn, 1 ci, 2 gi, 3 chi, 4 ci}.
052 * <p>
053 * See the <a href="http://cldr.unicode.org/index/cldr-spec/plural-rules">Unicode Common Locale Data Repository</a>
054 * and its <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">Language Plural Rules</a> for details.
055 * <p>
056 * Per the CLDR:
057 * <blockquote>
058 * These categories are only mnemonics -- the names don't necessarily imply the exact contents of the category.
059 * For example, for both English and French the number 1 has the category one (singular).
060 * <p>
061 * In English, every other number has a plural form, and is given the category other.
062 * French is similar, except that the number 0 also has the category one and not other or zero, because the form of
063 * units qualified by 0 is also singular.
064 * <p>
065 * This is worth emphasizing: A common mistake is to think that "one" is only for only the number 1.
066 * Instead, "one" is a category for any number that behaves like 1. So in some languages, for example,
067 * one → numbers that end in "1" (like 1, 21, 151) but that don't end in 11 (like "11, 111, 10311).
068 * </blockquote>
069 *
070 * @author <a href="https://revetkn.com">Mark Allen</a>
071 */
072public enum Cardinality implements LanguageForm {
073  /**
074   * Normally the form used with 0, if it is limited to numbers whose integer values end with 0.
075   * <p>
076   * For example: the Welsh {@code 0 cŵn, 0 cathod} means {@code 0 dogs, 0 cats} in English.
077   */
078  ZERO,
079  /**
080   * The form used with 1.
081   * <p>
082   * For example: the Welsh {@code 1 ci, 1 gath} means {@code 1 dog, 1 cat} in English.
083   */
084  ONE,
085  /**
086   * Normally the form used with 2, if it is limited to numbers whose integer values end with 2.
087   * <p>
088   * For example: the Welsh {@code 2 gi, 2 gath} means {@code 2 dogs, 2 cats} in English.
089   */
090  TWO,
091  /**
092   * The form that falls between {@code TWO} and {@code MANY}.
093   * <p>
094   * For example: the Welsh {@code  3 chi, 3 cath} means {@code 3 dogs, 3 cats} in English.
095   */
096  FEW,
097  /**
098   * The form that falls between {@code FEW} and {@code OTHER}.
099   * <p>
100   * For example: the Welsh {@code 6 chi, 6 chath} means {@code 6 dogs, 6 cats} in English.
101   */
102  MANY,
103  /**
104   * General "catchall" form which comprises any cases not handled by the other forms.
105   * <p>
106   * For example: the Welsh {@code 4 ci, 4 cath} means {@code 4 dogs, 4 cats} in English.
107   */
108  OTHER;
109
110  @Nonnull
111  private static final BigInteger BIG_INTEGER_0;
112  @Nonnull
113  private static final BigInteger BIG_INTEGER_1;
114  @Nonnull
115  private static final BigInteger BIG_INTEGER_2;
116  @Nonnull
117  private static final BigInteger BIG_INTEGER_3;
118  @Nonnull
119  private static final BigInteger BIG_INTEGER_4;
120  @Nonnull
121  private static final BigInteger BIG_INTEGER_5;
122  @Nonnull
123  private static final BigInteger BIG_INTEGER_6;
124  @Nonnull
125  private static final BigInteger BIG_INTEGER_9;
126  @Nonnull
127  private static final BigInteger BIG_INTEGER_10;
128  @Nonnull
129  private static final BigInteger BIG_INTEGER_11;
130  @Nonnull
131  private static final BigInteger BIG_INTEGER_12;
132  @Nonnull
133  private static final BigInteger BIG_INTEGER_14;
134  @Nonnull
135  private static final BigInteger BIG_INTEGER_19;
136  @Nonnull
137  private static final BigInteger BIG_INTEGER_20;
138  @Nonnull
139  private static final BigInteger BIG_INTEGER_40;
140  @Nonnull
141  private static final BigInteger BIG_INTEGER_60;
142  @Nonnull
143  private static final BigInteger BIG_INTEGER_80;
144  @Nonnull
145  private static final BigInteger BIG_INTEGER_100;
146
147  @Nonnull
148  private static final BigDecimal BIG_DECIMAL_0;
149  @Nonnull
150  private static final BigDecimal BIG_DECIMAL_1;
151  @Nonnull
152  private static final BigDecimal BIG_DECIMAL_2;
153  @Nonnull
154  private static final BigDecimal BIG_DECIMAL_3;
155  @Nonnull
156  private static final BigDecimal BIG_DECIMAL_4;
157  @Nonnull
158  private static final BigDecimal BIG_DECIMAL_5;
159  @Nonnull
160  private static final BigDecimal BIG_DECIMAL_6;
161  @Nonnull
162  private static final BigDecimal BIG_DECIMAL_7;
163  @Nonnull
164  private static final BigDecimal BIG_DECIMAL_9;
165  @Nonnull
166  private static final BigDecimal BIG_DECIMAL_10;
167  @Nonnull
168  private static final BigDecimal BIG_DECIMAL_11;
169  @Nonnull
170  private static final BigDecimal BIG_DECIMAL_12;
171  @Nonnull
172  private static final BigDecimal BIG_DECIMAL_13;
173  @Nonnull
174  private static final BigDecimal BIG_DECIMAL_14;
175  @Nonnull
176  private static final BigDecimal BIG_DECIMAL_19;
177  @Nonnull
178  private static final BigDecimal BIG_DECIMAL_70;
179  @Nonnull
180  private static final BigDecimal BIG_DECIMAL_71;
181  @Nonnull
182  private static final BigDecimal BIG_DECIMAL_72;
183  @Nonnull
184  private static final BigDecimal BIG_DECIMAL_79;
185  @Nonnull
186  private static final BigDecimal BIG_DECIMAL_90;
187  @Nonnull
188  private static final BigDecimal BIG_DECIMAL_91;
189  @Nonnull
190  private static final BigDecimal BIG_DECIMAL_92;
191  @Nonnull
192  private static final BigDecimal BIG_DECIMAL_99;
193  @Nonnull
194  private static final BigDecimal BIG_DECIMAL_100;
195  @Nonnull
196  private static final BigDecimal BIG_DECIMAL_1_000_000;
197
198  @Nonnull
199  static final Map<String, Cardinality> CARDINALITIES_BY_NAME;
200
201  static {
202    BIG_INTEGER_0 = BigInteger.ZERO;
203    BIG_INTEGER_1 = BigInteger.ONE;
204    BIG_INTEGER_2 = BigInteger.valueOf(2);
205    BIG_INTEGER_3 = BigInteger.valueOf(3);
206    BIG_INTEGER_4 = BigInteger.valueOf(4);
207    BIG_INTEGER_5 = BigInteger.valueOf(5);
208    BIG_INTEGER_6 = BigInteger.valueOf(6);
209    BIG_INTEGER_9 = BigInteger.valueOf(9);
210    BIG_INTEGER_10 = BigInteger.TEN;
211    BIG_INTEGER_11 = BigInteger.valueOf(11);
212    BIG_INTEGER_12 = BigInteger.valueOf(12);
213    BIG_INTEGER_14 = BigInteger.valueOf(14);
214    BIG_INTEGER_19 = BigInteger.valueOf(19);
215    BIG_INTEGER_20 = BigInteger.valueOf(20);
216    BIG_INTEGER_40 = BigInteger.valueOf(40);
217    BIG_INTEGER_60 = BigInteger.valueOf(60);
218    BIG_INTEGER_80 = BigInteger.valueOf(80);
219    BIG_INTEGER_100 = BigInteger.valueOf(100);
220
221    BIG_DECIMAL_0 = BigDecimal.ZERO;
222    BIG_DECIMAL_1 = BigDecimal.ONE;
223    BIG_DECIMAL_2 = BigDecimal.valueOf(2);
224    BIG_DECIMAL_3 = BigDecimal.valueOf(3);
225    BIG_DECIMAL_4 = BigDecimal.valueOf(4);
226    BIG_DECIMAL_5 = BigDecimal.valueOf(5);
227    BIG_DECIMAL_6 = BigDecimal.valueOf(6);
228    BIG_DECIMAL_7 = BigDecimal.valueOf(7);
229    BIG_DECIMAL_9 = BigDecimal.valueOf(9);
230    BIG_DECIMAL_10 = BigDecimal.TEN;
231    BIG_DECIMAL_11 = BigDecimal.valueOf(11);
232    BIG_DECIMAL_12 = BigDecimal.valueOf(12);
233    BIG_DECIMAL_13 = BigDecimal.valueOf(13);
234    BIG_DECIMAL_14 = BigDecimal.valueOf(14);
235    BIG_DECIMAL_19 = BigDecimal.valueOf(19);
236    BIG_DECIMAL_70 = BigDecimal.valueOf(70);
237    BIG_DECIMAL_71 = BigDecimal.valueOf(71);
238    BIG_DECIMAL_72 = BigDecimal.valueOf(72);
239    BIG_DECIMAL_79 = BigDecimal.valueOf(79);
240    BIG_DECIMAL_90 = BigDecimal.valueOf(90);
241    BIG_DECIMAL_91 = BigDecimal.valueOf(91);
242    BIG_DECIMAL_92 = BigDecimal.valueOf(92);
243    BIG_DECIMAL_99 = BigDecimal.valueOf(99);
244    BIG_DECIMAL_100 = BigDecimal.valueOf(100);
245    BIG_DECIMAL_1_000_000 = BigDecimal.valueOf(1_000_000);
246
247    CARDINALITIES_BY_NAME = Collections.unmodifiableMap(Arrays.stream(
248        Cardinality.values()).collect(Collectors.toMap(cardinality -> cardinality.name(), cardinality -> cardinality)));
249  }
250
251  /**
252   * Gets an appropriate plural cardinality for the given number and locale.
253   * <p>
254   * When determining cardinality, the decimal places of {@code number} will be computed and used.
255   * Note that if trailing zeroes are important, e.g. {@code 1.00} instead of {@code 1}, you must either specify a {@link BigDecimal} with appropriate
256   * scale or supply a non-null {@code visibleDecimalPlaces} value.
257   * <p>
258   * If you do not provide a {@link BigDecimal} and wish to manually specify the number of visible decimals, use {@link #forNumber(Number, Integer, Locale)} instead.
259   * <p>
260   * See the <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR Language Plural Rules</a>
261   * for further details.
262   *
263   * @param number the number that drives pluralization, not null
264   * @param locale the locale that drives pluralization, not null
265   * @return an appropriate plural cardinality, not null
266   * @throws UnsupportedLocaleException if the locale is not supported
267   */
268  @Nonnull
269  public static Cardinality forNumber(@Nonnull Number number, @Nonnull Locale locale) {
270    requireNonNull(number);
271    requireNonNull(locale);
272
273    return forNumber(number, null, locale);
274  }
275
276  /**
277   * Gets an appropriate plural cardinality for the given number, visible decimal places, and locale.
278   * <p>
279   * If {@code visibleDecimalPlaces} is null, then the decimal places of {@code number} will be computed and used.
280   * Note that if trailing zeroes are important, e.g. {@code 1.00} instead of {@code 1}, you must either specify a {@link BigDecimal} with appropriate
281   * scale or supply a non-null {@code visibleDecimalPlaces} value.
282   * <p>
283   * See the <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR Language Plural Rules</a>
284   * for further details.
285   *
286   * @param number               the number that drives pluralization, not null
287   * @param visibleDecimalPlaces the number of decimal places that will ultimately be displayed, may be null
288   * @param locale               the locale that drives pluralization, not null
289   * @return an appropriate plural cardinality, not null
290   * @throws UnsupportedLocaleException if the locale is not supported
291   */
292  @Nonnull
293  public static Cardinality forNumber(@Nonnull Number number, @Nullable Integer visibleDecimalPlaces, @Nonnull Locale locale) {
294    requireNonNull(number);
295    requireNonNull(locale);
296
297    boolean numberIsBigDecimal = number instanceof BigDecimal;
298    BigDecimal numberAsBigDecimal = null;
299
300    // If number of visible decimal places is not specified, compute the number of decimal places.
301    // If the number is a BigDecimal, then we have access to trailing zeroes.
302    // We cannot know the number of trailing zeroes otherwise - onus is on caller to explicitly specify if she cares about this
303    if (visibleDecimalPlaces == null && !numberIsBigDecimal) {
304      numberAsBigDecimal = NumberUtils.toBigDecimal(number);
305      numberAsBigDecimal = numberAsBigDecimal.setScale(NumberUtils.numberOfDecimalPlaces(number), RoundingMode.FLOOR);
306    } else if (visibleDecimalPlaces != null && numberIsBigDecimal) {
307      numberAsBigDecimal = (BigDecimal) number;
308      numberAsBigDecimal = numberAsBigDecimal.setScale(visibleDecimalPlaces, RoundingMode.FLOOR);
309    }
310
311    if (numberAsBigDecimal == null)
312      numberAsBigDecimal = NumberUtils.toBigDecimal(number);
313
314    Optional<CardinalityFamily> cardinalityFamily = CardinalityFamily.cardinalityFamilyForLocale(locale);
315
316    // TODO: throwing an exception might not be the best solution here...need to think about it
317    if (!cardinalityFamily.isPresent())
318      throw new UnsupportedLocaleException(locale);
319
320    return cardinalityFamily.get().getCardinalityFunction().apply(numberAsBigDecimal);
321  }
322
323  /**
324   * Gets an appropriate plural cardinality for the given range (start, end) and locale.
325   * <p>
326   * For example, a range might be {@code "1-3 hours"}.
327   * <p>
328   * Note that the cardinality of the end of the range does not necessarily
329   * determine the range's cardinality.  In English, we say {@code "0–1 days"} - the value {@code 1} is {@code CARDINALITY_ONE}
330   * but the range is {@code CARDINALITY_OTHER}.
331   * <p>
332   * See the <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR Language Plural Rules</a>
333   * for further details.
334   *
335   * @param start  the cardinality for the start of the range, not null
336   * @param end    the cardinality for the end of the range, not null
337   * @param locale the locale that drives pluralization, not null
338   * @return an appropriate plural cardinality for the range, not null
339   * @throws UnsupportedLocaleException if the locale is not supported
340   */
341  @Nonnull
342  public static Cardinality forRange(@Nonnull Cardinality start, @Nonnull Cardinality end, @Nonnull Locale locale) {
343    requireNonNull(start);
344    requireNonNull(end);
345    requireNonNull(locale);
346
347    Optional<CardinalityRangeFamily> cardinalityRangeFamily = CardinalityRangeFamily.cardinalityRangeFamilyForLocale(locale);
348
349    // TODO: throwing an exception might not be the best solution here...need to think about it
350    if (!cardinalityRangeFamily.isPresent())
351      throw new UnsupportedLocaleException(locale);
352
353    CardinalityRange cardinalityRange = CardinalityRange.of(start, end);
354    Cardinality cardinality = cardinalityRangeFamily.get().getCardinalitiesByCardinalityRange().get(cardinalityRange);
355
356    return cardinality == null ? Cardinality.OTHER : cardinality;
357  }
358
359  /**
360   * Gets the set of cardinalities supported for the given locale.
361   * <p>
362   * The empty set will be returned if the locale is not supported.
363   * <p>
364   * The set's values are sorted by the natural ordering of the {@link Cardinality} enumeration.
365   *
366   * @param locale the locale to use for lookup, not null
367   * @return the cardinalities supported by the given locale, not null
368   */
369  @Nonnull
370  public static SortedSet<Cardinality> supportedCardinalitiesForLocale(@Nonnull Locale locale) {
371    requireNonNull(locale);
372
373    Optional<CardinalityFamily> cardinalityFamily = CardinalityFamily.cardinalityFamilyForLocale(locale);
374    return cardinalityFamily.isPresent() ? cardinalityFamily.get().getSupportedCardinalities() : Collections.emptySortedSet();
375  }
376
377  /**
378   * Gets a mapping of cardinalities to example integer values for the given locale.
379   * <p>
380   * The empty map will be returned if the locale is not supported or if no example values are available.
381   * <p>
382   * The map's keys are sorted by the natural ordering of the {@link Cardinality} enumeration.
383   *
384   * @param locale the locale to use for lookup, not null
385   * @return a mapping of cardinalities to example integer values, not null
386   */
387  @Nonnull
388  public static SortedMap<Cardinality, Range<Integer>> exampleIntegerValuesForLocale(@Nonnull Locale locale) {
389    requireNonNull(locale);
390
391    Optional<CardinalityFamily> cardinalityFamily = CardinalityFamily.cardinalityFamilyForLocale(locale);
392    return cardinalityFamily.isPresent() ? cardinalityFamily.get().getExampleIntegerValuesByCardinality() : Collections.emptySortedMap();
393  }
394
395  /**
396   * Gets a mapping of cardinalities to example decimal values for the given locale.
397   * <p>
398   * The empty map will be returned if the locale is not supported or if no example values are available.
399   * <p>
400   * The map's keys are sorted by the natural ordering of the {@link Cardinality} enumeration.
401   *
402   * @param locale the locale to use for lookup, not null
403   * @return a mapping of cardinalities to example decimal values, not null
404   */
405  @Nonnull
406  public static SortedMap<Cardinality, Range<BigDecimal>> exampleDecimalValuesForLocale(@Nonnull Locale locale) {
407    requireNonNull(locale);
408
409    Optional<CardinalityFamily> cardinalityFamily = CardinalityFamily.cardinalityFamilyForLocale(locale);
410    return cardinalityFamily.isPresent() ? cardinalityFamily.get().getExampleDecimalValuesByCardinality() : Collections.emptySortedMap();
411  }
412
413  /**
414   * Gets the ISO 639 language codes for which cardinality operations are supported.
415   * <p>
416   * The set's values are ISO 639 codes and therefore sorted using English collation.
417   *
418   * @return the ISO 639 language codes for which cardinality operations are supported, not null
419   */
420  @Nonnull
421  public static SortedSet<String> getSupportedLanguageCodes() {
422    return CardinalityFamily.getSupportedLanguageCodes();
423  }
424
425  /**
426   * Gets the mapping of cardinality names to values.
427   *
428   * @return the mapping of cardinality names to values, not null
429   */
430  @Nonnull
431  static Map<String, Cardinality> getCardinalitiesByName() {
432    return CARDINALITIES_BY_NAME;
433  }
434
435  /**
436   * Plural cardinality forms grouped by language family.
437   * <p>
438   * Each family has a distinct cardinality calculation rule.
439   * <p>
440   * For example, Germanic languages {@link CardinalityFamily#FAMILY_3} support two {@link Cardinality} types: {@link Cardinality#ONE} for {@code 1}
441   * and {@link Cardinality#OTHER} for all other values.
442   * <p>
443   * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR Language Plural Rules</a>
444   * for more information.
445   * <p>
446   * Cardinality functions are driven by CLDR data.
447   * <p>
448   * The expression format as specified by http://www.unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
449   * uses the following notation:
450   * <ul>
451   * <li>{@code n} absolute value of the source number (integer and decimals).</li>
452   * <li>{@code i} integer digits of n.</li>
453   * <li>{@code v} number of visible fraction digits in n, with trailing zeros.</li>
454   * <li>{@code w} number of visible fraction digits in n, without trailing zeros.</li>
455   * <li>{@code f} visible fractional digits in n, with trailing zeros.</li>
456   * <li>{@code t} visible fractional digits in n, without trailing zeros.</li>
457   * </ul>
458   * <p>
459   * Some examples follow:
460   * <ul>
461   * <li>{@code n=1: i=1, v=0, w=0, f=0, t=0}</li>
462   * <li>{@code n=1.0: i=1, v=1, w=0, f=0, t=0}</li>
463   * <li>{@code n=1.00: i=1, v=2, w=0, f=0, t=0}</li>
464   * <li>{@code n=1.3: i=1, v=1, w=1, f=3, t=3}</li>
465   * <li>{@code n=1.30: i=1, v=2, w=1, f=30, t=3}</li>
466   * <li>{@code n=1.03: i=1, v=2, w=2, f=3, t=3}</li>
467   * <li>{@code n=1.230: i=1, v=3, w=2, f=230, t=23}</li>
468   * </ul>
469   */
470  enum CardinalityFamily {
471    /**
472     * Languages Include:
473     * <p>
474     * <ul>
475     * <li>Afrikaans (af)</li>
476     * <li>Asu (asa)</li>
477     * <li>Azeri (az)</li>
478     * <li>Bemba (bem)</li>
479     * <li>Bena (bez)</li>
480     * <li>Bulgarian (bg)</li>
481     * <li>Bodo (brx)</li>
482     * <li>Chechen (ce)</li>
483     * <li>Chiga (cgg)</li>
484     * <li>Cherokee (chr)</li>
485     * <li>Central Kurdish (ckb)</li>
486     * <li>Divehi (dv)</li>
487     * <li>Ewe (ee)</li>
488     * <li>Greek (el)</li>
489     * <li>Esperanto (eo)</li>
490     * <li>Spanish (es)</li>
491     * <li>Basque (eu)</li>
492     * <li>Faroese (fo)</li>
493     * <li>Friulian (fur)</li>
494     * <li>Swiss German (gsw)</li>
495     * <li>Hausa (ha)</li>
496     * <li>Hawaiian (haw)</li>
497     * <li>Hungarian (hu)</li>
498     * <li>Ngomba (jgo)</li>
499     * <li>Machame (jmc)</li>
500     * <li>Georgian (ka)</li>
501     * <li>Jju (kaj)</li>
502     * <li>Tyap (kcg)</li>
503     * <li>Kazakh (kk)</li>
504     * <li>Kako (kkj)</li>
505     * <li>Greenlandic (kl)</li>
506     * <li>Kashmiri (ks)</li>
507     * <li>Shambala (ksb)</li>
508     * <li>Kurdish (ku)</li>
509     * <li>Kirghiz (ky)</li>
510     * <li>Luxembourgish (lb)</li>
511     * <li>Ganda (lg)</li>
512     * <li>Masai (mas)</li>
513     * <li>Metaʼ (mgo)</li>
514     * <li>Malayalam (ml)</li>
515     * <li>Mongolian (mn)</li>
516     * <li>Nahuatl (nah)</li>
517     * <li>Norwegian Bokmål (nb)</li>
518     * <li>North Ndebele (nd)</li>
519     * <li>Nepali (ne)</li>
520     * <li>Norwegian Nynorsk (nn)</li>
521     * <li>Ngiemboon (nnh)</li>
522     * <li>Norwegian (no)</li>
523     * <li>South Ndebele (nr)</li>
524     * <li>Nyanja (ny)</li>
525     * <li>Nyankole (nyn)</li>
526     * <li>Oromo (om)</li>
527     * <li>Odia (or)</li>
528     * <li>Ossetian (os)</li>
529     * <li>Papiamento (pap)</li>
530     * <li>Pushto (ps)</li>
531     * <li>Romansh (rm)</li>
532     * <li>Rombo (rof)</li>
533     * <li>Rwa (rwk)</li>
534     * <li>Samburu (saq)</li>
535     * <li>Southern Kurdish (sdh)</li>
536     * <li>Sena (seh)</li>
537     * <li>Shona (sn)</li>
538     * <li>Somali (so)</li>
539     * <li>Albanian (sq)</li>
540     * <li>Swati (ss)</li>
541     * <li>Saho (ssy)</li>
542     * <li>Southern Sotho (st)</li>
543     * <li>Syriac (syr)</li>
544     * <li>Tamil (ta)</li>
545     * <li>Telugu (te)</li>
546     * <li>Teso (teo)</li>
547     * <li>Tigre (tig)</li>
548     * <li>Turkmen (tk)</li>
549     * <li>Tswana (tn)</li>
550     * <li>Turkish (tr)</li>
551     * <li>Tsonga (ts)</li>
552     * <li>Uighur (ug)</li>
553     * <li>Uzbek (uz)</li>
554     * <li>Venda (ve)</li>
555     * <li>Volapük (vo)</li>
556     * <li>Vunjo (vun)</li>
557     * <li>Walser (wae)</li>
558     * <li>Xhosa (xh)</li>
559     * <li>Soga (xog)</li>
560     * </ul>
561     */
562    FAMILY_1(
563        (n) -> {
564          // n = 1
565          if (equal(n, BIG_DECIMAL_1))
566            return ONE;
567
568          return OTHER;
569        },
570        Sets.sortedSet(
571            ONE,
572            OTHER
573        ),
574        Maps.sortedMap(
575            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
576            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
577        ),
578        Maps.sortedMap(
579            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6")))
580        )
581    ),
582
583    /**
584     * Languages Include:
585     * <p>
586     * <ul>
587     * <li>Bambara (bm)</li>
588     * <li>Tibetan (bo)</li>
589     * <li>Dzongkha (dz)</li>
590     * <li>Indonesian (id)</li>
591     * <li>Igbo (ig)</li>
592     * <li>Sichuan Yi (ii)</li>
593     * <li>Japanese (ja)</li>
594     * <li>Lojban (jbo)</li>
595     * <li>Javanese (jv)</li>
596     * <li>Javanese (jw)</li>
597     * <li>Makonde (kde)</li>
598     * <li>Kabuverdianu (kea)</li>
599     * <li>Khmer (km)</li>
600     * <li>Korean (ko)</li>
601     * <li>Lakota (lkt)</li>
602     * <li>Lao (lo)</li>
603     * <li>Malay (ms)</li>
604     * <li>Burmese (my)</li>
605     * <li>N’Ko (nqo)</li>
606     * <li>Root (root)</li>
607     * <li>Sakha (sah)</li>
608     * <li>Koyraboro Senni (ses)</li>
609     * <li>Sango (sg)</li>
610     * <li>Thai (th)</li>
611     * <li>Tongan (to)</li>
612     * <li>Vietnamese (vi)</li>
613     * <li>Wolof (wo)</li>
614     * <li>Yoruba (yo)</li>
615     * <li>Cantonese (yue)</li>
616     * <li>Mandarin Chinese (zh)</li>
617     * </ul>
618     */
619    FAMILY_2(
620        (n) -> {
621          // No cardinality rules for this family
622          return OTHER;
623        },
624        Sets.sortedSet(
625            OTHER
626        ),
627        Maps.sortedMap(
628            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 100, 1000, 10000, 100000, 1000000))
629        ),
630        Maps.sortedMap(
631            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
632        )
633    ),
634
635    /**
636     * Languages Include:
637     * <p>
638     * <ul>
639     * <li>Asturian (ast)</li>
640     * <li>Catalan (ca)</li>
641     * <li>German (de)</li>
642     * <li>English (en)</li>
643     * <li>Estonian (et)</li>
644     * <li>Finnish (fi)</li>
645     * <li>Western Frisian (fy)</li>
646     * <li>Galician (gl)</li>
647     * <li>Italian (it)</li>
648     * <li>Dutch (nl)</li>
649     * <li>Swedish (sv)</li>
650     * <li>Swahili (sw)</li>
651     * <li>Urdu (ur)</li>
652     * <li>Yiddish (yi)</li>
653     * </ul>
654     */
655    FAMILY_3(
656        (n) -> {
657          BigInteger i = NumberUtils.integerComponent(n);
658          int v = NumberUtils.numberOfDecimalPlaces(n);
659
660          // i = 1 and v = 0
661          if (equal(i, BIG_INTEGER_1) && v == 0)
662            return ONE;
663
664          return OTHER;
665        },
666        Sets.sortedSet(
667            ONE,
668            OTHER
669        ),
670        Maps.sortedMap(
671            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
672            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
673        ),
674        Maps.sortedMap(
675            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
676        )
677    ),
678
679    /**
680     * Languages Include:
681     * <p>
682     * <ul>
683     * <li>Akan (ak)</li>
684     * <li>Bihari (bh)</li>
685     * <li>Gun (guw)</li>
686     * <li>Lingala (ln)</li>
687     * <li>Malagasy (mg)</li>
688     * <li>Northern Sotho (nso)</li>
689     * <li>Punjabi (pa)</li>
690     * <li>Tigrinya (ti)</li>
691     * <li>Walloon (wa)</li>
692     * </ul>
693     */
694    FAMILY_4(
695        (n) -> {
696          // n = 0..1
697          if (inRange(n, BIG_DECIMAL_0, BIG_DECIMAL_1))
698            return ONE;
699
700          return OTHER;
701        },
702        Sets.sortedSet(
703            ONE,
704            OTHER
705        ),
706        Maps.sortedMap(
707            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
708            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
709        ),
710        Maps.sortedMap(
711            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7")))
712        )
713    ),
714
715    /**
716     * Languages Include:
717     * <p>
718     * <ul>
719     * <li>Amharic (am)</li>
720     * <li>Assamese (as)</li>
721     * <li>Bangla (bn)</li>
722     * <li>Persian (fa)</li>
723     * <li>Gujarati (gu)</li>
724     * <li>Hindi (hi)</li>
725     * <li>Kannada (kn)</li>
726     * <li>Marathi (mr)</li>
727     * <li>Zulu (zu)</li>
728     * </ul>
729     */
730    FAMILY_5(
731        (n) -> {
732          // i = 0 or n = 1
733          BigInteger i = NumberUtils.integerComponent(n);
734
735          if (equal(i, BIG_INTEGER_0) || equal(n, BIG_DECIMAL_1))
736            return ONE;
737
738          return OTHER;
739        },
740        Sets.sortedSet(
741            ONE,
742            OTHER
743        ),
744        Maps.sortedMap(
745            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
746            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
747        ),
748        Maps.sortedMap(
749            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.0"), new BigDecimal("0.01"), new BigDecimal("0.02"), new BigDecimal("0.03"), new BigDecimal("0.04"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"))),
750            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("1.9"), new BigDecimal("2.0"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6")))
751        )
752    ),
753
754    /**
755     * Languages Include:
756     * <p>
757     * <ul>
758     * <li>Inuktitut (iu)</li>
759     * <li>Cornish (kw)</li>
760     * <li>Nama (naq)</li>
761     * <li>Northern Sami (se)</li>
762     * <li>Southern Sami (sma)</li>
763     * <li>Sami (smi)</li>
764     * <li>Lule Sami (smj)</li>
765     * <li>Inari Sami (smn)</li>
766     * <li>Skolt Sami (sms)</li>
767     * </ul>
768     */
769    FAMILY_6(
770        (n) -> {
771          // n = 1
772          if (equal(n, BIG_DECIMAL_1))
773            return ONE;
774          // n = 2
775          if (equal(n, BIG_DECIMAL_2))
776            return TWO;
777
778          return OTHER;
779        },
780        Sets.sortedSet(
781            ONE,
782            TWO,
783            OTHER
784        ),
785        Maps.sortedMap(
786            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
787            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2)),
788            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
789        ),
790        Maps.sortedMap(
791            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6")))
792        )
793    ),
794
795    /**
796     * Languages Include:
797     * <p>
798     * <ul>
799     * <li>Bosnian (bs)</li>
800     * <li>Croatian (hr)</li>
801     * <li>Serbo-Croatian (sh)</li>
802     * <li>Serbian (sr)</li>
803     * </ul>
804     */
805    FAMILY_7(
806        (n) -> {
807          int v = NumberUtils.numberOfDecimalPlaces(n);
808          BigInteger i = NumberUtils.integerComponent(n);
809          BigInteger f = NumberUtils.fractionalComponent(n);
810
811          // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
812          if ((v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(i.mod(BIG_INTEGER_100), BIG_INTEGER_11))
813              || (equal(f.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(f.mod(BIG_INTEGER_100), BIG_INTEGER_11)))
814            return ONE;
815          // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14
816          if ((v == 0
817              && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_2, BIG_INTEGER_4)
818              && notInRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_12, BIG_INTEGER_14))
819              ||
820              (inRange(f.mod(BIG_INTEGER_10), BIG_INTEGER_2, BIG_INTEGER_4)
821                  && notInRange(f.mod(BIG_INTEGER_100), BIG_INTEGER_12, BIG_INTEGER_14)))
822            return FEW;
823
824          return OTHER;
825        },
826        Sets.sortedSet(
827            ONE,
828            FEW,
829            OTHER
830        ),
831        Maps.sortedMap(
832            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
833            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(2, 3, 4, 22, 23, 24, 32, 33, 34, 42, 43, 44, 52, 53, 54, 62, 102, 1002)),
834            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
835        ),
836        Maps.sortedMap(
837            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("1.1"), new BigDecimal("2.1"), new BigDecimal("3.1"), new BigDecimal("4.1"), new BigDecimal("5.1"), new BigDecimal("6.1"), new BigDecimal("7.1"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1"))),
838            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("3.2"), new BigDecimal("3.3"), new BigDecimal("3.4"), new BigDecimal("4.2"), new BigDecimal("4.3"), new BigDecimal("4.4"), new BigDecimal("5.2"), new BigDecimal("10.2"), new BigDecimal("100.2"), new BigDecimal("1000.2"))),
839            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("1.9"), new BigDecimal("2.0"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7")))
840        )
841    ),
842
843    /**
844     * Languages Include:
845     * <p>
846     * <ul>
847     * <li>Fulah (ff)</li>
848     * <li>French (fr)</li>
849     * <li>Armenian (hy)</li>
850     * <li>Kabyle (kab)</li>
851     * </ul>
852     */
853    FAMILY_8(
854        (n) -> {
855          BigInteger i = NumberUtils.integerComponent(n);
856
857          // i = 0,1
858          if (inSet(i, BIG_INTEGER_0, BIG_INTEGER_1))
859            return ONE;
860
861          return OTHER;
862        },
863        Sets.sortedSet(
864            ONE,
865            OTHER
866        ),
867        Maps.sortedMap(
868            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
869            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
870        ),
871        Maps.sortedMap(
872            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"))),
873            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("2.0"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7"), new BigDecimal("2.8"), new BigDecimal("2.9"), new BigDecimal("3.0"), new BigDecimal("3.1"), new BigDecimal("3.2"), new BigDecimal("3.3"), new BigDecimal("3.4"), new BigDecimal("3.5")))
874        )
875    ),
876
877    /**
878     * Languages Include:
879     * <p>
880     * <ul>
881     * <li>Arabic (ar)</li>
882     * <li>Najdi Arabic (ars)</li>
883     * </ul>
884     */
885    FAMILY_9(
886        (n) -> {
887          // n = 0
888          if (equal(n, BIG_DECIMAL_0))
889            return ZERO;
890          // n = 1
891          if (equal(n, BIG_DECIMAL_1))
892            return ONE;
893          // n = 2
894          if (equal(n, BIG_DECIMAL_2))
895            return TWO;
896          // n % 100 = 3..10
897          if (inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_3, BIG_DECIMAL_10))
898            return FEW;
899          // n % 100 = 11..99
900          if (inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_99))
901            return MANY;
902
903          return OTHER;
904        },
905        Sets.sortedSet(
906            ZERO,
907            ONE,
908            TWO,
909            FEW,
910            MANY,
911            OTHER
912        ),
913        Maps.sortedMap(
914            MapEntry.of(Cardinality.ZERO, Range.ofFiniteValues(0)),
915            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
916            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2)),
917            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(3, 4, 5, 6, 7, 8, 9, 10, 103, 104, 105, 106, 107, 108, 109, 110, 1003)),
918            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 111, 1011)),
919            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(100, 101, 102, 200, 201, 202, 300, 301, 302, 400, 401, 402, 500, 501, 502, 600, 1000, 10000, 100000, 1000000))
920        ),
921        Maps.sortedMap(
922            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("10.1")))
923        )
924    ),
925
926    /**
927     * Languages Include:
928     * <p>
929     * <ul>
930     * <li>Czech (cs)</li>
931     * <li>Slovak (sk)</li>
932     * </ul>
933     */
934    FAMILY_10(
935        (n) -> {
936          int v = NumberUtils.numberOfDecimalPlaces(n);
937          BigInteger i = NumberUtils.integerComponent(n);
938
939          // i = 1 and v = 0
940          if (equal(i, BIG_INTEGER_1) && v == 0)
941            return ONE;
942          // i = 2..4 and v = 0
943          if (inRange(i, BIG_INTEGER_2, BIG_INTEGER_4) && v == 0)
944            return FEW;
945          // v != 0
946          if (v != 0)
947            return MANY;
948
949          return OTHER;
950        },
951        Sets.sortedSet(
952            ONE,
953            FEW,
954            MANY,
955            OTHER
956        ),
957        Maps.sortedMap(
958            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
959            MapEntry.of(Cardinality.FEW, Range.ofFiniteValues(2, 3, 4)),
960            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
961        ),
962        Maps.sortedMap(
963            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
964        )
965    ),
966
967    /**
968     * Languages Include:
969     * <p>
970     * <ul>
971     * <li>Lower Sorbian (dsb)</li>
972     * <li>Upper Sorbian (hsb)</li>
973     * </ul>
974     */
975    FAMILY_11(
976        (n) -> {
977          int v = NumberUtils.numberOfDecimalPlaces(n);
978          BigInteger i = NumberUtils.integerComponent(n);
979          BigInteger f = NumberUtils.fractionalComponent(n);
980
981          // v = 0 and i % 100 = 1 or f % 100 = 1
982          if ((v == 0 && equal(i.mod(BIG_INTEGER_100), BIG_INTEGER_1))
983              || (equal(f.mod(BIG_INTEGER_100), BIG_INTEGER_1)))
984            return ONE;
985          // v = 0 and i % 100 = 2 or f % 100 = 2
986          if ((v == 0 && equal(i.mod(BIG_INTEGER_100), BIG_INTEGER_2))
987              || equal(f.mod(BIG_INTEGER_100), BIG_INTEGER_2))
988            return TWO;
989          // v = 0 and i % 100 = 3..4 or f % 100 = 3..4
990          if ((v == 0 && inRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_3, BIG_INTEGER_4))
991              || inRange(f.mod(BIG_INTEGER_100), BIG_INTEGER_3, BIG_INTEGER_4))
992            return FEW;
993
994          return OTHER;
995        },
996        Sets.sortedSet(
997            ONE,
998            TWO,
999            FEW,
1000            OTHER
1001        ),
1002        Maps.sortedMap(
1003            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 101, 201, 301, 401, 501, 601, 701, 1001)),
1004            MapEntry.of(Cardinality.TWO, Range.ofInfiniteValues(2, 102, 202, 302, 402, 502, 602, 702, 1002)),
1005            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003)),
1006            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1007        ),
1008        Maps.sortedMap(
1009            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("1.1"), new BigDecimal("2.1"), new BigDecimal("3.1"), new BigDecimal("4.1"), new BigDecimal("5.1"), new BigDecimal("6.1"), new BigDecimal("7.1"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1"))),
1010            MapEntry.of(Cardinality.TWO, Range.ofInfiniteValues(new BigDecimal("0.2"), new BigDecimal("1.2"), new BigDecimal("2.2"), new BigDecimal("3.2"), new BigDecimal("4.2"), new BigDecimal("5.2"), new BigDecimal("6.2"), new BigDecimal("7.2"), new BigDecimal("10.2"), new BigDecimal("100.2"), new BigDecimal("1000.2"))),
1011            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("3.3"), new BigDecimal("3.4"), new BigDecimal("4.3"), new BigDecimal("4.4"), new BigDecimal("5.3"), new BigDecimal("5.4"), new BigDecimal("6.3"), new BigDecimal("6.4"), new BigDecimal("7.3"), new BigDecimal("7.4"), new BigDecimal("10.3"), new BigDecimal("100.3"), new BigDecimal("1000.3"))),
1012            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("1.9"), new BigDecimal("2.0"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7")))
1013        )
1014    ),
1015
1016    /**
1017     * Languages Include:
1018     * <p>
1019     * <ul>
1020     * <li>Filipino (fil)</li>
1021     * <li>Tagalog (tl)</li>
1022     * </ul>
1023     */
1024    FAMILY_12(
1025        (n) -> {
1026          int v = NumberUtils.numberOfDecimalPlaces(n);
1027          BigInteger i = NumberUtils.integerComponent(n);
1028          BigInteger f = NumberUtils.fractionalComponent(n);
1029
1030          // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9
1031          if ((v == 0 && inSet(i, BIG_INTEGER_1, BIG_INTEGER_2, BIG_INTEGER_3))
1032              || (v == 0 && notInSet(i.mod(BIG_INTEGER_10), BIG_INTEGER_4, BIG_INTEGER_6, BIG_INTEGER_9))
1033              || (v != 0 && notInSet(f.mod(BIG_INTEGER_10), BIG_INTEGER_4, BIG_INTEGER_6, BIG_INTEGER_9)))
1034            return ONE;
1035
1036          return OTHER;
1037        },
1038        Sets.sortedSet(
1039            ONE,
1040            OTHER
1041        ),
1042        Maps.sortedMap(
1043            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(0, 1, 2, 3, 5, 7, 8, 10, 11, 12, 13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000)),
1044            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(4, 6, 9, 14, 16, 19, 24, 26, 104, 1004))
1045        ),
1046        Maps.sortedMap(
1047            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.5"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.5"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("2.1"))),
1048            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.4"), new BigDecimal("0.6"), new BigDecimal("0.9"), new BigDecimal("1.4"), new BigDecimal("1.6"), new BigDecimal("1.9"), new BigDecimal("2.4"), new BigDecimal("2.6"), new BigDecimal("10.4"), new BigDecimal("100.4"), new BigDecimal("1000.4")))
1049        )
1050    ),
1051
1052    /**
1053     * Languages Include:
1054     * <p>
1055     * <ul>
1056     * <li>Latvian (lv)</li>
1057     * <li>Prussian (prg)</li>
1058     * </ul>
1059     */
1060    FAMILY_13(
1061        (n) -> {
1062          int v = NumberUtils.numberOfDecimalPlaces(n);
1063          BigInteger f = NumberUtils.fractionalComponent(n);
1064
1065          // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19
1066          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_0)
1067              || inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_19)
1068              || (v == 2 && inRange(f.mod(BIG_INTEGER_100), BIG_INTEGER_11, BIG_INTEGER_19)))
1069            return ZERO;
1070          // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1
1071          if ((equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11))
1072              || (v == 2 && equal(f.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(f.mod(BIG_INTEGER_100), BIG_INTEGER_11))
1073              || (v != 2 && equal(f.mod(BIG_INTEGER_10), BIG_INTEGER_1)))
1074            return ONE;
1075
1076          return OTHER;
1077        },
1078        Sets.sortedSet(
1079            ZERO,
1080            ONE,
1081            OTHER
1082        ),
1083        Maps.sortedMap(
1084            MapEntry.of(Cardinality.ZERO, Range.ofInfiniteValues(0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000)),
1085            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
1086            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 22, 23, 24, 25, 26, 27, 28, 29, 102, 1002))
1087        ),
1088        Maps.sortedMap(
1089            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("1.1"), new BigDecimal("2.1"), new BigDecimal("3.1"), new BigDecimal("4.1"), new BigDecimal("5.1"), new BigDecimal("6.1"), new BigDecimal("7.1"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1"))),
1090            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("1.9"), new BigDecimal("10.2"), new BigDecimal("100.2"), new BigDecimal("1000.2")))
1091        )
1092    ),
1093
1094    /**
1095     * Languages Include:
1096     * <p>
1097     * <ul>
1098     * <li>Moldovan (mo)</li>
1099     * <li>Romanian (ro)</li>
1100     * </ul>
1101     */
1102    FAMILY_14(
1103        (n) -> {
1104          int v = NumberUtils.numberOfDecimalPlaces(n);
1105          BigInteger i = NumberUtils.integerComponent(n);
1106
1107          // i = 1 and v = 0
1108          if (equal(i, BIG_INTEGER_1) && v == 0)
1109            return ONE;
1110          // v != 0 or n = 0 or n != 1 and n % 100 = 1..19
1111          if (v != 0
1112              || equal(n, BIG_DECIMAL_0)
1113              || (notEqual(n, BIG_DECIMAL_1) && inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_1, BIG_DECIMAL_19)))
1114            return FEW;
1115
1116          return OTHER;
1117        },
1118        Sets.sortedSet(
1119            ONE,
1120            FEW,
1121            OTHER
1122        ),
1123        Maps.sortedMap(
1124            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1125            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 101, 1001)),
1126            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 100, 1000, 10000, 100000, 1000000))
1127        ),
1128        Maps.sortedMap(
1129            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1130        )
1131    ),
1132
1133    /**
1134     * Languages Include:
1135     * <p>
1136     * <ul>
1137     * <li>Russian (ru)</li>
1138     * <li>Ukrainian (uk)</li>
1139     * </ul>
1140     */
1141    FAMILY_15(
1142        (n) -> {
1143          int v = NumberUtils.numberOfDecimalPlaces(n);
1144          BigInteger i = NumberUtils.integerComponent(n);
1145
1146          // v = 0 and i % 10 = 1 and i % 100 != 11
1147          if (v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(i.mod(BIG_INTEGER_100), BIG_INTEGER_11))
1148            return ONE;
1149          // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
1150          if (v == 0
1151              && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_2, BIG_INTEGER_4)
1152              && notInRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_12, BIG_INTEGER_14))
1153            return FEW;
1154          // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14
1155          if ((v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_0))
1156              || (v == 0 && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_5, BIG_INTEGER_9))
1157              || (v == 0 && inRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_11, BIG_INTEGER_14)))
1158            return MANY;
1159
1160          return OTHER;
1161        },
1162        Sets.sortedSet(
1163            ONE,
1164            FEW,
1165            MANY,
1166            OTHER
1167        ),
1168        Maps.sortedMap(
1169            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
1170            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(2, 3, 4, 22, 23, 24, 32, 33, 34, 42, 43, 44, 52, 53, 54, 62, 102, 1002)),
1171            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1172        ),
1173        Maps.sortedMap(
1174            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1175        )
1176    ),
1177
1178    /**
1179     * Languages Include:
1180     * <p>
1181     * <ul>
1182     * <li>Belarusian (be)</li>
1183     * </ul>
1184     */
1185    FAMILY_16(
1186        (n) -> {
1187          // n % 10 = 1 and n % 100 != 11
1188          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11))
1189            return ONE;
1190          // n % 10 = 2..4 and n % 100 != 12..14
1191          if (inRange(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_2, BIG_DECIMAL_4)
1192              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_12, BIG_DECIMAL_14))
1193            return FEW;
1194          // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14
1195          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_0)
1196              || inRange(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_5, BIG_DECIMAL_9)
1197              || inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_14))
1198            return MANY;
1199
1200          return OTHER;
1201        },
1202        Sets.sortedSet(
1203            ONE,
1204            FEW,
1205            MANY,
1206            OTHER
1207        ),
1208        Maps.sortedMap(
1209            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
1210            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(2, 3, 4, 22, 23, 24, 32, 33, 34, 42, 43, 44, 52, 53, 54, 62, 102, 1002)),
1211            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1212        ),
1213        Maps.sortedMap(
1214            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1")))
1215        )
1216    ),
1217
1218    /**
1219     * Languages Include:
1220     * <p>
1221     * <ul>
1222     * <li>Breton (br)</li>
1223     * </ul>
1224     */
1225    FAMILY_17(
1226        (n) -> {
1227          // n % 10 = 1 and n % 100 != 11,71,91
1228          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1)
1229              && notInSet(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_71, BIG_DECIMAL_91))
1230            return ONE;
1231          // n % 10 = 2 and n % 100 != 12,72,92
1232          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_2)
1233              && notInSet(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_12, BIG_DECIMAL_72, BIG_DECIMAL_92))
1234            return TWO;
1235          // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99
1236          if ((inRange(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_3, BIG_DECIMAL_4) || equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_9))
1237              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_10, BIG_DECIMAL_19)
1238              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_70, BIG_DECIMAL_79)
1239              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_90, BIG_DECIMAL_99))
1240            return FEW;
1241          // n != 0 and n % 1000000 = 0
1242          if (notEqual(n, BIG_DECIMAL_0) && equal(n.remainder(BIG_DECIMAL_1_000_000), BIG_DECIMAL_0))
1243            return MANY;
1244
1245          return OTHER;
1246        },
1247        Sets.sortedSet(
1248            ONE,
1249            TWO,
1250            FEW,
1251            MANY,
1252            OTHER
1253        ),
1254        Maps.sortedMap(
1255            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 81, 101, 1001)),
1256            MapEntry.of(Cardinality.TWO, Range.ofInfiniteValues(2, 22, 32, 42, 52, 62, 82, 102, 1002)),
1257            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003)),
1258            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(1000000)),
1259            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 100, 1000, 10000, 100000))
1260        ),
1261        Maps.sortedMap(
1262            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6")))
1263        )
1264    ),
1265
1266    /**
1267     * Languages Include:
1268     * <p>
1269     * <ul>
1270     * <li>Welsh (cy)</li>
1271     * </ul>
1272     */
1273    FAMILY_18(
1274        (n) -> {
1275          // n = 0
1276          if (equal(n, BIG_DECIMAL_0))
1277            return ZERO;
1278          // n = 1
1279          if (equal(n, BIG_DECIMAL_1))
1280            return ONE;
1281          // n = 2
1282          if (equal(n, BIG_DECIMAL_2))
1283            return TWO;
1284          // n = 3
1285          if (equal(n, BIG_DECIMAL_3))
1286            return FEW;
1287          // n = 6
1288          if (equal(n, BIG_DECIMAL_6))
1289            return MANY;
1290
1291          return OTHER;
1292        },
1293        Sets.sortedSet(
1294            ZERO,
1295            ONE,
1296            TWO,
1297            FEW,
1298            MANY,
1299            OTHER
1300        ),
1301        Maps.sortedMap(
1302            MapEntry.of(Cardinality.ZERO, Range.ofFiniteValues(0)),
1303            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1304            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2)),
1305            MapEntry.of(Cardinality.FEW, Range.ofFiniteValues(3)),
1306            MapEntry.of(Cardinality.MANY, Range.ofFiniteValues(6)),
1307            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 100, 1000, 10000, 100000, 1000000))
1308        ),
1309        Maps.sortedMap(
1310            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7")))
1311        )
1312    ),
1313
1314    /**
1315     * Languages Include:
1316     * <p>
1317     * <ul>
1318     * <li>Danish (da)</li>
1319     * </ul>
1320     */
1321    FAMILY_19(
1322        (n) -> {
1323          BigInteger i = NumberUtils.integerComponent(n);
1324          BigInteger t = NumberUtils.fractionalComponent(n.stripTrailingZeros());
1325
1326          // n = 1 or t != 0 and i = 0,1
1327          if (equal(n, BIG_DECIMAL_1) || (notEqual(t, BIG_INTEGER_0) && inSet(i, BIG_INTEGER_0, BIG_INTEGER_1)))
1328            return ONE;
1329
1330          return OTHER;
1331        },
1332        Sets.sortedSet(
1333            ONE,
1334            OTHER
1335        ),
1336        Maps.sortedMap(
1337            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1338            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
1339        ),
1340        Maps.sortedMap(
1341            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"))),
1342            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("2.0"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7"), new BigDecimal("2.8"), new BigDecimal("2.9"), new BigDecimal("3.0"), new BigDecimal("3.1"), new BigDecimal("3.2"), new BigDecimal("3.3"), new BigDecimal("3.4")))
1343        )
1344    ),
1345
1346    /**
1347     * Languages Include:
1348     * <p>
1349     * <ul>
1350     * <li>Irish (ga)</li>
1351     * </ul>
1352     */
1353    FAMILY_20(
1354        (n) -> {
1355          // n = 1
1356          if (equal(n, BIG_DECIMAL_1))
1357            return ONE;
1358          // n = 2
1359          if (equal(n, BIG_DECIMAL_2))
1360            return TWO;
1361          // n = 3..6
1362          if (inRange(n, BIG_DECIMAL_3, BIG_DECIMAL_6))
1363            return FEW;
1364          // n = 7..10
1365          if (inRange(n, BIG_DECIMAL_7, BIG_DECIMAL_10))
1366            return MANY;
1367
1368          return OTHER;
1369        },
1370        Sets.sortedSet(
1371            ONE,
1372            TWO,
1373            FEW,
1374            MANY,
1375            OTHER
1376        ),
1377        Maps.sortedMap(
1378            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1379            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2)),
1380            MapEntry.of(Cardinality.FEW, Range.ofFiniteValues(3, 4, 5, 6)),
1381            MapEntry.of(Cardinality.MANY, Range.ofFiniteValues(7, 8, 9, 10)),
1382            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 100, 1000, 10000, 100000, 1000000))
1383        ),
1384        Maps.sortedMap(
1385            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("10.1")))
1386        )
1387    ),
1388
1389    /**
1390     * Languages Include:
1391     * <p>
1392     * <ul>
1393     * <li>Scottish Gaelic (gd)</li>
1394     * </ul>
1395     */
1396    FAMILY_21(
1397        (n) -> {
1398          // n = 1,11
1399          if (inSet(n, BIG_DECIMAL_1, BIG_DECIMAL_11))
1400            return ONE;
1401          // n = 2,12
1402          if (inSet(n, BIG_DECIMAL_2, BIG_DECIMAL_12))
1403            return TWO;
1404          // n = 3..10,13..19
1405          if (inRange(n, BIG_DECIMAL_3, BIG_DECIMAL_10) || inRange(n, BIG_DECIMAL_13, BIG_DECIMAL_19))
1406            return FEW;
1407
1408          return OTHER;
1409        },
1410        Sets.sortedSet(
1411            ONE,
1412            TWO,
1413            FEW,
1414            OTHER
1415        ),
1416        Maps.sortedMap(
1417            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1, 11)),
1418            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2, 12)),
1419            MapEntry.of(Cardinality.FEW, Range.ofFiniteValues(3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, 18, 19)),
1420            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 100, 1000, 10000, 100000, 1000000))
1421        ),
1422        Maps.sortedMap(
1423            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("10.1")))
1424        )
1425    ),
1426
1427    /**
1428     * Languages Include:
1429     * <p>
1430     * <ul>
1431     * <li>Manx (gv)</li>
1432     * </ul>
1433     */
1434    FAMILY_22(
1435        (n) -> {
1436          int v = NumberUtils.numberOfDecimalPlaces(n);
1437          BigInteger i = NumberUtils.integerComponent(n);
1438
1439          // v = 0 and i % 10 = 1
1440          if (v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1))
1441            return ONE;
1442          // v = 0 and i % 10 = 2
1443          if (v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_2))
1444            return TWO;
1445          // v = 0 and i % 100 = 0,20,40,60,80
1446          if (v == 0 && inSet(i.mod(BIG_INTEGER_100), BIG_INTEGER_0, BIG_INTEGER_20, BIG_INTEGER_40, BIG_INTEGER_60, BIG_INTEGER_80))
1447            return FEW;
1448          // v != 0
1449          if (v != 0)
1450            return MANY;
1451
1452          return OTHER;
1453        },
1454        Sets.sortedSet(
1455            ONE,
1456            TWO,
1457            FEW,
1458            MANY,
1459            OTHER
1460        ),
1461        Maps.sortedMap(
1462            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 11, 21, 31, 41, 51, 61, 71, 101, 1001)),
1463            MapEntry.of(Cardinality.TWO, Range.ofInfiniteValues(2, 12, 22, 32, 42, 52, 62, 72, 102, 1002)),
1464            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000)),
1465            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, 18, 19, 23, 103, 1003))
1466        ),
1467        Maps.sortedMap(
1468            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1469        )
1470    ),
1471
1472    /**
1473     * Languages Include:
1474     * <p>
1475     * <ul>
1476     * <li>Hebrew (he)</li>
1477     * </ul>
1478     */
1479    FAMILY_23(
1480        (n) -> {
1481          int v = NumberUtils.numberOfDecimalPlaces(n);
1482          BigInteger i = NumberUtils.integerComponent(n);
1483
1484          // i = 1 and v = 0
1485          if (equal(i, BIG_INTEGER_1) && v == 0)
1486            return ONE;
1487          // i = 2 and v = 0
1488          if (equal(i, BIG_INTEGER_2) && v == 0)
1489            return TWO;
1490          // v = 0 and n != 0..10 and n % 10 = 0
1491          if (v == 0
1492              && notInRange(n, BIG_DECIMAL_0, BIG_DECIMAL_10)
1493              && equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_0))
1494            return MANY;
1495
1496          return OTHER;
1497        },
1498        Sets.sortedSet(
1499            ONE,
1500            TWO,
1501            MANY,
1502            OTHER
1503        ),
1504        Maps.sortedMap(
1505            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1506            MapEntry.of(Cardinality.TWO, Range.ofFiniteValues(2)),
1507            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000)),
1508            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 101, 1001))
1509        ),
1510        Maps.sortedMap(
1511            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1512        )
1513    ),
1514
1515    /**
1516     * Languages Include:
1517     * <p>
1518     * <ul>
1519     * <li>Icelandic (is)</li>
1520     * </ul>
1521     */
1522    FAMILY_24(
1523        (n) -> {
1524          BigInteger i = NumberUtils.integerComponent(n);
1525          BigInteger t = NumberUtils.fractionalComponent(n.stripTrailingZeros());
1526
1527          // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0
1528          if ((equal(t, BIG_INTEGER_0) && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(i.mod(BIG_INTEGER_100), BIG_INTEGER_11))
1529              || notEqual(t, BIG_INTEGER_0))
1530            return ONE;
1531
1532          return OTHER;
1533        },
1534        Sets.sortedSet(
1535            ONE,
1536            OTHER
1537        ),
1538        Maps.sortedMap(
1539            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
1540            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
1541        ),
1542        Maps.sortedMap(
1543            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1")))
1544        )
1545    ),
1546
1547    /**
1548     * Languages Include:
1549     * <p>
1550     * <ul>
1551     * <li>Colognian (ksh)</li>
1552     * </ul>
1553     */
1554    FAMILY_25(
1555        (n) -> {
1556          // n = 0
1557          if (equal(n, BIG_DECIMAL_0))
1558            return ZERO;
1559          // n = 1
1560          if (equal(n, BIG_DECIMAL_1))
1561            return ONE;
1562
1563          return OTHER;
1564        },
1565        Sets.sortedSet(
1566            ZERO,
1567            ONE,
1568            OTHER
1569        ),
1570        Maps.sortedMap(
1571            MapEntry.of(Cardinality.ZERO, Range.ofFiniteValues(0)),
1572            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1573            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1574        ),
1575        Maps.sortedMap(
1576            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7")))
1577        )
1578    ),
1579
1580    /**
1581     * Languages Include:
1582     * <p>
1583     * <ul>
1584     * <li>Langi (lag)</li>
1585     * </ul>
1586     */
1587    FAMILY_26(
1588        (n) -> {
1589          BigInteger i = NumberUtils.integerComponent(n);
1590
1591          // n = 0
1592          if (equal(n, BIG_DECIMAL_0))
1593            return ZERO;
1594          // i = 0,1 and n != 0
1595          if (inSet(i, BIG_INTEGER_0, BIG_INTEGER_1) && notEqual(n, BIG_DECIMAL_0))
1596            return ONE;
1597
1598          return OTHER;
1599        },
1600        Sets.sortedSet(
1601            ZERO,
1602            ONE,
1603            OTHER
1604        ),
1605        Maps.sortedMap(
1606            MapEntry.of(Cardinality.ZERO, Range.ofFiniteValues(0)),
1607            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1608            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1609        ),
1610        Maps.sortedMap(
1611            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"))),
1612            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("2.0"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7"), new BigDecimal("2.8"), new BigDecimal("2.9"), new BigDecimal("3.0"), new BigDecimal("3.1"), new BigDecimal("3.2"), new BigDecimal("3.3"), new BigDecimal("3.4"), new BigDecimal("3.5")))
1613        )
1614    ),
1615
1616    /**
1617     * Languages Include:
1618     * <p>
1619     * <ul>
1620     * <li>Lithuanian (lt)</li>
1621     * </ul>
1622     */
1623    FAMILY_27(
1624        (n) -> {
1625          BigInteger f = NumberUtils.fractionalComponent(n);
1626
1627          // n % 10 = 1 and n % 100 != 11..19
1628          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1)
1629              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_19))
1630            return ONE;
1631          // n % 10 = 2..9 and n % 100 != 11..19
1632          if (inRange(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_2, BIG_DECIMAL_9)
1633              && notInRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_19))
1634            return FEW;
1635          // f != 0
1636          if (notEqual(f, BIG_INTEGER_0))
1637            return MANY;
1638
1639          return OTHER;
1640        },
1641        Sets.sortedSet(
1642            ONE,
1643            FEW,
1644            MANY,
1645            OTHER
1646        ),
1647        Maps.sortedMap(
1648            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
1649            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 22, 23, 24, 25, 26, 27, 28, 29, 102, 1002)),
1650            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000))
1651        ),
1652        Maps.sortedMap(
1653            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1")))
1654        )
1655    ),
1656
1657    /**
1658     * Languages Include:
1659     * <p>
1660     * <ul>
1661     * <li>Macedonian (mk)</li>
1662     * </ul>
1663     */
1664    FAMILY_28(
1665        (n) -> {
1666          int v = NumberUtils.numberOfDecimalPlaces(n);
1667          BigInteger i = NumberUtils.integerComponent(n);
1668          BigInteger f = NumberUtils.fractionalComponent(n);
1669
1670          // v = 0 and i % 10 = 1 or f % 10 = 1
1671          if ((v == 0 && equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1))
1672              || equal(f.mod(BIG_INTEGER_10), BIG_INTEGER_1))
1673            return ONE;
1674
1675          return OTHER;
1676        },
1677        Sets.sortedSet(
1678            ONE,
1679            OTHER
1680        ),
1681        Maps.sortedMap(
1682            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 11, 21, 31, 41, 51, 61, 71, 101, 1001)),
1683            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1684        ),
1685        Maps.sortedMap(
1686            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("1.1"), new BigDecimal("2.1"), new BigDecimal("3.1"), new BigDecimal("4.1"), new BigDecimal("5.1"), new BigDecimal("6.1"), new BigDecimal("7.1"), new BigDecimal("10.1"), new BigDecimal("100.1"), new BigDecimal("1000.1"))),
1687            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7")))
1688        )
1689    ),
1690
1691    /**
1692     * Languages Include:
1693     * <p>
1694     * <ul>
1695     * <li>Maltese (mt)</li>
1696     * </ul>
1697     */
1698    FAMILY_29(
1699        (n) -> {
1700          // n = 1
1701          if (equal(n, BIG_DECIMAL_1))
1702            return ONE;
1703          // n = 0 or n % 100 = 2..10
1704          if (equal(n, BIG_DECIMAL_0) || inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_2, BIG_DECIMAL_10))
1705            return FEW;
1706          // n % 100 = 11..19
1707          if (inRange(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_19))
1708            return MANY;
1709
1710          return OTHER;
1711        },
1712        Sets.sortedSet(
1713            ONE,
1714            FEW,
1715            MANY,
1716            OTHER
1717        ),
1718        Maps.sortedMap(
1719            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1720            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 102, 103, 104, 105, 106, 107, 1002)),
1721            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(11, 12, 13, 14, 15, 16, 17, 18, 19, 111, 112, 113, 114, 115, 116, 117, 1011)),
1722            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 100, 1000, 10000, 100000, 1000000))
1723        ),
1724        Maps.sortedMap(
1725            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("10.1")))
1726        )
1727    ),
1728
1729    /**
1730     * Languages Include:
1731     * <p>
1732     * <ul>
1733     * <li>Polish (pl)</li>
1734     * </ul>
1735     */
1736    FAMILY_30(
1737        (n) -> {
1738          int v = NumberUtils.numberOfDecimalPlaces(n);
1739          BigInteger i = NumberUtils.integerComponent(n);
1740
1741          // i = 1 and v = 0
1742          if (equal(i, BIG_INTEGER_1) && v == 0)
1743            return ONE;
1744          // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
1745          if (v == 0
1746              && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_2, BIG_INTEGER_4)
1747              && notInRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_12, BIG_INTEGER_14))
1748            return FEW;
1749          // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14
1750          if ((v == 0 && notEqual(i, BIG_INTEGER_1) && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_0, BIG_INTEGER_1))
1751              || (v == 0 && inRange(i.mod(BIG_INTEGER_10), BIG_INTEGER_5, BIG_INTEGER_9))
1752              || (v == 0 && inRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_12, BIG_INTEGER_14)))
1753            return MANY;
1754
1755          return OTHER;
1756        },
1757        Sets.sortedSet(
1758            ONE,
1759            FEW,
1760            MANY,
1761            OTHER
1762        ),
1763        Maps.sortedMap(
1764            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(1)),
1765            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(2, 3, 4, 22, 23, 24, 32, 33, 34, 42, 43, 44, 52, 53, 54, 62, 102, 1002)),
1766            MapEntry.of(Cardinality.MANY, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1767        ),
1768        Maps.sortedMap(
1769            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1770        )
1771    ),
1772
1773    /**
1774     * Languages Include:
1775     * <p>
1776     * <ul>
1777     * <li>Portuguese (pt)</li>
1778     * </ul>
1779     */
1780    FAMILY_31(
1781        (n) -> {
1782          BigInteger i = NumberUtils.integerComponent(n);
1783
1784          // i = 0..1
1785          if (inRange(i, BIG_INTEGER_0, BIG_INTEGER_1))
1786            return ONE;
1787
1788          return OTHER;
1789        },
1790        Sets.sortedSet(
1791            ONE,
1792            OTHER
1793        ),
1794        Maps.sortedMap(
1795            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
1796            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1797        ),
1798        Maps.sortedMap(
1799            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"))),
1800            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("2.0"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7"), new BigDecimal("2.8"), new BigDecimal("2.9"), new BigDecimal("3.0"), new BigDecimal("3.1"), new BigDecimal("3.2"), new BigDecimal("3.3"), new BigDecimal("3.4"), new BigDecimal("3.5")))
1801        )
1802    ),
1803
1804    /**
1805     * Languages Include:
1806     * <p>
1807     * <ul>
1808     * <li>Tachelhit (shi)</li>
1809     * </ul>
1810     */
1811    FAMILY_32(
1812        (n) -> {
1813          BigInteger i = NumberUtils.integerComponent(n);
1814
1815          // i = 0 or n = 1
1816          if (equal(i, BIG_INTEGER_0) || equal(n, BIG_DECIMAL_1))
1817            return ONE;
1818          // n = 2..10
1819          if (inRange(n, BIG_DECIMAL_2, BIG_DECIMAL_10))
1820            return FEW;
1821
1822          return OTHER;
1823        },
1824        Sets.sortedSet(
1825            ONE,
1826            FEW,
1827            OTHER
1828        ),
1829        Maps.sortedMap(
1830            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
1831            MapEntry.of(Cardinality.FEW, Range.ofFiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10)),
1832            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 100, 1000, 10000, 100000, 1000000))
1833        ),
1834        Maps.sortedMap(
1835            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.0"), new BigDecimal("0.01"), new BigDecimal("0.02"), new BigDecimal("0.03"), new BigDecimal("0.04"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"))),
1836            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8"), new BigDecimal("1.9"), new BigDecimal("2.1"), new BigDecimal("2.2"), new BigDecimal("2.3"), new BigDecimal("2.4"), new BigDecimal("2.5"), new BigDecimal("2.6"), new BigDecimal("2.7"), new BigDecimal("10.1")))
1837        )
1838    ),
1839
1840    /**
1841     * Languages Include:
1842     * <p>
1843     * <ul>
1844     * <li>Sinhalese (si)</li>
1845     * </ul>
1846     */
1847    FAMILY_33(
1848        (n) -> {
1849          BigInteger i = NumberUtils.integerComponent(n);
1850          BigInteger f = NumberUtils.fractionalComponent(n);
1851
1852          // n = 0,1 or i = 0 and f = 1
1853          if (inSet(n, BIG_DECIMAL_0, BIG_DECIMAL_1)
1854              || (equal(i, BIG_INTEGER_0) && equal(f, BIG_INTEGER_1)))
1855            return ONE;
1856
1857          return OTHER;
1858        },
1859        Sets.sortedSet(
1860            ONE,
1861            OTHER
1862        ),
1863        Maps.sortedMap(
1864            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1)),
1865            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1866        ),
1867        Maps.sortedMap(
1868            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(new BigDecimal("0.0001"), new BigDecimal("0.001"), new BigDecimal("0.01"), new BigDecimal("0.1"))),
1869            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7"), new BigDecimal("1.8")))
1870        )
1871    ),
1872
1873    /**
1874     * Languages Include:
1875     * <p>
1876     * <ul>
1877     * <li>Slovenian (sl)</li>
1878     * </ul>
1879     */
1880    FAMILY_34(
1881        (n) -> {
1882          int v = NumberUtils.numberOfDecimalPlaces(n);
1883          BigInteger i = NumberUtils.integerComponent(n);
1884
1885          // v = 0 and i % 100 = 1
1886          if (v == 0 && equal(i.mod(BIG_INTEGER_100), BIG_INTEGER_1))
1887            return ONE;
1888          // v = 0 and i % 100 = 2
1889          if (v == 0 && equal(i.mod(BIG_INTEGER_100), BIG_INTEGER_2))
1890            return TWO;
1891          // v = 0 and i % 100 = 3..4 or v != 0
1892          if ((v == 0 && inRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_3, BIG_INTEGER_4))
1893              || v != 0)
1894            return FEW;
1895
1896          return OTHER;
1897        },
1898        Sets.sortedSet(
1899            ONE,
1900            TWO,
1901            FEW,
1902            OTHER
1903        ),
1904        Maps.sortedMap(
1905            MapEntry.of(Cardinality.ONE, Range.ofInfiniteValues(1, 101, 201, 301, 401, 501, 601, 701, 1001)),
1906            MapEntry.of(Cardinality.TWO, Range.ofInfiniteValues(2, 102, 202, 302, 402, 502, 602, 702, 1002)),
1907            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003)),
1908            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1909        ),
1910        Maps.sortedMap(
1911            MapEntry.of(Cardinality.FEW, Range.ofInfiniteValues(new BigDecimal("0.0"), new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.0"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5")))
1912        )
1913    ),
1914
1915    /**
1916     * Languages Include:
1917     * <p>
1918     * <ul>
1919     * <li>Central Atlas Tamazight (tzm)</li>
1920     * </ul>
1921     */
1922    FAMILY_35(
1923        (n) -> {
1924          // n = 0..1 or n = 11..99
1925          if (inRange(n, BIG_DECIMAL_0, BIG_DECIMAL_1) || inRange(n, BIG_DECIMAL_11, BIG_DECIMAL_99))
1926            return ONE;
1927
1928          return OTHER;
1929        },
1930        Sets.sortedSet(
1931            ONE,
1932            OTHER
1933        ),
1934        Maps.sortedMap(
1935            MapEntry.of(Cardinality.ONE, Range.ofFiniteValues(0, 1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)),
1936            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 101, 102, 103, 104, 105, 106, 1000, 10000, 100000, 1000000))
1937        ),
1938        Maps.sortedMap(
1939            MapEntry.of(Cardinality.OTHER, Range.ofInfiniteValues(new BigDecimal("0.1"), new BigDecimal("0.2"), new BigDecimal("0.3"), new BigDecimal("0.4"), new BigDecimal("0.5"), new BigDecimal("0.6"), new BigDecimal("0.7"), new BigDecimal("0.8"), new BigDecimal("0.9"), new BigDecimal("1.1"), new BigDecimal("1.2"), new BigDecimal("1.3"), new BigDecimal("1.4"), new BigDecimal("1.5"), new BigDecimal("1.6"), new BigDecimal("1.7")))
1940        )
1941    );
1942
1943    @Nonnull
1944    private static final Map<String, CardinalityFamily> CARDINALITY_FAMILIES_BY_LANGUAGE_CODE;
1945    @Nonnull
1946    private static final SortedSet<String> SUPPORTED_LANGUAGE_CODES;
1947
1948    @Nonnull
1949    private final Function<BigDecimal, Cardinality> cardinalityFunction;
1950    @Nonnull
1951    private final SortedSet<Cardinality> supportedCardinalities;
1952    @Nonnull
1953    private final SortedMap<Cardinality, Range<Integer>> exampleIntegerValuesByCardinality;
1954    @Nonnull
1955    private final SortedMap<Cardinality, Range<BigDecimal>> exampleDecimalValuesByCardinality;
1956
1957    /**
1958     * Constructs a cardinality family.
1959     *
1960     * @param cardinalityFunction               the cardinality-determining function for this cardinality family, not null
1961     * @param supportedCardinalities            the cardinalities supported by this family sorted by the natural ordering of {@link Cardinality}, not null
1962     * @param exampleIntegerValuesByCardinality a mapping of cardinalities to example integer values for this cardinality family sorted by the natural ordering of {@link Cardinality}, not null
1963     * @param exampleDecimalValuesByCardinality a mapping of cardinalities to example decimal values for this cardinality family sorted by the natural ordering of {@link Cardinality}, not null
1964     */
1965    CardinalityFamily(@Nonnull Function<BigDecimal, Cardinality> cardinalityFunction,
1966                      @Nonnull SortedSet<Cardinality> supportedCardinalities,
1967                      @Nonnull SortedMap<Cardinality, Range<Integer>> exampleIntegerValuesByCardinality,
1968                      @Nonnull SortedMap<Cardinality, Range<BigDecimal>> exampleDecimalValuesByCardinality) {
1969      requireNonNull(cardinalityFunction);
1970      requireNonNull(supportedCardinalities);
1971      requireNonNull(exampleIntegerValuesByCardinality);
1972      requireNonNull(exampleDecimalValuesByCardinality);
1973
1974      this.cardinalityFunction = cardinalityFunction;
1975      this.supportedCardinalities = supportedCardinalities;
1976      this.exampleIntegerValuesByCardinality = exampleIntegerValuesByCardinality;
1977      this.exampleDecimalValuesByCardinality = exampleDecimalValuesByCardinality;
1978    }
1979
1980    static {
1981      CARDINALITY_FAMILIES_BY_LANGUAGE_CODE = Collections.unmodifiableMap(new HashMap<String, CardinalityFamily>() {{
1982        put("af", CardinalityFamily.FAMILY_1); // Afrikaans
1983        put("ak", CardinalityFamily.FAMILY_4); // Akan
1984        put("am", CardinalityFamily.FAMILY_5); // Amharic
1985        put("ar", CardinalityFamily.FAMILY_9); // Arabic
1986        put("ars", CardinalityFamily.FAMILY_9); // Najdi Arabic
1987        put("as", CardinalityFamily.FAMILY_5); // Assamese
1988        put("asa", CardinalityFamily.FAMILY_1); // Asu
1989        put("ast", CardinalityFamily.FAMILY_3); // Asturian
1990        put("az", CardinalityFamily.FAMILY_1); // Azeri
1991        put("be", CardinalityFamily.FAMILY_16); // Belarusian
1992        put("bem", CardinalityFamily.FAMILY_1); // Bemba
1993        put("bez", CardinalityFamily.FAMILY_1); // Bena
1994        put("bg", CardinalityFamily.FAMILY_1); // Bulgarian
1995        put("bh", CardinalityFamily.FAMILY_4); // Bihari
1996        put("bm", CardinalityFamily.FAMILY_2); // Bambara
1997        put("bn", CardinalityFamily.FAMILY_5); // Bangla
1998        put("bo", CardinalityFamily.FAMILY_2); // Tibetan
1999        put("br", CardinalityFamily.FAMILY_17); // Breton
2000        put("brx", CardinalityFamily.FAMILY_1); // Bodo
2001        put("bs", CardinalityFamily.FAMILY_7); // Bosnian
2002        put("ca", CardinalityFamily.FAMILY_3); // Catalan
2003        put("ce", CardinalityFamily.FAMILY_1); // Chechen
2004        put("cgg", CardinalityFamily.FAMILY_1); // Chiga
2005        put("chr", CardinalityFamily.FAMILY_1); // Cherokee
2006        put("ckb", CardinalityFamily.FAMILY_1); // Central Kurdish
2007        put("cs", CardinalityFamily.FAMILY_10); // Czech
2008        put("cy", CardinalityFamily.FAMILY_18); // Welsh
2009        put("da", CardinalityFamily.FAMILY_19); // Danish
2010        put("de", CardinalityFamily.FAMILY_3); // German
2011        put("dsb", CardinalityFamily.FAMILY_11); // Lower Sorbian
2012        put("dv", CardinalityFamily.FAMILY_1); // Divehi
2013        put("dz", CardinalityFamily.FAMILY_2); // Dzongkha
2014        put("ee", CardinalityFamily.FAMILY_1); // Ewe
2015        put("el", CardinalityFamily.FAMILY_1); // Greek
2016        put("en", CardinalityFamily.FAMILY_3); // English
2017        put("eo", CardinalityFamily.FAMILY_1); // Esperanto
2018        put("es", CardinalityFamily.FAMILY_1); // Spanish
2019        put("et", CardinalityFamily.FAMILY_3); // Estonian
2020        put("eu", CardinalityFamily.FAMILY_1); // Basque
2021        put("fa", CardinalityFamily.FAMILY_5); // Persian
2022        put("ff", CardinalityFamily.FAMILY_8); // Fulah
2023        put("fi", CardinalityFamily.FAMILY_3); // Finnish
2024        put("fil", CardinalityFamily.FAMILY_12); // Filipino
2025        put("fo", CardinalityFamily.FAMILY_1); // Faroese
2026        put("fr", CardinalityFamily.FAMILY_8); // French
2027        put("fur", CardinalityFamily.FAMILY_1); // Friulian
2028        put("fy", CardinalityFamily.FAMILY_3); // Western Frisian
2029        put("ga", CardinalityFamily.FAMILY_20); // Irish
2030        put("gd", CardinalityFamily.FAMILY_21); // Scottish Gaelic
2031        put("gl", CardinalityFamily.FAMILY_3); // Galician
2032        put("gsw", CardinalityFamily.FAMILY_1); // Swiss German
2033        put("gu", CardinalityFamily.FAMILY_5); // Gujarati
2034        put("guw", CardinalityFamily.FAMILY_4); // Gun
2035        put("gv", CardinalityFamily.FAMILY_22); // Manx
2036        put("ha", CardinalityFamily.FAMILY_1); // Hausa
2037        put("haw", CardinalityFamily.FAMILY_1); // Hawaiian
2038        put("he", CardinalityFamily.FAMILY_23); // Hebrew
2039        put("hi", CardinalityFamily.FAMILY_5); // Hindi
2040        put("hr", CardinalityFamily.FAMILY_7); // Croatian
2041        put("hsb", CardinalityFamily.FAMILY_11); // Upper Sorbian
2042        put("hu", CardinalityFamily.FAMILY_1); // Hungarian
2043        put("hy", CardinalityFamily.FAMILY_8); // Armenian
2044        put("id", CardinalityFamily.FAMILY_2); // Indonesian
2045        put("ig", CardinalityFamily.FAMILY_2); // Igbo
2046        put("ii", CardinalityFamily.FAMILY_2); // Sichuan Yi
2047        put("is", CardinalityFamily.FAMILY_24); // Icelandic
2048        put("it", CardinalityFamily.FAMILY_3); // Italian
2049        put("iu", CardinalityFamily.FAMILY_6); // Inuktitut
2050        put("ja", CardinalityFamily.FAMILY_2); // Japanese
2051        put("jbo", CardinalityFamily.FAMILY_2); // Lojban
2052        put("jgo", CardinalityFamily.FAMILY_1); // Ngomba
2053        put("jmc", CardinalityFamily.FAMILY_1); // Machame
2054        put("jv", CardinalityFamily.FAMILY_2); // Javanese
2055        put("jw", CardinalityFamily.FAMILY_2); // Javanese
2056        put("ka", CardinalityFamily.FAMILY_1); // Georgian
2057        put("kab", CardinalityFamily.FAMILY_8); // Kabyle
2058        put("kaj", CardinalityFamily.FAMILY_1); // Jju
2059        put("kcg", CardinalityFamily.FAMILY_1); // Tyap
2060        put("kde", CardinalityFamily.FAMILY_2); // Makonde
2061        put("kea", CardinalityFamily.FAMILY_2); // Kabuverdianu
2062        put("kk", CardinalityFamily.FAMILY_1); // Kazakh
2063        put("kkj", CardinalityFamily.FAMILY_1); // Kako
2064        put("kl", CardinalityFamily.FAMILY_1); // Greenlandic
2065        put("km", CardinalityFamily.FAMILY_2); // Khmer
2066        put("kn", CardinalityFamily.FAMILY_5); // Kannada
2067        put("ko", CardinalityFamily.FAMILY_2); // Korean
2068        put("ks", CardinalityFamily.FAMILY_1); // Kashmiri
2069        put("ksb", CardinalityFamily.FAMILY_1); // Shambala
2070        put("ksh", CardinalityFamily.FAMILY_25); // Colognian
2071        put("ku", CardinalityFamily.FAMILY_1); // Kurdish
2072        put("kw", CardinalityFamily.FAMILY_6); // Cornish
2073        put("ky", CardinalityFamily.FAMILY_1); // Kirghiz
2074        put("lag", CardinalityFamily.FAMILY_26); // Langi
2075        put("lb", CardinalityFamily.FAMILY_1); // Luxembourgish
2076        put("lg", CardinalityFamily.FAMILY_1); // Ganda
2077        put("lkt", CardinalityFamily.FAMILY_2); // Lakota
2078        put("ln", CardinalityFamily.FAMILY_4); // Lingala
2079        put("lo", CardinalityFamily.FAMILY_2); // Lao
2080        put("lt", CardinalityFamily.FAMILY_27); // Lithuanian
2081        put("lv", CardinalityFamily.FAMILY_13); // Latvian
2082        put("mas", CardinalityFamily.FAMILY_1); // Masai
2083        put("mg", CardinalityFamily.FAMILY_4); // Malagasy
2084        put("mgo", CardinalityFamily.FAMILY_1); // Metaʼ
2085        put("mk", CardinalityFamily.FAMILY_28); // Macedonian
2086        put("ml", CardinalityFamily.FAMILY_1); // Malayalam
2087        put("mn", CardinalityFamily.FAMILY_1); // Mongolian
2088        put("mo", CardinalityFamily.FAMILY_14); // Moldovan
2089        put("mr", CardinalityFamily.FAMILY_5); // Marathi
2090        put("ms", CardinalityFamily.FAMILY_2); // Malay
2091        put("mt", CardinalityFamily.FAMILY_29); // Maltese
2092        put("my", CardinalityFamily.FAMILY_2); // Burmese
2093        put("nah", CardinalityFamily.FAMILY_1); // Nahuatl
2094        put("naq", CardinalityFamily.FAMILY_6); // Nama
2095        put("nb", CardinalityFamily.FAMILY_1); // Norwegian Bokmål
2096        put("nd", CardinalityFamily.FAMILY_1); // North Ndebele
2097        put("ne", CardinalityFamily.FAMILY_1); // Nepali
2098        put("nl", CardinalityFamily.FAMILY_3); // Dutch
2099        put("nn", CardinalityFamily.FAMILY_1); // Norwegian Nynorsk
2100        put("nnh", CardinalityFamily.FAMILY_1); // Ngiemboon
2101        put("no", CardinalityFamily.FAMILY_1); // Norwegian
2102        put("nqo", CardinalityFamily.FAMILY_2); // N’Ko
2103        put("nr", CardinalityFamily.FAMILY_1); // South Ndebele
2104        put("nso", CardinalityFamily.FAMILY_4); // Northern Sotho
2105        put("ny", CardinalityFamily.FAMILY_1); // Nyanja
2106        put("nyn", CardinalityFamily.FAMILY_1); // Nyankole
2107        put("om", CardinalityFamily.FAMILY_1); // Oromo
2108        put("or", CardinalityFamily.FAMILY_1); // Odia
2109        put("os", CardinalityFamily.FAMILY_1); // Ossetian
2110        put("pa", CardinalityFamily.FAMILY_4); // Punjabi
2111        put("pap", CardinalityFamily.FAMILY_1); // Papiamento
2112        put("pl", CardinalityFamily.FAMILY_30); // Polish
2113        put("prg", CardinalityFamily.FAMILY_13); // Prussian
2114        put("ps", CardinalityFamily.FAMILY_1); // Pushto
2115        put("pt", CardinalityFamily.FAMILY_31); // Portuguese
2116        put("rm", CardinalityFamily.FAMILY_1); // Romansh
2117        put("ro", CardinalityFamily.FAMILY_14); // Romanian
2118        put("rof", CardinalityFamily.FAMILY_1); // Rombo
2119        put("root", CardinalityFamily.FAMILY_2); // Root
2120        put("ru", CardinalityFamily.FAMILY_15); // Russian
2121        put("rwk", CardinalityFamily.FAMILY_1); // Rwa
2122        put("sah", CardinalityFamily.FAMILY_2); // Sakha
2123        put("saq", CardinalityFamily.FAMILY_1); // Samburu
2124        put("sdh", CardinalityFamily.FAMILY_1); // Southern Kurdish
2125        put("se", CardinalityFamily.FAMILY_6); // Northern Sami
2126        put("seh", CardinalityFamily.FAMILY_1); // Sena
2127        put("ses", CardinalityFamily.FAMILY_2); // Koyraboro Senni
2128        put("sg", CardinalityFamily.FAMILY_2); // Sango
2129        put("sh", CardinalityFamily.FAMILY_7); // Serbo-Croatian
2130        put("shi", CardinalityFamily.FAMILY_32); // Tachelhit
2131        put("si", CardinalityFamily.FAMILY_33); // Sinhalese
2132        put("sk", CardinalityFamily.FAMILY_10); // Slovak
2133        put("sl", CardinalityFamily.FAMILY_34); // Slovenian
2134        put("sma", CardinalityFamily.FAMILY_6); // Southern Sami
2135        put("smi", CardinalityFamily.FAMILY_6); // Sami
2136        put("smj", CardinalityFamily.FAMILY_6); // Lule Sami
2137        put("smn", CardinalityFamily.FAMILY_6); // Inari Sami
2138        put("sms", CardinalityFamily.FAMILY_6); // Skolt Sami
2139        put("sn", CardinalityFamily.FAMILY_1); // Shona
2140        put("so", CardinalityFamily.FAMILY_1); // Somali
2141        put("sq", CardinalityFamily.FAMILY_1); // Albanian
2142        put("sr", CardinalityFamily.FAMILY_7); // Serbian
2143        put("ss", CardinalityFamily.FAMILY_1); // Swati
2144        put("ssy", CardinalityFamily.FAMILY_1); // Saho
2145        put("st", CardinalityFamily.FAMILY_1); // Southern Sotho
2146        put("sv", CardinalityFamily.FAMILY_3); // Swedish
2147        put("sw", CardinalityFamily.FAMILY_3); // Swahili
2148        put("syr", CardinalityFamily.FAMILY_1); // Syriac
2149        put("ta", CardinalityFamily.FAMILY_1); // Tamil
2150        put("te", CardinalityFamily.FAMILY_1); // Telugu
2151        put("teo", CardinalityFamily.FAMILY_1); // Teso
2152        put("th", CardinalityFamily.FAMILY_2); // Thai
2153        put("ti", CardinalityFamily.FAMILY_4); // Tigrinya
2154        put("tig", CardinalityFamily.FAMILY_1); // Tigre
2155        put("tk", CardinalityFamily.FAMILY_1); // Turkmen
2156        put("tl", CardinalityFamily.FAMILY_12); // Tagalog
2157        put("tn", CardinalityFamily.FAMILY_1); // Tswana
2158        put("to", CardinalityFamily.FAMILY_2); // Tongan
2159        put("tr", CardinalityFamily.FAMILY_1); // Turkish
2160        put("ts", CardinalityFamily.FAMILY_1); // Tsonga
2161        put("tzm", CardinalityFamily.FAMILY_35); // Central Atlas Tamazight
2162        put("ug", CardinalityFamily.FAMILY_1); // Uighur
2163        put("uk", CardinalityFamily.FAMILY_15); // Ukrainian
2164        put("ur", CardinalityFamily.FAMILY_3); // Urdu
2165        put("uz", CardinalityFamily.FAMILY_1); // Uzbek
2166        put("ve", CardinalityFamily.FAMILY_1); // Venda
2167        put("vi", CardinalityFamily.FAMILY_2); // Vietnamese
2168        put("vo", CardinalityFamily.FAMILY_1); // Volapük
2169        put("vun", CardinalityFamily.FAMILY_1); // Vunjo
2170        put("wa", CardinalityFamily.FAMILY_4); // Walloon
2171        put("wae", CardinalityFamily.FAMILY_1); // Walser
2172        put("wo", CardinalityFamily.FAMILY_2); // Wolof
2173        put("xh", CardinalityFamily.FAMILY_1); // Xhosa
2174        put("xog", CardinalityFamily.FAMILY_1); // Soga
2175        put("yi", CardinalityFamily.FAMILY_3); // Yiddish
2176        put("yo", CardinalityFamily.FAMILY_2); // Yoruba
2177        put("yue", CardinalityFamily.FAMILY_2); // Cantonese
2178        put("zh", CardinalityFamily.FAMILY_2); // Mandarin Chinese
2179        put("zu", CardinalityFamily.FAMILY_5); // Zulu
2180      }});
2181
2182      // Language codes are in English - force collation for sorting
2183      SortedSet<String> supportedLanguageCodes = new TreeSet<>(Collator.getInstance(Locale.ENGLISH));
2184      supportedLanguageCodes.addAll(CARDINALITY_FAMILIES_BY_LANGUAGE_CODE.keySet());
2185
2186      SUPPORTED_LANGUAGE_CODES = Collections.unmodifiableSortedSet(supportedLanguageCodes);
2187    }
2188
2189    /**
2190     * Gets the cardinality-determining function for this cardinality family.
2191     * <p>
2192     * The function takes a numeric value as input and returns the appropriate cardinal form.
2193     * <p>
2194     * The function's input must not be null and its output is guaranteed non-null.
2195     *
2196     * @return the cardinality-determining function for this cardinality family, not null
2197     */
2198    @Nonnull
2199    Function<BigDecimal, Cardinality> getCardinalityFunction() {
2200      return cardinalityFunction;
2201    }
2202
2203    /**
2204     * Gets the cardinalities supported by this cardinality family.
2205     * <p>
2206     * There will always be at least one value - {@link Cardinality#OTHER} - in the set.
2207     * <p>
2208     * The set's values are sorted by the natural ordering of the {@link Cardinality} enumeration.
2209     *
2210     * @return the cardinalities supported by this cardinality family, not null
2211     */
2212    @Nonnull
2213    SortedSet<Cardinality> getSupportedCardinalities() {
2214      return supportedCardinalities;
2215    }
2216
2217    /**
2218     * Gets a mapping of cardinalities to example integer values for this cardinality family.
2219     * <p>
2220     * The map may be empty.
2221     * <p>
2222     * The map's keys are sorted by the natural ordering of the {@link Cardinality} enumeration.
2223     *
2224     * @return a mapping of cardinalities to example integer values, not null
2225     */
2226    @Nonnull
2227    SortedMap<Cardinality, Range<Integer>> getExampleIntegerValuesByCardinality() {
2228      return exampleIntegerValuesByCardinality;
2229    }
2230
2231    /**
2232     * Gets a mapping of cardinalities to example decimal values for this cardinality family.
2233     * <p>
2234     * The map may be empty.
2235     * <p>
2236     * The map's keys are sorted by the natural ordering of the {@link Cardinality} enumeration.
2237     *
2238     * @return a mapping of cardinalities to example decimal values, not null
2239     */
2240    @Nonnull
2241    SortedMap<Cardinality, Range<BigDecimal>> getExampleDecimalValuesByCardinality() {
2242      return exampleDecimalValuesByCardinality;
2243    }
2244
2245    /**
2246     * Gets the ISO 639 language codes for which cardinality operations are supported.
2247     * <p>
2248     * The set's values are ISO 639 codes and therefore sorted using English collation.
2249     *
2250     * @return the ISO 639 language codes for which cardinality operations are supported, not null
2251     */
2252    @Nonnull
2253    static SortedSet<String> getSupportedLanguageCodes() {
2254      return SUPPORTED_LANGUAGE_CODES;
2255    }
2256
2257    /**
2258     * Gets an appropriate plural cardinality family for the given locale.
2259     *
2260     * @param locale the locale to check, not null
2261     * @return the appropriate plural cardinality family (if one exists) for the given locale, not null
2262     */
2263    @Nonnull
2264    static Optional<CardinalityFamily> cardinalityFamilyForLocale(@Nonnull Locale locale) {
2265      requireNonNull(locale);
2266
2267      String language = LocaleUtils.normalizedLanguage(locale).orElse(null);
2268      String country = locale.getCountry();
2269
2270      CardinalityFamily cardinalityFamily = null;
2271
2272      if (language != null && country != null)
2273        cardinalityFamily = CARDINALITY_FAMILIES_BY_LANGUAGE_CODE.get(format("%s-%s", language, country));
2274
2275      if (cardinalityFamily != null)
2276        return Optional.of(cardinalityFamily);
2277
2278      if (language != null)
2279        cardinalityFamily = CARDINALITY_FAMILIES_BY_LANGUAGE_CODE.get(language);
2280
2281      return Optional.ofNullable(cardinalityFamily);
2282    }
2283  }
2284
2285
2286  enum CardinalityRangeFamily {
2287    /**
2288     * Languages Include:
2289     * <p>
2290     * <ul>
2291     * <li>Akan (ak)</li>
2292     * <li>Najdi Arabic (ars)</li>
2293     * <li>Assamese (as)</li>
2294     * <li>Asu (asa)</li>
2295     * <li>Asturian (ast)</li>
2296     * <li>Bemba (bem)</li>
2297     * <li>Bena (bez)</li>
2298     * <li>Bihari (bh)</li>
2299     * <li>Bambara (bm)</li>
2300     * <li>Tibetan (bo)</li>
2301     * <li>Breton (br)</li>
2302     * <li>Bodo (brx)</li>
2303     * <li>Chechen (ce)</li>
2304     * <li>Chiga (cgg)</li>
2305     * <li>Cherokee (chr)</li>
2306     * <li>Central Kurdish (ckb)</li>
2307     * <li>Lower Sorbian (dsb)</li>
2308     * <li>Divehi (dv)</li>
2309     * <li>Dzongkha (dz)</li>
2310     * <li>Ewe (ee)</li>
2311     * <li>Esperanto (eo)</li>
2312     * <li>Fulah (ff)</li>
2313     * <li>Faroese (fo)</li>
2314     * <li>Friulian (fur)</li>
2315     * <li>Western Frisian (fy)</li>
2316     * <li>Scottish Gaelic (gd)</li>
2317     * <li>Gun (guw)</li>
2318     * <li>Manx (gv)</li>
2319     * <li>Hausa (ha)</li>
2320     * <li>Hawaiian (haw)</li>
2321     * <li>Upper Sorbian (hsb)</li>
2322     * <li>Igbo (ig)</li>
2323     * <li>Sichuan Yi (ii)</li>
2324     * <li>Inuktitut (iu)</li>
2325     * <li>Lojban (jbo)</li>
2326     * <li>Ngomba (jgo)</li>
2327     * <li>Machame (jmc)</li>
2328     * <li>Javanese (jv)</li>
2329     * <li>Javanese (jw)</li>
2330     * <li>Kabyle (kab)</li>
2331     * <li>Jju (kaj)</li>
2332     * <li>Tyap (kcg)</li>
2333     * <li>Makonde (kde)</li>
2334     * <li>Kabuverdianu (kea)</li>
2335     * <li>Kako (kkj)</li>
2336     * <li>Greenlandic (kl)</li>
2337     * <li>Kashmiri (ks)</li>
2338     * <li>Shambala (ksb)</li>
2339     * <li>Colognian (ksh)</li>
2340     * <li>Kurdish (ku)</li>
2341     * <li>Cornish (kw)</li>
2342     * <li>Langi (lag)</li>
2343     * <li>Luxembourgish (lb)</li>
2344     * <li>Ganda (lg)</li>
2345     * <li>Lakota (lkt)</li>
2346     * <li>Lingala (ln)</li>
2347     * <li>Masai (mas)</li>
2348     * <li>Malagasy (mg)</li>
2349     * <li>Metaʼ (mgo)</li>
2350     * <li>Moldovan (mo)</li>
2351     * <li>Maltese (mt)</li>
2352     * <li>Nahuatl (nah)</li>
2353     * <li>Nama (naq)</li>
2354     * <li>North Ndebele (nd)</li>
2355     * <li>Norwegian Nynorsk (nn)</li>
2356     * <li>Ngiemboon (nnh)</li>
2357     * <li>Norwegian (no)</li>
2358     * <li>N’Ko (nqo)</li>
2359     * <li>South Ndebele (nr)</li>
2360     * <li>Northern Sotho (nso)</li>
2361     * <li>Nyanja (ny)</li>
2362     * <li>Nyankole (nyn)</li>
2363     * <li>Oromo (om)</li>
2364     * <li>Odia (or)</li>
2365     * <li>Ossetian (os)</li>
2366     * <li>Papiamento (pap)</li>
2367     * <li>Prussian (prg)</li>
2368     * <li>Pushto (ps)</li>
2369     * <li>Romansh (rm)</li>
2370     * <li>Rombo (rof)</li>
2371     * <li>Root (root)</li>
2372     * <li>Rwa (rwk)</li>
2373     * <li>Sakha (sah)</li>
2374     * <li>Samburu (saq)</li>
2375     * <li>Southern Kurdish (sdh)</li>
2376     * <li>Northern Sami (se)</li>
2377     * <li>Sena (seh)</li>
2378     * <li>Koyraboro Senni (ses)</li>
2379     * <li>Sango (sg)</li>
2380     * <li>Serbo-Croatian (sh)</li>
2381     * <li>Tachelhit (shi)</li>
2382     * <li>Southern Sami (sma)</li>
2383     * <li>Sami (smi)</li>
2384     * <li>Lule Sami (smj)</li>
2385     * <li>Inari Sami (smn)</li>
2386     * <li>Skolt Sami (sms)</li>
2387     * <li>Shona (sn)</li>
2388     * <li>Somali (so)</li>
2389     * <li>Swati (ss)</li>
2390     * <li>Saho (ssy)</li>
2391     * <li>Southern Sotho (st)</li>
2392     * <li>Syriac (syr)</li>
2393     * <li>Teso (teo)</li>
2394     * <li>Tigrinya (ti)</li>
2395     * <li>Tigre (tig)</li>
2396     * <li>Turkmen (tk)</li>
2397     * <li>Tagalog (tl)</li>
2398     * <li>Tswana (tn)</li>
2399     * <li>Tongan (to)</li>
2400     * <li>Tsonga (ts)</li>
2401     * <li>Central Atlas Tamazight (tzm)</li>
2402     * <li>Venda (ve)</li>
2403     * <li>Volapük (vo)</li>
2404     * <li>Vunjo (vun)</li>
2405     * <li>Walloon (wa)</li>
2406     * <li>Walser (wae)</li>
2407     * <li>Wolof (wo)</li>
2408     * <li>Xhosa (xh)</li>
2409     * <li>Soga (xog)</li>
2410     * <li>Yiddish (yi)</li>
2411     * <li>Yoruba (yo)</li>
2412     * </ul>
2413     */
2414    FAMILY_1(
2415        // There are no cardinality ranges for this family
2416        Collections.emptySortedMap()
2417    ),
2418
2419    /**
2420     * Languages Include:
2421     * <p>
2422     * <ul>
2423     * <li>Azeri (az)</li>
2424     * <li>German (de)</li>
2425     * <li>Greek (el)</li>
2426     * <li>Galician (gl)</li>
2427     * <li>Swiss German (gsw)</li>
2428     * <li>Hungarian (hu)</li>
2429     * <li>Italian (it)</li>
2430     * <li>Kazakh (kk)</li>
2431     * <li>Kirghiz (ky)</li>
2432     * <li>Malayalam (ml)</li>
2433     * <li>Mongolian (mn)</li>
2434     * <li>Nepali (ne)</li>
2435     * <li>Dutch (nl)</li>
2436     * <li>Albanian (sq)</li>
2437     * <li>Swahili (sw)</li>
2438     * <li>Tamil (ta)</li>
2439     * <li>Telugu (te)</li>
2440     * <li>Turkish (tr)</li>
2441     * <li>Uighur (ug)</li>
2442     * <li>Uzbek (uz)</li>
2443     * </ul>
2444     */
2445    FAMILY_2(
2446        Maps.sortedMap(
2447            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2448            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2449            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2450        )
2451    ),
2452
2453    /**
2454     * Languages Include:
2455     * <p>
2456     * <ul>
2457     * <li>Afrikaans (af)</li>
2458     * <li>Bulgarian (bg)</li>
2459     * <li>Catalan (ca)</li>
2460     * <li>English (en)</li>
2461     * <li>Spanish (es)</li>
2462     * <li>Estonian (et)</li>
2463     * <li>Basque (eu)</li>
2464     * <li>Finnish (fi)</li>
2465     * <li>Norwegian Bokmål (nb)</li>
2466     * <li>Swedish (sv)</li>
2467     * <li>Urdu (ur)</li>
2468     * </ul>
2469     */
2470    FAMILY_3(
2471        Maps.sortedMap(
2472            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2473            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2474            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2475        )
2476    ),
2477
2478    /**
2479     * Languages Include:
2480     * <p>
2481     * <ul>
2482     * <li>Indonesian (id)</li>
2483     * <li>Japanese (ja)</li>
2484     * <li>Khmer (km)</li>
2485     * <li>Korean (ko)</li>
2486     * <li>Lao (lo)</li>
2487     * <li>Malay (ms)</li>
2488     * <li>Burmese (my)</li>
2489     * <li>Thai (th)</li>
2490     * <li>Vietnamese (vi)</li>
2491     * <li>Cantonese (yue)</li>
2492     * <li>Mandarin Chinese (zh)</li>
2493     * </ul>
2494     */
2495    FAMILY_4(
2496        Maps.sortedMap(
2497            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2498        )
2499    ),
2500
2501    /**
2502     * Languages Include:
2503     * <p>
2504     * <ul>
2505     * <li>Amharic (am)</li>
2506     * <li>Bangla (bn)</li>
2507     * <li>French (fr)</li>
2508     * <li>Gujarati (gu)</li>
2509     * <li>Hindi (hi)</li>
2510     * <li>Armenian (hy)</li>
2511     * <li>Kannada (kn)</li>
2512     * <li>Marathi (mr)</li>
2513     * <li>Zulu (zu)</li>
2514     * </ul>
2515     */
2516    FAMILY_5(
2517        Maps.sortedMap(
2518            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2519            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2520            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2521        )
2522    ),
2523
2524    /**
2525     * Languages Include:
2526     * <p>
2527     * <ul>
2528     * <li>Danish (da)</li>
2529     * <li>Filipino (fil)</li>
2530     * <li>Icelandic (is)</li>
2531     * <li>Punjabi (pa)</li>
2532     * <li>Portuguese (pt)</li>
2533     * </ul>
2534     */
2535    FAMILY_6(
2536        Maps.sortedMap(
2537            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2538            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2539            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2540            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2541        )
2542    ),
2543
2544    /**
2545     * Languages Include:
2546     * <p>
2547     * <ul>
2548     * <li>Belarusian (be)</li>
2549     * <li>Lithuanian (lt)</li>
2550     * <li>Russian (ru)</li>
2551     * <li>Ukrainian (uk)</li>
2552     * </ul>
2553     */
2554    FAMILY_7(
2555        Maps.sortedMap(
2556            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2557            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2558            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2559            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2560            MapEntry.of(CardinalityRange.of(FEW, ONE), ONE),
2561            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2562            MapEntry.of(CardinalityRange.of(FEW, MANY), MANY),
2563            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2564            MapEntry.of(CardinalityRange.of(MANY, ONE), ONE),
2565            MapEntry.of(CardinalityRange.of(MANY, FEW), FEW),
2566            MapEntry.of(CardinalityRange.of(MANY, MANY), MANY),
2567            MapEntry.of(CardinalityRange.of(MANY, OTHER), OTHER),
2568            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2569            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2570            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2571            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2572        )
2573    ),
2574
2575    /**
2576     * Languages Include:
2577     * <p>
2578     * <ul>
2579     * <li>Bosnian (bs)</li>
2580     * <li>Croatian (hr)</li>
2581     * <li>Serbian (sr)</li>
2582     * </ul>
2583     */
2584    FAMILY_8(
2585        Maps.sortedMap(
2586            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2587            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2588            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2589            MapEntry.of(CardinalityRange.of(FEW, ONE), ONE),
2590            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2591            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2592            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2593            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2594            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2595        )
2596    ),
2597
2598    /**
2599     * Languages Include:
2600     * <p>
2601     * <ul>
2602     * <li>Czech (cs)</li>
2603     * <li>Polish (pl)</li>
2604     * <li>Slovak (sk)</li>
2605     * </ul>
2606     */
2607    FAMILY_9(
2608        Maps.sortedMap(
2609            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2610            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2611            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2612            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2613            MapEntry.of(CardinalityRange.of(FEW, MANY), MANY),
2614            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2615            MapEntry.of(CardinalityRange.of(MANY, ONE), ONE),
2616            MapEntry.of(CardinalityRange.of(MANY, FEW), FEW),
2617            MapEntry.of(CardinalityRange.of(MANY, MANY), MANY),
2618            MapEntry.of(CardinalityRange.of(MANY, OTHER), OTHER),
2619            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2620            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2621            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2622            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2623        )
2624    ),
2625
2626    /**
2627     * Languages Include:
2628     * <p>
2629     * <ul>
2630     * <li>Arabic (ar)</li>
2631     * </ul>
2632     */
2633    FAMILY_10(
2634        Maps.sortedMap(
2635            MapEntry.of(CardinalityRange.of(ZERO, ONE), ZERO),
2636            MapEntry.of(CardinalityRange.of(ZERO, TWO), ZERO),
2637            MapEntry.of(CardinalityRange.of(ZERO, FEW), FEW),
2638            MapEntry.of(CardinalityRange.of(ZERO, MANY), MANY),
2639            MapEntry.of(CardinalityRange.of(ZERO, OTHER), OTHER),
2640            MapEntry.of(CardinalityRange.of(ONE, TWO), OTHER),
2641            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2642            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2643            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2644            MapEntry.of(CardinalityRange.of(TWO, FEW), FEW),
2645            MapEntry.of(CardinalityRange.of(TWO, MANY), MANY),
2646            MapEntry.of(CardinalityRange.of(TWO, OTHER), OTHER),
2647            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2648            MapEntry.of(CardinalityRange.of(FEW, MANY), MANY),
2649            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2650            MapEntry.of(CardinalityRange.of(MANY, FEW), FEW),
2651            MapEntry.of(CardinalityRange.of(MANY, MANY), MANY),
2652            MapEntry.of(CardinalityRange.of(MANY, OTHER), OTHER),
2653            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2654            MapEntry.of(CardinalityRange.of(OTHER, TWO), OTHER),
2655            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2656            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2657            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2658        )
2659    ),
2660
2661    /**
2662     * Languages Include:
2663     * <p>
2664     * <ul>
2665     * <li>Welsh (cy)</li>
2666     * </ul>
2667     */
2668    FAMILY_11(
2669        Maps.sortedMap(
2670            MapEntry.of(CardinalityRange.of(ZERO, ONE), ONE),
2671            MapEntry.of(CardinalityRange.of(ZERO, TWO), TWO),
2672            MapEntry.of(CardinalityRange.of(ZERO, FEW), FEW),
2673            MapEntry.of(CardinalityRange.of(ZERO, MANY), MANY),
2674            MapEntry.of(CardinalityRange.of(ZERO, OTHER), OTHER),
2675            MapEntry.of(CardinalityRange.of(ONE, TWO), TWO),
2676            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2677            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2678            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2679            MapEntry.of(CardinalityRange.of(TWO, FEW), FEW),
2680            MapEntry.of(CardinalityRange.of(TWO, MANY), MANY),
2681            MapEntry.of(CardinalityRange.of(TWO, OTHER), OTHER),
2682            MapEntry.of(CardinalityRange.of(FEW, MANY), MANY),
2683            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2684            MapEntry.of(CardinalityRange.of(MANY, OTHER), OTHER),
2685            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2686            MapEntry.of(CardinalityRange.of(OTHER, TWO), TWO),
2687            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2688            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2689            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2690        )
2691    ),
2692
2693    /**
2694     * Languages Include:
2695     * <p>
2696     * <ul>
2697     * <li>Persian (fa)</li>
2698     * </ul>
2699     */
2700    FAMILY_12(
2701        Maps.sortedMap(
2702            MapEntry.of(CardinalityRange.of(ONE, ONE), OTHER),
2703            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2704            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2705        )
2706    ),
2707
2708    /**
2709     * Languages Include:
2710     * <p>
2711     * <ul>
2712     * <li>Irish (ga)</li>
2713     * </ul>
2714     */
2715    FAMILY_13(
2716        Maps.sortedMap(
2717            MapEntry.of(CardinalityRange.of(ONE, TWO), TWO),
2718            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2719            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2720            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2721            MapEntry.of(CardinalityRange.of(TWO, FEW), FEW),
2722            MapEntry.of(CardinalityRange.of(TWO, MANY), MANY),
2723            MapEntry.of(CardinalityRange.of(TWO, OTHER), OTHER),
2724            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2725            MapEntry.of(CardinalityRange.of(FEW, MANY), MANY),
2726            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2727            MapEntry.of(CardinalityRange.of(MANY, MANY), MANY),
2728            MapEntry.of(CardinalityRange.of(MANY, OTHER), OTHER),
2729            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2730            MapEntry.of(CardinalityRange.of(OTHER, TWO), TWO),
2731            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2732            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2733            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2734        )
2735    ),
2736
2737    /**
2738     * Languages Include:
2739     * <p>
2740     * <ul>
2741     * <li>Hebrew (he)</li>
2742     * </ul>
2743     */
2744    FAMILY_14(
2745        Maps.sortedMap(
2746            MapEntry.of(CardinalityRange.of(ONE, TWO), OTHER),
2747            MapEntry.of(CardinalityRange.of(ONE, MANY), MANY),
2748            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2749            MapEntry.of(CardinalityRange.of(TWO, MANY), OTHER),
2750            MapEntry.of(CardinalityRange.of(TWO, OTHER), OTHER),
2751            MapEntry.of(CardinalityRange.of(MANY, MANY), MANY),
2752            MapEntry.of(CardinalityRange.of(MANY, OTHER), MANY),
2753            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2754            MapEntry.of(CardinalityRange.of(OTHER, TWO), OTHER),
2755            MapEntry.of(CardinalityRange.of(OTHER, MANY), MANY),
2756            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2757        )
2758    ),
2759
2760    /**
2761     * Languages Include:
2762     * <p>
2763     * <ul>
2764     * <li>Georgian (ka)</li>
2765     * </ul>
2766     */
2767    FAMILY_15(
2768        Maps.sortedMap(
2769            MapEntry.of(CardinalityRange.of(ONE, OTHER), ONE),
2770            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2771            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2772        )
2773    ),
2774
2775    /**
2776     * Languages Include:
2777     * <p>
2778     * <ul>
2779     * <li>Latvian (lv)</li>
2780     * </ul>
2781     */
2782    FAMILY_16(
2783        Maps.sortedMap(
2784            MapEntry.of(CardinalityRange.of(ZERO, ZERO), OTHER),
2785            MapEntry.of(CardinalityRange.of(ZERO, ONE), ONE),
2786            MapEntry.of(CardinalityRange.of(ZERO, OTHER), OTHER),
2787            MapEntry.of(CardinalityRange.of(ONE, ZERO), OTHER),
2788            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2789            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2790            MapEntry.of(CardinalityRange.of(OTHER, ZERO), OTHER),
2791            MapEntry.of(CardinalityRange.of(OTHER, ONE), ONE),
2792            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2793        )
2794    ),
2795
2796    /**
2797     * Languages Include:
2798     * <p>
2799     * <ul>
2800     * <li>Macedonian (mk)</li>
2801     * </ul>
2802     */
2803    FAMILY_17(
2804        Maps.sortedMap(
2805            MapEntry.of(CardinalityRange.of(ONE, ONE), OTHER),
2806            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2807            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2808            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2809        )
2810    ),
2811
2812    /**
2813     * Languages Include:
2814     * <p>
2815     * <ul>
2816     * <li>Romanian (ro)</li>
2817     * </ul>
2818     */
2819    FAMILY_18(
2820        Maps.sortedMap(
2821            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2822            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2823            MapEntry.of(CardinalityRange.of(FEW, ONE), FEW),
2824            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2825            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2826            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2827            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2828        )
2829    ),
2830
2831    /**
2832     * Languages Include:
2833     * <p>
2834     * <ul>
2835     * <li>Sinhalese (si)</li>
2836     * </ul>
2837     */
2838    FAMILY_19(
2839        Maps.sortedMap(
2840            MapEntry.of(CardinalityRange.of(ONE, ONE), ONE),
2841            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2842            MapEntry.of(CardinalityRange.of(OTHER, ONE), OTHER),
2843            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2844        )
2845    ),
2846
2847    /**
2848     * Languages Include:
2849     * <p>
2850     * <ul>
2851     * <li>Slovenian (sl)</li>
2852     * </ul>
2853     */
2854    FAMILY_20(
2855        Maps.sortedMap(
2856            MapEntry.of(CardinalityRange.of(ONE, ONE), FEW),
2857            MapEntry.of(CardinalityRange.of(ONE, TWO), TWO),
2858            MapEntry.of(CardinalityRange.of(ONE, FEW), FEW),
2859            MapEntry.of(CardinalityRange.of(ONE, OTHER), OTHER),
2860            MapEntry.of(CardinalityRange.of(TWO, ONE), FEW),
2861            MapEntry.of(CardinalityRange.of(TWO, TWO), TWO),
2862            MapEntry.of(CardinalityRange.of(TWO, FEW), FEW),
2863            MapEntry.of(CardinalityRange.of(TWO, OTHER), OTHER),
2864            MapEntry.of(CardinalityRange.of(FEW, ONE), FEW),
2865            MapEntry.of(CardinalityRange.of(FEW, TWO), TWO),
2866            MapEntry.of(CardinalityRange.of(FEW, FEW), FEW),
2867            MapEntry.of(CardinalityRange.of(FEW, OTHER), OTHER),
2868            MapEntry.of(CardinalityRange.of(OTHER, ONE), FEW),
2869            MapEntry.of(CardinalityRange.of(OTHER, TWO), TWO),
2870            MapEntry.of(CardinalityRange.of(OTHER, FEW), FEW),
2871            MapEntry.of(CardinalityRange.of(OTHER, OTHER), OTHER)
2872        )
2873    );
2874
2875    @Nonnull
2876    private static final Map<String, CardinalityRangeFamily> CARDINALITY_RANGE_FAMILIES_BY_LANGUAGE_CODE;
2877
2878    @Nonnull
2879    private final SortedMap<CardinalityRange, Cardinality> cardinalitiesByCardinalityRange;
2880
2881    static {
2882      CARDINALITY_RANGE_FAMILIES_BY_LANGUAGE_CODE = Collections.unmodifiableMap(new HashMap<String, CardinalityRangeFamily>() {
2883        {
2884          put("af", CardinalityRangeFamily.FAMILY_3); // Afrikaans
2885          put("ak", CardinalityRangeFamily.FAMILY_1); // Akan
2886          put("am", CardinalityRangeFamily.FAMILY_5); // Amharic
2887          put("ar", CardinalityRangeFamily.FAMILY_10); // Arabic
2888          put("ars", CardinalityRangeFamily.FAMILY_1); // Najdi Arabic
2889          put("as", CardinalityRangeFamily.FAMILY_1); // Assamese
2890          put("asa", CardinalityRangeFamily.FAMILY_1); // Asu
2891          put("ast", CardinalityRangeFamily.FAMILY_1); // Asturian
2892          put("az", CardinalityRangeFamily.FAMILY_2); // Azeri
2893          put("be", CardinalityRangeFamily.FAMILY_7); // Belarusian
2894          put("bem", CardinalityRangeFamily.FAMILY_1); // Bemba
2895          put("bez", CardinalityRangeFamily.FAMILY_1); // Bena
2896          put("bg", CardinalityRangeFamily.FAMILY_3); // Bulgarian
2897          put("bh", CardinalityRangeFamily.FAMILY_1); // Bihari
2898          put("bm", CardinalityRangeFamily.FAMILY_1); // Bambara
2899          put("bn", CardinalityRangeFamily.FAMILY_5); // Bangla
2900          put("bo", CardinalityRangeFamily.FAMILY_1); // Tibetan
2901          put("br", CardinalityRangeFamily.FAMILY_1); // Breton
2902          put("brx", CardinalityRangeFamily.FAMILY_1); // Bodo
2903          put("bs", CardinalityRangeFamily.FAMILY_8); // Bosnian
2904          put("ca", CardinalityRangeFamily.FAMILY_3); // Catalan
2905          put("ce", CardinalityRangeFamily.FAMILY_1); // Chechen
2906          put("cgg", CardinalityRangeFamily.FAMILY_1); // Chiga
2907          put("chr", CardinalityRangeFamily.FAMILY_1); // Cherokee
2908          put("ckb", CardinalityRangeFamily.FAMILY_1); // Central Kurdish
2909          put("cs", CardinalityRangeFamily.FAMILY_9); // Czech
2910          put("cy", CardinalityRangeFamily.FAMILY_11); // Welsh
2911          put("da", CardinalityRangeFamily.FAMILY_6); // Danish
2912          put("de", CardinalityRangeFamily.FAMILY_2); // German
2913          put("dsb", CardinalityRangeFamily.FAMILY_1); // Lower Sorbian
2914          put("dv", CardinalityRangeFamily.FAMILY_1); // Divehi
2915          put("dz", CardinalityRangeFamily.FAMILY_1); // Dzongkha
2916          put("ee", CardinalityRangeFamily.FAMILY_1); // Ewe
2917          put("el", CardinalityRangeFamily.FAMILY_2); // Greek
2918          put("en", CardinalityRangeFamily.FAMILY_3); // English
2919          put("eo", CardinalityRangeFamily.FAMILY_1); // Esperanto
2920          put("es", CardinalityRangeFamily.FAMILY_3); // Spanish
2921          put("et", CardinalityRangeFamily.FAMILY_3); // Estonian
2922          put("eu", CardinalityRangeFamily.FAMILY_3); // Basque
2923          put("fa", CardinalityRangeFamily.FAMILY_12); // Persian
2924          put("ff", CardinalityRangeFamily.FAMILY_1); // Fulah
2925          put("fi", CardinalityRangeFamily.FAMILY_3); // Finnish
2926          put("fil", CardinalityRangeFamily.FAMILY_6); // Filipino
2927          put("fo", CardinalityRangeFamily.FAMILY_1); // Faroese
2928          put("fr", CardinalityRangeFamily.FAMILY_5); // French
2929          put("fur", CardinalityRangeFamily.FAMILY_1); // Friulian
2930          put("fy", CardinalityRangeFamily.FAMILY_1); // Western Frisian
2931          put("ga", CardinalityRangeFamily.FAMILY_13); // Irish
2932          put("gd", CardinalityRangeFamily.FAMILY_1); // Scottish Gaelic
2933          put("gl", CardinalityRangeFamily.FAMILY_2); // Galician
2934          put("gsw", CardinalityRangeFamily.FAMILY_2); // Swiss German
2935          put("gu", CardinalityRangeFamily.FAMILY_5); // Gujarati
2936          put("guw", CardinalityRangeFamily.FAMILY_1); // Gun
2937          put("gv", CardinalityRangeFamily.FAMILY_1); // Manx
2938          put("ha", CardinalityRangeFamily.FAMILY_1); // Hausa
2939          put("haw", CardinalityRangeFamily.FAMILY_1); // Hawaiian
2940          put("he", CardinalityRangeFamily.FAMILY_14); // Hebrew
2941          put("hi", CardinalityRangeFamily.FAMILY_5); // Hindi
2942          put("hr", CardinalityRangeFamily.FAMILY_8); // Croatian
2943          put("hsb", CardinalityRangeFamily.FAMILY_1); // Upper Sorbian
2944          put("hu", CardinalityRangeFamily.FAMILY_2); // Hungarian
2945          put("hy", CardinalityRangeFamily.FAMILY_5); // Armenian
2946          put("id", CardinalityRangeFamily.FAMILY_4); // Indonesian
2947          put("ig", CardinalityRangeFamily.FAMILY_1); // Igbo
2948          put("ii", CardinalityRangeFamily.FAMILY_1); // Sichuan Yi
2949          put("is", CardinalityRangeFamily.FAMILY_6); // Icelandic
2950          put("it", CardinalityRangeFamily.FAMILY_2); // Italian
2951          put("iu", CardinalityRangeFamily.FAMILY_1); // Inuktitut
2952          put("ja", CardinalityRangeFamily.FAMILY_4); // Japanese
2953          put("jbo", CardinalityRangeFamily.FAMILY_1); // Lojban
2954          put("jgo", CardinalityRangeFamily.FAMILY_1); // Ngomba
2955          put("jmc", CardinalityRangeFamily.FAMILY_1); // Machame
2956          put("jv", CardinalityRangeFamily.FAMILY_1); // Javanese
2957          put("jw", CardinalityRangeFamily.FAMILY_1); // Javanese
2958          put("ka", CardinalityRangeFamily.FAMILY_15); // Georgian
2959          put("kab", CardinalityRangeFamily.FAMILY_1); // Kabyle
2960          put("kaj", CardinalityRangeFamily.FAMILY_1); // Jju
2961          put("kcg", CardinalityRangeFamily.FAMILY_1); // Tyap
2962          put("kde", CardinalityRangeFamily.FAMILY_1); // Makonde
2963          put("kea", CardinalityRangeFamily.FAMILY_1); // Kabuverdianu
2964          put("kk", CardinalityRangeFamily.FAMILY_2); // Kazakh
2965          put("kkj", CardinalityRangeFamily.FAMILY_1); // Kako
2966          put("kl", CardinalityRangeFamily.FAMILY_1); // Greenlandic
2967          put("km", CardinalityRangeFamily.FAMILY_4); // Khmer
2968          put("kn", CardinalityRangeFamily.FAMILY_5); // Kannada
2969          put("ko", CardinalityRangeFamily.FAMILY_4); // Korean
2970          put("ks", CardinalityRangeFamily.FAMILY_1); // Kashmiri
2971          put("ksb", CardinalityRangeFamily.FAMILY_1); // Shambala
2972          put("ksh", CardinalityRangeFamily.FAMILY_1); // Colognian
2973          put("ku", CardinalityRangeFamily.FAMILY_1); // Kurdish
2974          put("kw", CardinalityRangeFamily.FAMILY_1); // Cornish
2975          put("ky", CardinalityRangeFamily.FAMILY_2); // Kirghiz
2976          put("lag", CardinalityRangeFamily.FAMILY_1); // Langi
2977          put("lb", CardinalityRangeFamily.FAMILY_1); // Luxembourgish
2978          put("lg", CardinalityRangeFamily.FAMILY_1); // Ganda
2979          put("lkt", CardinalityRangeFamily.FAMILY_1); // Lakota
2980          put("ln", CardinalityRangeFamily.FAMILY_1); // Lingala
2981          put("lo", CardinalityRangeFamily.FAMILY_4); // Lao
2982          put("lt", CardinalityRangeFamily.FAMILY_7); // Lithuanian
2983          put("lv", CardinalityRangeFamily.FAMILY_16); // Latvian
2984          put("mas", CardinalityRangeFamily.FAMILY_1); // Masai
2985          put("mg", CardinalityRangeFamily.FAMILY_1); // Malagasy
2986          put("mgo", CardinalityRangeFamily.FAMILY_1); // Metaʼ
2987          put("mk", CardinalityRangeFamily.FAMILY_17); // Macedonian
2988          put("ml", CardinalityRangeFamily.FAMILY_2); // Malayalam
2989          put("mn", CardinalityRangeFamily.FAMILY_2); // Mongolian
2990          put("mo", CardinalityRangeFamily.FAMILY_1); // Moldovan
2991          put("mr", CardinalityRangeFamily.FAMILY_5); // Marathi
2992          put("ms", CardinalityRangeFamily.FAMILY_4); // Malay
2993          put("mt", CardinalityRangeFamily.FAMILY_1); // Maltese
2994          put("my", CardinalityRangeFamily.FAMILY_4); // Burmese
2995          put("nah", CardinalityRangeFamily.FAMILY_1); // Nahuatl
2996          put("naq", CardinalityRangeFamily.FAMILY_1); // Nama
2997          put("nb", CardinalityRangeFamily.FAMILY_3); // Norwegian Bokmål
2998          put("nd", CardinalityRangeFamily.FAMILY_1); // North Ndebele
2999          put("ne", CardinalityRangeFamily.FAMILY_2); // Nepali
3000          put("nl", CardinalityRangeFamily.FAMILY_2); // Dutch
3001          put("nn", CardinalityRangeFamily.FAMILY_1); // Norwegian Nynorsk
3002          put("nnh", CardinalityRangeFamily.FAMILY_1); // Ngiemboon
3003          put("no", CardinalityRangeFamily.FAMILY_1); // Norwegian
3004          put("nqo", CardinalityRangeFamily.FAMILY_1); // N’Ko
3005          put("nr", CardinalityRangeFamily.FAMILY_1); // South Ndebele
3006          put("nso", CardinalityRangeFamily.FAMILY_1); // Northern Sotho
3007          put("ny", CardinalityRangeFamily.FAMILY_1); // Nyanja
3008          put("nyn", CardinalityRangeFamily.FAMILY_1); // Nyankole
3009          put("om", CardinalityRangeFamily.FAMILY_1); // Oromo
3010          put("or", CardinalityRangeFamily.FAMILY_1); // Odia
3011          put("os", CardinalityRangeFamily.FAMILY_1); // Ossetian
3012          put("pa", CardinalityRangeFamily.FAMILY_6); // Punjabi
3013          put("pap", CardinalityRangeFamily.FAMILY_1); // Papiamento
3014          put("pl", CardinalityRangeFamily.FAMILY_9); // Polish
3015          put("prg", CardinalityRangeFamily.FAMILY_1); // Prussian
3016          put("ps", CardinalityRangeFamily.FAMILY_1); // Pushto
3017          put("pt", CardinalityRangeFamily.FAMILY_6); // Portuguese
3018          put("rm", CardinalityRangeFamily.FAMILY_1); // Romansh
3019          put("ro", CardinalityRangeFamily.FAMILY_18); // Romanian
3020          put("rof", CardinalityRangeFamily.FAMILY_1); // Rombo
3021          put("root", CardinalityRangeFamily.FAMILY_1); // Root
3022          put("ru", CardinalityRangeFamily.FAMILY_7); // Russian
3023          put("rwk", CardinalityRangeFamily.FAMILY_1); // Rwa
3024          put("sah", CardinalityRangeFamily.FAMILY_1); // Sakha
3025          put("saq", CardinalityRangeFamily.FAMILY_1); // Samburu
3026          put("sdh", CardinalityRangeFamily.FAMILY_1); // Southern Kurdish
3027          put("se", CardinalityRangeFamily.FAMILY_1); // Northern Sami
3028          put("seh", CardinalityRangeFamily.FAMILY_1); // Sena
3029          put("ses", CardinalityRangeFamily.FAMILY_1); // Koyraboro Senni
3030          put("sg", CardinalityRangeFamily.FAMILY_1); // Sango
3031          put("sh", CardinalityRangeFamily.FAMILY_1); // Serbo-Croatian
3032          put("shi", CardinalityRangeFamily.FAMILY_1); // Tachelhit
3033          put("si", CardinalityRangeFamily.FAMILY_19); // Sinhalese
3034          put("sk", CardinalityRangeFamily.FAMILY_9); // Slovak
3035          put("sl", CardinalityRangeFamily.FAMILY_20); // Slovenian
3036          put("sma", CardinalityRangeFamily.FAMILY_1); // Southern Sami
3037          put("smi", CardinalityRangeFamily.FAMILY_1); // Sami
3038          put("smj", CardinalityRangeFamily.FAMILY_1); // Lule Sami
3039          put("smn", CardinalityRangeFamily.FAMILY_1); // Inari Sami
3040          put("sms", CardinalityRangeFamily.FAMILY_1); // Skolt Sami
3041          put("sn", CardinalityRangeFamily.FAMILY_1); // Shona
3042          put("so", CardinalityRangeFamily.FAMILY_1); // Somali
3043          put("sq", CardinalityRangeFamily.FAMILY_2); // Albanian
3044          put("sr", CardinalityRangeFamily.FAMILY_8); // Serbian
3045          put("ss", CardinalityRangeFamily.FAMILY_1); // Swati
3046          put("ssy", CardinalityRangeFamily.FAMILY_1); // Saho
3047          put("st", CardinalityRangeFamily.FAMILY_1); // Southern Sotho
3048          put("sv", CardinalityRangeFamily.FAMILY_3); // Swedish
3049          put("sw", CardinalityRangeFamily.FAMILY_2); // Swahili
3050          put("syr", CardinalityRangeFamily.FAMILY_1); // Syriac
3051          put("ta", CardinalityRangeFamily.FAMILY_2); // Tamil
3052          put("te", CardinalityRangeFamily.FAMILY_2); // Telugu
3053          put("teo", CardinalityRangeFamily.FAMILY_1); // Teso
3054          put("th", CardinalityRangeFamily.FAMILY_4); // Thai
3055          put("ti", CardinalityRangeFamily.FAMILY_1); // Tigrinya
3056          put("tig", CardinalityRangeFamily.FAMILY_1); // Tigre
3057          put("tk", CardinalityRangeFamily.FAMILY_1); // Turkmen
3058          put("tl", CardinalityRangeFamily.FAMILY_1); // Tagalog
3059          put("tn", CardinalityRangeFamily.FAMILY_1); // Tswana
3060          put("to", CardinalityRangeFamily.FAMILY_1); // Tongan
3061          put("tr", CardinalityRangeFamily.FAMILY_2); // Turkish
3062          put("ts", CardinalityRangeFamily.FAMILY_1); // Tsonga
3063          put("tzm", CardinalityRangeFamily.FAMILY_1); // Central Atlas Tamazight
3064          put("ug", CardinalityRangeFamily.FAMILY_2); // Uighur
3065          put("uk", CardinalityRangeFamily.FAMILY_7); // Ukrainian
3066          put("ur", CardinalityRangeFamily.FAMILY_3); // Urdu
3067          put("uz", CardinalityRangeFamily.FAMILY_2); // Uzbek
3068          put("ve", CardinalityRangeFamily.FAMILY_1); // Venda
3069          put("vi", CardinalityRangeFamily.FAMILY_4); // Vietnamese
3070          put("vo", CardinalityRangeFamily.FAMILY_1); // Volapük
3071          put("vun", CardinalityRangeFamily.FAMILY_1); // Vunjo
3072          put("wa", CardinalityRangeFamily.FAMILY_1); // Walloon
3073          put("wae", CardinalityRangeFamily.FAMILY_1); // Walser
3074          put("wo", CardinalityRangeFamily.FAMILY_1); // Wolof
3075          put("xh", CardinalityRangeFamily.FAMILY_1); // Xhosa
3076          put("xog", CardinalityRangeFamily.FAMILY_1); // Soga
3077          put("yi", CardinalityRangeFamily.FAMILY_1); // Yiddish
3078          put("yo", CardinalityRangeFamily.FAMILY_1); // Yoruba
3079          put("yue", CardinalityRangeFamily.FAMILY_4); // Cantonese
3080          put("zh", CardinalityRangeFamily.FAMILY_4); // Mandarin Chinese
3081          put("zu", CardinalityRangeFamily.FAMILY_5); // Zulu
3082        }
3083      });
3084    }
3085
3086    /**
3087     * Gets an appropriate plural cardinality range family for the given locale.
3088     *
3089     * @param locale the locale to check, not null
3090     * @return the appropriate plural cardinality range family (if one exists) for the given locale, not null
3091     */
3092    @Nonnull
3093    static Optional<CardinalityRangeFamily> cardinalityRangeFamilyForLocale(@Nonnull Locale locale) {
3094      requireNonNull(locale);
3095
3096      String language = LocaleUtils.normalizedLanguage(locale).orElse(null);
3097      String country = locale.getCountry();
3098
3099      CardinalityRangeFamily cardinalityRangeFamily = null;
3100
3101      if (language != null && country != null)
3102        cardinalityRangeFamily = CARDINALITY_RANGE_FAMILIES_BY_LANGUAGE_CODE.get(format("%s-%s", language, country));
3103
3104      if (cardinalityRangeFamily != null)
3105        return Optional.of(cardinalityRangeFamily);
3106
3107      if (language != null)
3108        cardinalityRangeFamily = CARDINALITY_RANGE_FAMILIES_BY_LANGUAGE_CODE.get(language);
3109
3110      return Optional.ofNullable(cardinalityRangeFamily);
3111    }
3112
3113    /**
3114     * Constructs a cardinality range family.
3115     *
3116     * @param cardinalitiesByCardinalityRange a mapping of cardinalities to example integer values for this cardinality range family sorted by the natural ordering of {@link Cardinality}, not null
3117     */
3118    CardinalityRangeFamily(@Nonnull SortedMap<CardinalityRange, Cardinality> cardinalitiesByCardinalityRange) {
3119      this.cardinalitiesByCardinalityRange = cardinalitiesByCardinalityRange;
3120    }
3121
3122    @Nonnull
3123    SortedMap<CardinalityRange, Cardinality> getCardinalitiesByCardinalityRange() {
3124      return cardinalitiesByCardinalityRange;
3125    }
3126  }
3127}