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