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