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;
021
022import java.math.BigDecimal;
023import java.math.BigInteger;
024import java.text.Collator;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.Locale;
029import java.util.Map;
030import java.util.Optional;
031import java.util.SortedMap;
032import java.util.SortedSet;
033import java.util.TreeSet;
034import java.util.function.Function;
035import java.util.stream.Collectors;
036
037import static com.lokalized.NumberUtils.equal;
038import static com.lokalized.NumberUtils.inRange;
039import static com.lokalized.NumberUtils.inSet;
040import static com.lokalized.NumberUtils.notEqual;
041import static com.lokalized.NumberUtils.notInSet;
042import static java.lang.String.format;
043import static java.util.Objects.requireNonNull;
044
045/**
046 * Language plural ordinality forms.
047 * <p>
048 * For example, English has four: {@code 1st, 2nd, 3rd, 4th}, while Swedish has two: {@code 1:a, 3:e}.
049 * <p>
050 * See the <a href="http://cldr.unicode.org/index/cldr-spec/plural-rules">Unicode Common Locale Data Repository</a>
051 * and its <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">Language Plural Rules</a> for details.
052 * <p>
053 * Per the CLDR:
054 * <blockquote>
055 * These categories are only mnemonics -- the names don't necessarily imply the exact contents of the category.
056 * For example, for both English and French the number 1 has the category one (singular).
057 * <p>
058 * In English, every other number has a plural form, and is given the category other.
059 * French is similar, except that the number 0 also has the category one and not other or zero, because the form of
060 * units qualified by 0 is also singular.
061 * <p>
062 * This is worth emphasizing: A common mistake is to think that "one" is only for only the number 1.
063 * Instead, "one" is a category for any number that behaves like 1. So in some languages, for example,
064 * one → numbers that end in "1" (like 1, 21, 151) but that don't end in 11 (like "11, 111, 10311).
065 * </blockquote>
066 *
067 * @author <a href="https://revetkn.com">Mark Allen</a>
068 */
069public enum Ordinality implements LanguageForm {
070  /**
071   * Normally the form used with 0, if it is limited to numbers whose integer values end with 0.
072   * <p>
073   * For example: the Welsh {@code 0fed ci} means "{@code 0th dog}" in English.
074   */
075  ZERO,
076  /**
077   * The form used with 1.
078   * <p>
079   * For example: the Welsh {@code ci 1af} means {@code 1st dog} in English.
080   */
081  ONE,
082  /**
083   * Normally the form used with 2, if it is limited to numbers whose integer values end with 2.
084   * <p>
085   * For example: the Welsh {@code 2il gi} means {@code 2nd dog} in English.
086   */
087  TWO,
088  /**
089   * The form that falls between {@code TWO} and {@code MANY}.
090   * <p>
091   * For example: the Welsh {@code 3ydd ci} means {@code 3rd dog} in English.
092   */
093  FEW,
094  /**
095   * The form that falls between {@code FEW} and {@code OTHER}.
096   * <p>
097   * For example: the Welsh {@code 5ed ci} means {@code 5th dog} in English.
098   */
099  MANY,
100  /**
101   * General "catchall" form which comprises any cases not handled by the other forms.
102   * <p>
103   * For example: the Welsh {@code ci rhif 10} means {@code 10th dog} in English.
104   */
105  OTHER;
106
107  @NonNull
108  private static final BigInteger BIG_INTEGER_0;
109  @NonNull
110  private static final BigInteger BIG_INTEGER_1;
111  @NonNull
112  private static final BigInteger BIG_INTEGER_2;
113  @NonNull
114  private static final BigInteger BIG_INTEGER_3;
115  @NonNull
116  private static final BigInteger BIG_INTEGER_4;
117  @NonNull
118  private static final BigInteger BIG_INTEGER_5;
119  @NonNull
120  private static final BigInteger BIG_INTEGER_6;
121  @NonNull
122  private static final BigInteger BIG_INTEGER_7;
123  @NonNull
124  private static final BigInteger BIG_INTEGER_8;
125  @NonNull
126  private static final BigInteger BIG_INTEGER_10;
127  @NonNull
128  private static final BigInteger BIG_INTEGER_11;
129  @NonNull
130  private static final BigInteger BIG_INTEGER_12;
131  @NonNull
132  private static final BigInteger BIG_INTEGER_17;
133  @NonNull
134  private static final BigInteger BIG_INTEGER_18;
135  @NonNull
136  private static final BigInteger BIG_INTEGER_20;
137  @NonNull
138  private static final BigInteger BIG_INTEGER_40;
139  @NonNull
140  private static final BigInteger BIG_INTEGER_50;
141  @NonNull
142  private static final BigInteger BIG_INTEGER_60;
143  @NonNull
144  private static final BigInteger BIG_INTEGER_70;
145  @NonNull
146  private static final BigInteger BIG_INTEGER_80;
147  @NonNull
148  private static final BigInteger BIG_INTEGER_90;
149  @NonNull
150  private static final BigInteger BIG_INTEGER_100;
151  @NonNull
152  private static final BigInteger BIG_INTEGER_200;
153  @NonNull
154  private static final BigInteger BIG_INTEGER_300;
155  @NonNull
156  private static final BigInteger BIG_INTEGER_400;
157  @NonNull
158  private static final BigInteger BIG_INTEGER_500;
159  @NonNull
160  private static final BigInteger BIG_INTEGER_600;
161  @NonNull
162  private static final BigInteger BIG_INTEGER_700;
163  @NonNull
164  private static final BigInteger BIG_INTEGER_800;
165  @NonNull
166  private static final BigInteger BIG_INTEGER_900;
167  @NonNull
168  private static final BigInteger BIG_INTEGER_1_000;
169
170  @NonNull
171  private static final BigDecimal BIG_DECIMAL_0;
172  @NonNull
173  private static final BigDecimal BIG_DECIMAL_1;
174  @NonNull
175  private static final BigDecimal BIG_DECIMAL_2;
176  @NonNull
177  private static final BigDecimal BIG_DECIMAL_3;
178  @NonNull
179  private static final BigDecimal BIG_DECIMAL_4;
180  @NonNull
181  private static final BigDecimal BIG_DECIMAL_5;
182  @NonNull
183  private static final BigDecimal BIG_DECIMAL_6;
184  @NonNull
185  private static final BigDecimal BIG_DECIMAL_7;
186  @NonNull
187  private static final BigDecimal BIG_DECIMAL_8;
188  @NonNull
189  private static final BigDecimal BIG_DECIMAL_9;
190  @NonNull
191  private static final BigDecimal BIG_DECIMAL_10;
192  @NonNull
193  private static final BigDecimal BIG_DECIMAL_11;
194  @NonNull
195  private static final BigDecimal BIG_DECIMAL_12;
196  @NonNull
197  private static final BigDecimal BIG_DECIMAL_13;
198  @NonNull
199  private static final BigDecimal BIG_DECIMAL_14;
200  @NonNull
201  private static final BigDecimal BIG_DECIMAL_80;
202  @NonNull
203  private static final BigDecimal BIG_DECIMAL_100;
204  @NonNull
205  private static final BigDecimal BIG_DECIMAL_800;
206
207  @NonNull
208  static final Map<@NonNull String, @NonNull Ordinality> ORDINALITIES_BY_NAME;
209
210  static {
211    BIG_INTEGER_0 = BigInteger.ZERO;
212    BIG_INTEGER_1 = BigInteger.ONE;
213    BIG_INTEGER_2 = BigInteger.valueOf(2);
214    BIG_INTEGER_3 = BigInteger.valueOf(3);
215    BIG_INTEGER_4 = BigInteger.valueOf(4);
216    BIG_INTEGER_5 = BigInteger.valueOf(5);
217    BIG_INTEGER_6 = BigInteger.valueOf(6);
218    BIG_INTEGER_7 = BigInteger.valueOf(7);
219    BIG_INTEGER_8 = BigInteger.valueOf(8);
220    BIG_INTEGER_10 = BigInteger.TEN;
221    BIG_INTEGER_11 = BigInteger.valueOf(11);
222    BIG_INTEGER_12 = BigInteger.valueOf(12);
223    BIG_INTEGER_17 = BigInteger.valueOf(17);
224    BIG_INTEGER_18 = BigInteger.valueOf(18);
225    BIG_INTEGER_20 = BigInteger.valueOf(20);
226    BIG_INTEGER_40 = BigInteger.valueOf(40);
227    BIG_INTEGER_50 = BigInteger.valueOf(50);
228    BIG_INTEGER_60 = BigInteger.valueOf(60);
229    BIG_INTEGER_70 = BigInteger.valueOf(70);
230    BIG_INTEGER_80 = BigInteger.valueOf(80);
231    BIG_INTEGER_90 = BigInteger.valueOf(90);
232    BIG_INTEGER_100 = BigInteger.valueOf(100);
233    BIG_INTEGER_200 = BigInteger.valueOf(200);
234    BIG_INTEGER_300 = BigInteger.valueOf(300);
235    BIG_INTEGER_400 = BigInteger.valueOf(400);
236    BIG_INTEGER_500 = BigInteger.valueOf(500);
237    BIG_INTEGER_600 = BigInteger.valueOf(600);
238    BIG_INTEGER_700 = BigInteger.valueOf(700);
239    BIG_INTEGER_800 = BigInteger.valueOf(800);
240    BIG_INTEGER_900 = BigInteger.valueOf(900);
241    BIG_INTEGER_1_000 = BigInteger.valueOf(1_000);
242
243    BIG_DECIMAL_0 = BigDecimal.ZERO;
244    BIG_DECIMAL_1 = BigDecimal.ONE;
245    BIG_DECIMAL_2 = BigDecimal.valueOf(2);
246    BIG_DECIMAL_3 = BigDecimal.valueOf(3);
247    BIG_DECIMAL_4 = BigDecimal.valueOf(4);
248    BIG_DECIMAL_5 = BigDecimal.valueOf(5);
249    BIG_DECIMAL_6 = BigDecimal.valueOf(6);
250    BIG_DECIMAL_7 = BigDecimal.valueOf(7);
251    BIG_DECIMAL_8 = BigDecimal.valueOf(8);
252    BIG_DECIMAL_9 = BigDecimal.valueOf(9);
253    BIG_DECIMAL_10 = BigDecimal.TEN;
254    BIG_DECIMAL_11 = BigDecimal.valueOf(11);
255    BIG_DECIMAL_12 = BigDecimal.valueOf(12);
256    BIG_DECIMAL_13 = BigDecimal.valueOf(13);
257    BIG_DECIMAL_14 = BigDecimal.valueOf(14);
258    BIG_DECIMAL_80 = BigDecimal.valueOf(80);
259    BIG_DECIMAL_100 = BigDecimal.valueOf(100);
260    BIG_DECIMAL_800 = BigDecimal.valueOf(800);
261
262    ORDINALITIES_BY_NAME = Collections.unmodifiableMap(Arrays.stream(
263        Ordinality.values()).collect(Collectors.toMap(ordinality -> ordinality.name(), ordinality -> ordinality)));
264  }
265
266  /**
267   * Gets an appropriate plural ordinality for the given number and locale.
268   * <p>
269   * Negative numbers are evaluated using their absolute value.
270   * <p>
271   * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html</a>
272   * for a cheat sheet.
273   *
274   * @param number the number that drives pluralization, not null
275   * @param locale the locale that drives pluralization, not null
276   * @return an appropriate plural ordinality, not null
277   * @throws UnsupportedLocaleException if the locale is not supported
278   */
279  @NonNull
280  public static Ordinality forNumber(@NonNull Number number, @NonNull Locale locale) {
281    requireNonNull(number);
282    requireNonNull(locale);
283
284    BigDecimal numberAsBigDecimal = NumberUtils.toBigDecimal(number).abs();
285
286    Optional<OrdinalityFamily> ordinalityFamily = OrdinalityFamily.ordinalityFamilyForLocale(locale);
287
288    // TODO: throwing an exception might not be the best solution here...need to think about it
289    if (!ordinalityFamily.isPresent())
290      throw new UnsupportedLocaleException(locale);
291
292    return ordinalityFamily.get().getOrdinalityFunction().apply(numberAsBigDecimal);
293  }
294
295  /**
296   * Gets the set of ordinalities supported for the given locale.
297   * <p>
298   * The empty set will be returned if the locale is not supported.
299   * <p>
300   * The set's values are sorted by the natural ordering of the {@link Ordinality} enumeration.
301   *
302   * @param locale the locale to use for lookup, not null
303   * @return the ordinalities supported by the given locale, not null
304   */
305  @NonNull
306  public static SortedSet<@NonNull Ordinality> supportedOrdinalitiesForLocale(@NonNull Locale locale) {
307    requireNonNull(locale);
308
309    Optional<OrdinalityFamily> ordinalityFamily = OrdinalityFamily.ordinalityFamilyForLocale(locale);
310    return ordinalityFamily.isPresent() ? ordinalityFamily.get().getSupportedOrdinalities() : Collections.emptySortedSet();
311  }
312
313  /**
314   * Gets a mapping of ordinalities to example integer values for the given locale.
315   * <p>
316   * The empty map will be returned if the locale is not supported or if no example values are available.
317   * <p>
318   * The map's keys are sorted by the natural ordering of the {@link Ordinality} enumeration.
319   *
320   * @param locale the locale to use for lookup, not null
321   * @return a mapping of ordinalities to example integer values, not null
322   */
323  @NonNull
324  public static SortedMap<@NonNull Ordinality, @NonNull Range<@NonNull Integer>> exampleIntegerValuesForLocale(@NonNull Locale locale) {
325    requireNonNull(locale);
326
327    Optional<OrdinalityFamily> ordinalityFamily = OrdinalityFamily.ordinalityFamilyForLocale(locale);
328    return ordinalityFamily.isPresent() ? ordinalityFamily.get().getExampleIntegerValuesByOrdinality() : Collections.emptySortedMap();
329  }
330
331  /**
332   * Gets the ISO 639 language codes for which ordinality operations are supported.
333   * <p>
334   * The set's values are ISO 639 codes and therefore sorted using English collation.
335   *
336   * @return the ISO 639 language codes for which ordinality operations are supported, not null
337   */
338  @NonNull
339  public static SortedSet<@NonNull String> getSupportedLanguageCodes() {
340    return OrdinalityFamily.getSupportedLanguageCodes();
341  }
342
343  /**
344   * Gets the mapping of ordinality names to values.
345   *
346   * @return the mapping of ordinality names to values, not null
347   */
348  @NonNull
349  static Map<@NonNull String, @NonNull Ordinality> getOrdinalitiesByName() {
350    return ORDINALITIES_BY_NAME;
351  }
352
353  /**
354   * Plural ordinality forms grouped by language family.
355   * <p>
356   * Each family has a distinct ordinality calculation rule.
357   * <p>
358   * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html</a>
359   * for a cheat sheet.
360   */
361  enum OrdinalityFamily {
362    /**
363     * Languages Include:
364     * <p>
365     * <ul>
366     * <li>Afrikaans (af)</li>
367     * <li>Akan (ak) (no CLDR data available)</li>
368     * <li>Amharic (am)</li>
369     * <li>Arabic (ar)</li>
370     * <li>Najdi Arabic (ars) (no CLDR data available)</li>
371     * <li>Asu (asa) (no CLDR data available)</li>
372     * <li>Asturian (ast) (no CLDR data available)</li>
373     * <li>Bemba (bem) (no CLDR data available)</li>
374     * <li>Bena (bez) (no CLDR data available)</li>
375     * <li>Bulgarian (bg)</li>
376     * <li>Bihari (bh) (no CLDR data available)</li>
377     * <li>Bambara (bm) (no CLDR data available)</li>
378     * <li>Tibetan (bo) (no CLDR data available)</li>
379     * <li>Breton (br) (no CLDR data available)</li>
380     * <li>Bodo (brx) (no CLDR data available)</li>
381     * <li>Bosnian (bs)</li>
382     * <li>Chechen (ce)</li>
383     * <li>Chiga (cgg) (no CLDR data available)</li>
384     * <li>Cherokee (chr) (no CLDR data available)</li>
385     * <li>Central Kurdish (ckb) (no CLDR data available)</li>
386     * <li>Czech (cs)</li>
387     * <li>Danish (da)</li>
388     * <li>German (de)</li>
389     * <li>Lower Sorbian (dsb)</li>
390     * <li>Divehi (dv) (no CLDR data available)</li>
391     * <li>Dzongkha (dz) (no CLDR data available)</li>
392     * <li>Ewe (ee) (no CLDR data available)</li>
393     * <li>Greek (el)</li>
394     * <li>Esperanto (eo) (no CLDR data available)</li>
395     * <li>Spanish (es)</li>
396     * <li>Estonian (et)</li>
397     * <li>Basque (eu)</li>
398     * <li>Persian (fa)</li>
399     * <li>Fulah (ff) (no CLDR data available)</li>
400     * <li>Finnish (fi)</li>
401     * <li>Faroese (fo) (no CLDR data available)</li>
402     * <li>Friulian (fur) (no CLDR data available)</li>
403     * <li>Western Frisian (fy)</li>
404     * <li>Scottish Gaelic (gd) (no CLDR data available)</li>
405     * <li>Galician (gl)</li>
406     * <li>Swiss German (gsw)</li>
407     * <li>Gun (guw) (no CLDR data available)</li>
408     * <li>Manx (gv) (no CLDR data available)</li>
409     * <li>Hausa (ha) (no CLDR data available)</li>
410     * <li>Hawaiian (haw) (no CLDR data available)</li>
411     * <li>Hebrew (he)</li>
412     * <li>Croatian (hr)</li>
413     * <li>Upper Sorbian (hsb)</li>
414     * <li>Indonesian (id)</li>
415     * <li>Igbo (ig) (no CLDR data available)</li>
416     * <li>Sichuan Yi (ii) (no CLDR data available)</li>
417     * <li>Icelandic (is)</li>
418     * <li>Inuktitut (iu) (no CLDR data available)</li>
419     * <li>Japanese (ja)</li>
420     * <li>Lojban (jbo) (no CLDR data available)</li>
421     * <li>Ngomba (jgo) (no CLDR data available)</li>
422     * <li>Machame (jmc) (no CLDR data available)</li>
423     * <li>Javanese (jv) (no CLDR data available)</li>
424     * <li>Javanese (jw) (no CLDR data available)</li>
425     * <li>Kabyle (kab) (no CLDR data available)</li>
426     * <li>Jju (kaj) (no CLDR data available)</li>
427     * <li>Tyap (kcg) (no CLDR data available)</li>
428     * <li>Makonde (kde) (no CLDR data available)</li>
429     * <li>Kabuverdianu (kea) (no CLDR data available)</li>
430     * <li>Kako (kkj) (no CLDR data available)</li>
431     * <li>Greenlandic (kl) (no CLDR data available)</li>
432     * <li>Khmer (km)</li>
433     * <li>Kannada (kn)</li>
434     * <li>Korean (ko)</li>
435     * <li>Kashmiri (ks) (no CLDR data available)</li>
436     * <li>Shambala (ksb) (no CLDR data available)</li>
437     * <li>Colognian (ksh) (no CLDR data available)</li>
438     * <li>Kurdish (ku) (no CLDR data available)</li>
439     * <li>Cornish (kw) (no CLDR data available)</li>
440     * <li>Kirghiz (ky)</li>
441     * <li>Langi (lag) (no CLDR data available)</li>
442     * <li>Luxembourgish (lb) (no CLDR data available)</li>
443     * <li>Ganda (lg) (no CLDR data available)</li>
444     * <li>Lakota (lkt) (no CLDR data available)</li>
445     * <li>Lingala (ln) (no CLDR data available)</li>
446     * <li>Lithuanian (lt)</li>
447     * <li>Latvian (lv)</li>
448     * <li>Masai (mas) (no CLDR data available)</li>
449     * <li>Malagasy (mg) (no CLDR data available)</li>
450     * <li>Metaʼ (mgo) (no CLDR data available)</li>
451     * <li>Malayalam (ml)</li>
452     * <li>Mongolian (mn)</li>
453     * <li>Maltese (mt) (no CLDR data available)</li>
454     * <li>Burmese (my)</li>
455     * <li>Nahuatl (nah) (no CLDR data available)</li>
456     * <li>Nama (naq) (no CLDR data available)</li>
457     * <li>Norwegian Bokmål (nb)</li>
458     * <li>North Ndebele (nd) (no CLDR data available)</li>
459     * <li>Dutch (nl)</li>
460     * <li>Norwegian Nynorsk (nn) (no CLDR data available)</li>
461     * <li>Ngiemboon (nnh) (no CLDR data available)</li>
462     * <li>Norwegian (no) (no CLDR data available)</li>
463     * <li>N’Ko (nqo) (no CLDR data available)</li>
464     * <li>South Ndebele (nr) (no CLDR data available)</li>
465     * <li>Northern Sotho (nso) (no CLDR data available)</li>
466     * <li>Nyanja (ny) (no CLDR data available)</li>
467     * <li>Nyankole (nyn) (no CLDR data available)</li>
468     * <li>Oromo (om) (no CLDR data available)</li>
469     * <li>Odia (or) (no CLDR data available)</li>
470     * <li>Ossetian (os) (no CLDR data available)</li>
471     * <li>Punjabi (pa)</li>
472     * <li>Papiamento (pap) (no CLDR data available)</li>
473     * <li>Polish (pl)</li>
474     * <li>Prussian (prg)</li>
475     * <li>Pushto (ps) (no CLDR data available)</li>
476     * <li>Portuguese (pt)</li>
477     * <li>Romansh (rm) (no CLDR data available)</li>
478     * <li>Rombo (rof) (no CLDR data available)</li>
479     * <li>Root (root)</li>
480     * <li>Russian (ru)</li>
481     * <li>Rwa (rwk) (no CLDR data available)</li>
482     * <li>Sakha (sah) (no CLDR data available)</li>
483     * <li>Samburu (saq) (no CLDR data available)</li>
484     * <li>Southern Kurdish (sdh) (no CLDR data available)</li>
485     * <li>Northern Sami (se) (no CLDR data available)</li>
486     * <li>Sena (seh) (no CLDR data available)</li>
487     * <li>Koyraboro Senni (ses) (no CLDR data available)</li>
488     * <li>Sango (sg) (no CLDR data available)</li>
489     * <li>Serbo-Croatian (sh)</li>
490     * <li>Tachelhit (shi) (no CLDR data available)</li>
491     * <li>Sinhalese (si)</li>
492     * <li>Slovak (sk)</li>
493     * <li>Slovenian (sl)</li>
494     * <li>Southern Sami (sma) (no CLDR data available)</li>
495     * <li>Sami (smi) (no CLDR data available)</li>
496     * <li>Lule Sami (smj) (no CLDR data available)</li>
497     * <li>Inari Sami (smn) (no CLDR data available)</li>
498     * <li>Skolt Sami (sms) (no CLDR data available)</li>
499     * <li>Shona (sn) (no CLDR data available)</li>
500     * <li>Somali (so) (no CLDR data available)</li>
501     * <li>Serbian (sr)</li>
502     * <li>Swati (ss) (no CLDR data available)</li>
503     * <li>Saho (ssy) (no CLDR data available)</li>
504     * <li>Southern Sotho (st) (no CLDR data available)</li>
505     * <li>Swahili (sw)</li>
506     * <li>Syriac (syr) (no CLDR data available)</li>
507     * <li>Tamil (ta)</li>
508     * <li>Telugu (te)</li>
509     * <li>Teso (teo) (no CLDR data available)</li>
510     * <li>Thai (th)</li>
511     * <li>Tigrinya (ti) (no CLDR data available)</li>
512     * <li>Tigre (tig) (no CLDR data available)</li>
513     * <li>Turkmen (tk) (no CLDR data available)</li>
514     * <li>Tswana (tn) (no CLDR data available)</li>
515     * <li>Tongan (to) (no CLDR data available)</li>
516     * <li>Turkish (tr)</li>
517     * <li>Tsonga (ts) (no CLDR data available)</li>
518     * <li>Central Atlas Tamazight (tzm) (no CLDR data available)</li>
519     * <li>Uighur (ug) (no CLDR data available)</li>
520     * <li>Urdu (ur)</li>
521     * <li>Uzbek (uz)</li>
522     * <li>Venda (ve) (no CLDR data available)</li>
523     * <li>Volapük (vo) (no CLDR data available)</li>
524     * <li>Vunjo (vun) (no CLDR data available)</li>
525     * <li>Walloon (wa) (no CLDR data available)</li>
526     * <li>Walser (wae) (no CLDR data available)</li>
527     * <li>Wolof (wo) (no CLDR data available)</li>
528     * <li>Xhosa (xh) (no CLDR data available)</li>
529     * <li>Soga (xog) (no CLDR data available)</li>
530     * <li>Yiddish (yi) (no CLDR data available)</li>
531     * <li>Yoruba (yo) (no CLDR data available)</li>
532     * <li>Cantonese (yue)</li>
533     * <li>Mandarin Chinese (zh)</li>
534     * <li>Zulu (zu)</li>
535     * </ul>
536     */
537    FAMILY_1(
538        (n) -> {
539          // No ordinality rules for this family
540          return OTHER;
541        },
542        Sets.sortedSet(
543            OTHER
544        ),
545        Maps.sortedMap(
546            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 100, 1000, 10000, 100000, 1000000))
547        )
548    ),
549
550    /**
551     * Languages Include:
552     * <p>
553     * <ul>
554     * <li>Filipino (fil)</li>
555     * <li>French (fr)</li>
556     * <li>Irish (ga)</li>
557     * <li>Armenian (hy)</li>
558     * <li>Lao (lo)</li>
559     * <li>Moldovan (mo)</li>
560     * <li>Malay (ms)</li>
561     * <li>Romanian (ro)</li>
562     * <li>Tagalog (tl)</li>
563     * <li>Vietnamese (vi)</li>
564     * </ul>
565     */
566    FAMILY_2(
567        (n) -> {
568          // n = 1
569          if (equal(n, BIG_DECIMAL_1))
570            return ONE;
571
572          return OTHER;
573        },
574        Sets.sortedSet(
575            ONE,
576            OTHER
577        ),
578        Maps.sortedMap(
579            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
580            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
581        )
582    ),
583
584    /**
585     * Languages Include:
586     * <p>
587     * <ul>
588     * <li>Assamese (as)</li>
589     * <li>Bangla (bn)</li>
590     * </ul>
591     */
592    FAMILY_3(
593        (n) -> {
594          // n = 1,5,7,8,9,10
595          if (inSet(n, BIG_DECIMAL_1, BIG_DECIMAL_5, BIG_DECIMAL_7, BIG_DECIMAL_8, BIG_DECIMAL_9, BIG_DECIMAL_10))
596            return ONE;
597          // n = 2,3
598          if (inSet(n, BIG_DECIMAL_2, BIG_DECIMAL_3))
599            return TWO;
600          // n = 4
601          if (equal(n, BIG_DECIMAL_4))
602            return FEW;
603          // n = 6
604          if (equal(n, BIG_DECIMAL_6))
605            return MANY;
606
607          return OTHER;
608        },
609        Sets.sortedSet(
610            ONE,
611            TWO,
612            FEW,
613            MANY,
614            OTHER
615        ),
616        Maps.sortedMap(
617            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1, 5, 7, 8, 9, 10)),
618            MapEntry.of(Ordinality.TWO, Range.ofFiniteValues(2, 3)),
619            MapEntry.of(Ordinality.FEW, Range.ofFiniteValues(4)),
620            MapEntry.of(Ordinality.MANY, Range.ofFiniteValues(6)),
621            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 100, 1000, 10000, 100000, 1000000))
622        )
623    ),
624
625    /**
626     * Languages Include:
627     * <p>
628     * <ul>
629     * <li>Gujarati (gu)</li>
630     * <li>Hindi (hi)</li>
631     * </ul>
632     */
633    FAMILY_4(
634        (n) -> {
635          // n = 1
636          if (equal(n, BIG_DECIMAL_1))
637            return ONE;
638          // n = 2,3
639          if (inSet(n, BIG_DECIMAL_2, BIG_DECIMAL_3))
640            return TWO;
641          // n = 4
642          if (equal(n, BIG_DECIMAL_4))
643            return FEW;
644          // n = 6
645          if (equal(n, BIG_DECIMAL_6))
646            return MANY;
647
648          return OTHER;
649        },
650        Sets.sortedSet(
651            ONE,
652            TWO,
653            FEW,
654            MANY,
655            OTHER
656        ),
657        Maps.sortedMap(
658            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
659            MapEntry.of(Ordinality.TWO, Range.ofFiniteValues(2, 3)),
660            MapEntry.of(Ordinality.FEW, Range.ofFiniteValues(4)),
661            MapEntry.of(Ordinality.MANY, Range.ofFiniteValues(6)),
662            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 100, 1000, 10000, 100000, 1000000))
663        )
664    ),
665
666    /**
667     * Languages Include:
668     * <p>
669     * <ul>
670     * <li>Azeri (az)</li>
671     * </ul>
672     */
673    FAMILY_5(
674        (n) -> {
675          BigInteger i = NumberUtils.integerComponent(n);
676
677          // i % 10 = 1,2,5,7,8 or i % 100 = 20,50,70,80
678          if (inSet(i.mod(BIG_INTEGER_10), BIG_INTEGER_1, BIG_INTEGER_2, BIG_INTEGER_5, BIG_INTEGER_7, BIG_INTEGER_8)
679              || inSet(i.mod(BIG_INTEGER_100), BIG_INTEGER_20, BIG_INTEGER_50, BIG_INTEGER_70, BIG_INTEGER_80))
680            return ONE;
681          // i % 10 = 3,4 or i % 1000 = 100,200,300,400,500,600,700,800,900
682          if (inSet(i.mod(BIG_INTEGER_10), BIG_INTEGER_3, BIG_INTEGER_4)
683              || inSet(i.mod(BIG_INTEGER_1_000), BIG_INTEGER_100, BIG_INTEGER_200, BIG_INTEGER_300, BIG_INTEGER_400, BIG_INTEGER_500, BIG_INTEGER_600, BIG_INTEGER_700, BIG_INTEGER_800, BIG_INTEGER_900))
684            return FEW;
685          // i = 0 or i % 10 = 6 or i % 100 = 40,60,90
686          if (equal(i, BIG_INTEGER_0)
687              || equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_6)
688              || inSet(i.mod(BIG_INTEGER_100), BIG_INTEGER_40, BIG_INTEGER_60, BIG_INTEGER_90))
689            return MANY;
690
691          return OTHER;
692        },
693        Sets.sortedSet(
694            ONE,
695            FEW,
696            MANY,
697            OTHER
698        ),
699        Maps.sortedMap(
700            MapEntry.of(Ordinality.ONE, Range.ofInfiniteValues(1, 2, 5, 7, 8, 11, 12, 15, 17, 18, 20, 21, 22, 25, 101, 1001)),
701            MapEntry.of(Ordinality.FEW, Range.ofInfiniteValues(3, 4, 13, 14, 23, 24, 33, 34, 43, 44, 53, 54, 63, 64, 73, 74, 100, 1003)),
702            MapEntry.of(Ordinality.MANY, Range.ofInfiniteValues(0, 6, 16, 26, 36, 40, 46, 56, 106, 1006)),
703            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(9, 10, 19, 29, 30, 39, 49, 59, 69, 79, 109, 1000, 10000, 100000, 1000000))
704        )
705    ),
706
707    /**
708     * Languages Include:
709     * <p>
710     * <ul>
711     * <li>Belarusian (be)</li>
712     * </ul>
713     */
714    FAMILY_6(
715        (n) -> {
716          // n % 10 = 2,3 and n % 100 != 12,13
717          if (inSet(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_2, BIG_DECIMAL_3)
718              && notInSet(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_12, BIG_DECIMAL_13))
719            return FEW;
720
721          return OTHER;
722        },
723        Sets.sortedSet(
724            FEW,
725            OTHER
726        ),
727        Maps.sortedMap(
728            MapEntry.of(Ordinality.FEW, Range.ofInfiniteValues(2, 3, 22, 23, 32, 33, 42, 43, 52, 53, 62, 63, 72, 73, 82, 83, 102, 1002)),
729            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
730        )
731    ),
732
733    /**
734     * Languages Include:
735     * <p>
736     * <ul>
737     * <li>Catalan (ca)</li>
738     * </ul>
739     */
740    FAMILY_7(
741        (n) -> {
742          // n = 1,3
743          if (inSet(n, BIG_DECIMAL_1, BIG_DECIMAL_3))
744            return ONE;
745          // n = 2
746          if (equal(n, BIG_DECIMAL_2))
747            return TWO;
748          // n = 4
749          if (equal(n, BIG_DECIMAL_4))
750            return FEW;
751
752          return OTHER;
753        },
754        Sets.sortedSet(
755            ONE,
756            TWO,
757            FEW,
758            OTHER
759        ),
760        Maps.sortedMap(
761            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1, 3)),
762            MapEntry.of(Ordinality.TWO, Range.ofFiniteValues(2)),
763            MapEntry.of(Ordinality.FEW, Range.ofFiniteValues(4)),
764            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
765        )
766    ),
767
768    /**
769     * Languages Include:
770     * <p>
771     * <ul>
772     * <li>Welsh (cy)</li>
773     * </ul>
774     */
775    FAMILY_8(
776        (n) -> {
777          // n = 0,7,8,9
778          if (inSet(n, BIG_DECIMAL_0, BIG_DECIMAL_7, BIG_DECIMAL_8, BIG_DECIMAL_9))
779            return ZERO;
780          // n = 1
781          if (equal(n, BIG_DECIMAL_1))
782            return ONE;
783          // n = 2
784          if (equal(n, BIG_DECIMAL_2))
785            return TWO;
786          // n = 3,4
787          if (inSet(n, BIG_DECIMAL_3, BIG_DECIMAL_4))
788            return FEW;
789          // n = 5,6
790          if (inSet(n, BIG_DECIMAL_5, BIG_DECIMAL_6))
791            return MANY;
792
793          return OTHER;
794        },
795        Sets.sortedSet(
796            ZERO,
797            ONE,
798            TWO,
799            FEW,
800            MANY,
801            OTHER
802        ),
803        Maps.sortedMap(
804            MapEntry.of(Ordinality.ZERO, Range.ofFiniteValues(0, 7, 8, 9)),
805            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
806            MapEntry.of(Ordinality.TWO, Range.ofFiniteValues(2)),
807            MapEntry.of(Ordinality.FEW, Range.ofFiniteValues(3, 4)),
808            MapEntry.of(Ordinality.MANY, Range.ofFiniteValues(5, 6)),
809            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 100, 1000, 10000, 100000, 1000000))
810        )
811    ),
812
813    /**
814     * Languages Include:
815     * <p>
816     * <ul>
817     * <li>English (en)</li>
818     * </ul>
819     */
820    FAMILY_9(
821        (n) -> {
822          // n % 10 = 1 and n % 100 != 11
823          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11))
824            return ONE;
825          // n % 10 = 2 and n % 100 != 12
826          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_2) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_12))
827            return TWO;
828          // n % 10 = 3 and n % 100 != 13
829          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_3) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_13))
830            return FEW;
831
832          return OTHER;
833        },
834        Sets.sortedSet(
835            ONE,
836            TWO,
837            FEW,
838            OTHER
839        ),
840        Maps.sortedMap(
841            MapEntry.of(Ordinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
842            MapEntry.of(Ordinality.TWO, Range.ofInfiniteValues(2, 22, 32, 42, 52, 62, 72, 82, 102, 1002)),
843            MapEntry.of(Ordinality.FEW, Range.ofInfiniteValues(3, 23, 33, 43, 53, 63, 73, 83, 103, 1003)),
844            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 100, 1000, 10000, 100000, 1000000))
845        )
846    ),
847
848    /**
849     * Languages Include:
850     * <p>
851     * <ul>
852     * <li>Hungarian (hu)</li>
853     * </ul>
854     */
855    FAMILY_10(
856        (n) -> {
857          // n = 1,5
858          if (inSet(n, BIG_DECIMAL_1, BIG_DECIMAL_5))
859            return ONE;
860
861          return OTHER;
862        },
863        Sets.sortedSet(
864            ONE,
865            OTHER
866        ),
867        Maps.sortedMap(
868            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1, 5)),
869            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
870        )
871    ),
872
873    /**
874     * Languages Include:
875     * <p>
876     * <ul>
877     * <li>Italian (it)</li>
878     * </ul>
879     */
880    FAMILY_11(
881        (n) -> {
882          // n = 11,8,80,800
883          if (inSet(n, BIG_DECIMAL_11, BIG_DECIMAL_8, BIG_DECIMAL_80, BIG_DECIMAL_800))
884            return MANY;
885
886          return OTHER;
887        },
888        Sets.sortedSet(
889            MANY,
890            OTHER
891        ),
892        Maps.sortedMap(
893            MapEntry.of(Ordinality.MANY, Range.ofFiniteValues(8, 11, 80, 800)),
894            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
895        )
896    ),
897
898    /**
899     * Languages Include:
900     * <p>
901     * <ul>
902     * <li>Georgian (ka)</li>
903     * </ul>
904     */
905    FAMILY_12(
906        (n) -> {
907          BigInteger i = NumberUtils.integerComponent(n);
908
909          // i = 1
910          if (equal(i, BIG_INTEGER_1))
911            return ONE;
912          // i = 0 or i % 100 = 2..20,40,60,80
913          if (equal(i, BIG_INTEGER_0) || inRange(i.mod(BIG_INTEGER_100), BIG_INTEGER_2, BIG_INTEGER_20) || inSet(i.mod(BIG_INTEGER_100), BIG_INTEGER_40, BIG_INTEGER_60, BIG_INTEGER_80))
914            return MANY;
915
916          return OTHER;
917        },
918        Sets.sortedSet(
919            ONE,
920            MANY,
921            OTHER
922        ),
923        Maps.sortedMap(
924            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
925            MapEntry.of(Ordinality.MANY, Range.ofInfiniteValues(0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 102, 1002)),
926            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 100, 1000, 10000, 100000, 1000000))
927        )
928    ),
929
930    /**
931     * Languages Include:
932     * <p>
933     * <ul>
934     * <li>Kazakh (kk)</li>
935     * </ul>
936     */
937    FAMILY_13(
938        (n) -> {
939          // n % 10 = 6 or n % 10 = 9 or n % 10 = 0 and n != 0
940          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_6)
941              || equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_9)
942              || (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_0) && notEqual(n, BIG_DECIMAL_0)))
943            return MANY;
944
945          return OTHER;
946        },
947        Sets.sortedSet(
948            MANY,
949            OTHER
950        ),
951        Maps.sortedMap(
952            MapEntry.of(Ordinality.MANY, Range.ofInfiniteValues(6, 9, 10, 16, 19, 20, 26, 29, 30, 36, 39, 40, 100, 1000, 10000, 100000, 1000000)),
953            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 13, 14, 15, 17, 18, 21, 101, 1001))
954        )
955    ),
956
957    /**
958     * Languages Include:
959     * <p>
960     * <ul>
961     * <li>Macedonian (mk)</li>
962     * </ul>
963     */
964    FAMILY_14(
965        (n) -> {
966          BigInteger i = NumberUtils.integerComponent(n);
967
968          // i % 10 = 1 and i % 100 != 11
969          if (equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_1) && notEqual(i.mod(BIG_INTEGER_100), BIG_INTEGER_11))
970            return ONE;
971          // i % 10 = 2 and i % 100 != 12
972          if (equal(i.mod(BIG_INTEGER_10), BIG_INTEGER_2) && notEqual(i.mod(BIG_INTEGER_100), BIG_INTEGER_12))
973            return TWO;
974          // i % 10 = 7,8 and i % 100 != 17,18
975          if (inSet(i.mod(BIG_INTEGER_10), BIG_INTEGER_7, BIG_INTEGER_8) && notInSet(i.mod(BIG_INTEGER_100), BIG_INTEGER_17, BIG_INTEGER_18))
976            return MANY;
977
978          return OTHER;
979        },
980        Sets.sortedSet(
981            ONE,
982            TWO,
983            MANY,
984            OTHER
985        ),
986        Maps.sortedMap(
987            MapEntry.of(Ordinality.ONE, Range.ofInfiniteValues(1, 21, 31, 41, 51, 61, 71, 81, 101, 1001)),
988            MapEntry.of(Ordinality.TWO, Range.ofInfiniteValues(2, 22, 32, 42, 52, 62, 72, 82, 102, 1002)),
989            MapEntry.of(Ordinality.MANY, Range.ofInfiniteValues(7, 8, 27, 28, 37, 38, 47, 48, 57, 58, 67, 68, 77, 78, 87, 88, 107, 1007)),
990            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
991        )
992    ),
993
994    /**
995     * Languages Include:
996     * <p>
997     * <ul>
998     * <li>Marathi (mr)</li>
999     * </ul>
1000     */
1001    FAMILY_15(
1002        (n) -> {
1003          // n = 1
1004          if (equal(n, BIG_DECIMAL_1))
1005            return ONE;
1006          // n = 2,3
1007          if (inSet(n, BIG_DECIMAL_2, BIG_DECIMAL_3))
1008            return TWO;
1009          // n = 4
1010          if (equal(n, BIG_DECIMAL_4))
1011            return FEW;
1012
1013          return OTHER;
1014        },
1015        Sets.sortedSet(
1016            ONE,
1017            TWO,
1018            FEW,
1019            OTHER
1020        ),
1021        Maps.sortedMap(
1022            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
1023            MapEntry.of(Ordinality.TWO, Range.ofFiniteValues(2, 3)),
1024            MapEntry.of(Ordinality.FEW, Range.ofFiniteValues(4)),
1025            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1026        )
1027    ),
1028
1029    /**
1030     * Languages Include:
1031     * <p>
1032     * <ul>
1033     * <li>Nepali (ne)</li>
1034     * </ul>
1035     */
1036    FAMILY_16(
1037        (n) -> {
1038          // n = 1..4
1039          if (inRange(n, BIG_DECIMAL_1, BIG_DECIMAL_4))
1040            return ONE;
1041
1042          return OTHER;
1043        },
1044        Sets.sortedSet(
1045            ONE,
1046            OTHER
1047        ),
1048        Maps.sortedMap(
1049            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1, 2, 3, 4)),
1050            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 1000, 10000, 100000, 1000000))
1051        )
1052    ),
1053
1054    /**
1055     * Languages Include:
1056     * <p>
1057     * <ul>
1058     * <li>Albanian (sq)</li>
1059     * </ul>
1060     */
1061    FAMILY_17(
1062        (n) -> {
1063          // n = 1
1064          if (equal(n, BIG_DECIMAL_1))
1065            return ONE;
1066          // n % 10 = 4 and n % 100 != 14
1067          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_4) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_14))
1068            return MANY;
1069
1070          return OTHER;
1071        },
1072        Sets.sortedSet(
1073            ONE,
1074            MANY,
1075            OTHER
1076        ),
1077        Maps.sortedMap(
1078            MapEntry.of(Ordinality.ONE, Range.ofFiniteValues(1)),
1079            MapEntry.of(Ordinality.MANY, Range.ofInfiniteValues(4, 24, 34, 44, 54, 64, 74, 84, 104, 1004)),
1080            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1081        )
1082    ),
1083
1084    /**
1085     * Languages Include:
1086     * <p>
1087     * <ul>
1088     * <li>Swedish (sv)</li>
1089     * </ul>
1090     */
1091    FAMILY_18(
1092        (n) -> {
1093          // n % 10 = 1,2 and n % 100 != 11,12
1094          if (inSet(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_1, BIG_DECIMAL_2) && notInSet(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_11, BIG_DECIMAL_12))
1095            return ONE;
1096
1097          return OTHER;
1098        },
1099        Sets.sortedSet(
1100            ONE,
1101            OTHER
1102        ),
1103        Maps.sortedMap(
1104            MapEntry.of(Ordinality.ONE, Range.ofInfiniteValues(1, 2, 21, 22, 31, 32, 41, 42, 51, 52, 61, 62, 71, 72, 81, 82, 101, 1001)),
1105            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 100, 1000, 10000, 100000, 1000000))
1106        )
1107    ),
1108
1109    /**
1110     * Languages Include:
1111     * <p>
1112     * <ul>
1113     * <li>Ukrainian (uk)</li>
1114     * </ul>
1115     */
1116    FAMILY_19(
1117        (n) -> {
1118          // n % 10 = 3 and n % 100 != 13
1119          if (equal(n.remainder(BIG_DECIMAL_10), BIG_DECIMAL_3) && notEqual(n.remainder(BIG_DECIMAL_100), BIG_DECIMAL_13))
1120            return FEW;
1121
1122          return OTHER;
1123        },
1124        Sets.sortedSet(
1125            FEW,
1126            OTHER
1127        ),
1128        Maps.sortedMap(
1129            MapEntry.of(Ordinality.FEW, Range.ofInfiniteValues(3, 23, 33, 43, 53, 63, 73, 83, 103, 1003)),
1130            MapEntry.of(Ordinality.OTHER, Range.ofInfiniteValues(0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 100, 1000, 10000, 100000, 1000000))
1131        )
1132    );
1133
1134    @NonNull
1135    private static final Map<@NonNull String, @NonNull OrdinalityFamily> ORDINALITY_FAMILIES_BY_LANGUAGE_CODE;
1136    @NonNull
1137    private static final SortedSet<@NonNull String> SUPPORTED_LANGUAGE_CODES;
1138
1139    @NonNull
1140    private final Function<BigDecimal, Ordinality> ordinalityFunction;
1141    @NonNull
1142    private final SortedSet<@NonNull Ordinality> supportedOrdinalities;
1143    @NonNull
1144    private final SortedMap<@NonNull Ordinality, @NonNull Range<@NonNull Integer>> exampleIntegerValuesByOrdinality;
1145
1146    /**
1147     * Constructs an ordinality family.
1148     *
1149     * @param ordinalityFunction               the ordinality-determining function for this ordinality family, not null
1150     * @param supportedOrdinalities            the ordinalities supported by this family sorted by the natural ordering of {@link Ordinality}, not null
1151     * @param exampleIntegerValuesByOrdinality a mapping of ordinalities to example integer values for this ordinality family sorted by the natural ordering of {@link Ordinality}, not null
1152     */
1153    OrdinalityFamily(@NonNull Function<BigDecimal, Ordinality> ordinalityFunction,
1154                     @NonNull SortedSet<@NonNull Ordinality> supportedOrdinalities,
1155                     @NonNull SortedMap<@NonNull Ordinality, @NonNull Range<@NonNull Integer>> exampleIntegerValuesByOrdinality) {
1156      requireNonNull(ordinalityFunction);
1157      requireNonNull(supportedOrdinalities);
1158      requireNonNull(exampleIntegerValuesByOrdinality);
1159
1160      this.ordinalityFunction = ordinalityFunction;
1161      this.supportedOrdinalities = supportedOrdinalities;
1162      this.exampleIntegerValuesByOrdinality = exampleIntegerValuesByOrdinality;
1163    }
1164
1165    static {
1166      ORDINALITY_FAMILIES_BY_LANGUAGE_CODE = Collections.unmodifiableMap(new HashMap<@NonNull String, @NonNull OrdinalityFamily>() {{
1167        put("af", OrdinalityFamily.FAMILY_1); // Afrikaans
1168        put("ak", OrdinalityFamily.FAMILY_1); // Akan (no CLDR data available)
1169        put("am", OrdinalityFamily.FAMILY_1); // Amharic
1170        put("ar", OrdinalityFamily.FAMILY_1); // Arabic
1171        put("ars", OrdinalityFamily.FAMILY_1); // Najdi Arabic (no CLDR data available)
1172        put("as", OrdinalityFamily.FAMILY_3); // Assamese
1173        put("asa", OrdinalityFamily.FAMILY_1); // Asu (no CLDR data available)
1174        put("ast", OrdinalityFamily.FAMILY_1); // Asturian (no CLDR data available)
1175        put("az", OrdinalityFamily.FAMILY_5); // Azeri
1176        put("be", OrdinalityFamily.FAMILY_6); // Belarusian
1177        put("bem", OrdinalityFamily.FAMILY_1); // Bemba (no CLDR data available)
1178        put("bez", OrdinalityFamily.FAMILY_1); // Bena (no CLDR data available)
1179        put("bg", OrdinalityFamily.FAMILY_1); // Bulgarian
1180        put("bh", OrdinalityFamily.FAMILY_1); // Bihari (no CLDR data available)
1181        put("bm", OrdinalityFamily.FAMILY_1); // Bambara (no CLDR data available)
1182        put("bn", OrdinalityFamily.FAMILY_3); // Bangla
1183        put("bo", OrdinalityFamily.FAMILY_1); // Tibetan (no CLDR data available)
1184        put("br", OrdinalityFamily.FAMILY_1); // Breton (no CLDR data available)
1185        put("brx", OrdinalityFamily.FAMILY_1); // Bodo (no CLDR data available)
1186        put("bs", OrdinalityFamily.FAMILY_1); // Bosnian
1187        put("ca", OrdinalityFamily.FAMILY_7); // Catalan
1188        put("ce", OrdinalityFamily.FAMILY_1); // Chechen
1189        put("cgg", OrdinalityFamily.FAMILY_1); // Chiga (no CLDR data available)
1190        put("chr", OrdinalityFamily.FAMILY_1); // Cherokee (no CLDR data available)
1191        put("ckb", OrdinalityFamily.FAMILY_1); // Central Kurdish (no CLDR data available)
1192        put("cs", OrdinalityFamily.FAMILY_1); // Czech
1193        put("cy", OrdinalityFamily.FAMILY_8); // Welsh
1194        put("da", OrdinalityFamily.FAMILY_1); // Danish
1195        put("de", OrdinalityFamily.FAMILY_1); // German
1196        put("dsb", OrdinalityFamily.FAMILY_1); // Lower Sorbian
1197        put("dv", OrdinalityFamily.FAMILY_1); // Divehi (no CLDR data available)
1198        put("dz", OrdinalityFamily.FAMILY_1); // Dzongkha (no CLDR data available)
1199        put("ee", OrdinalityFamily.FAMILY_1); // Ewe (no CLDR data available)
1200        put("el", OrdinalityFamily.FAMILY_1); // Greek
1201        put("en", OrdinalityFamily.FAMILY_9); // English
1202        put("eo", OrdinalityFamily.FAMILY_1); // Esperanto (no CLDR data available)
1203        put("es", OrdinalityFamily.FAMILY_1); // Spanish
1204        put("et", OrdinalityFamily.FAMILY_1); // Estonian
1205        put("eu", OrdinalityFamily.FAMILY_1); // Basque
1206        put("fa", OrdinalityFamily.FAMILY_1); // Persian
1207        put("ff", OrdinalityFamily.FAMILY_1); // Fulah (no CLDR data available)
1208        put("fi", OrdinalityFamily.FAMILY_1); // Finnish
1209        put("fil", OrdinalityFamily.FAMILY_2); // Filipino
1210        put("fo", OrdinalityFamily.FAMILY_1); // Faroese (no CLDR data available)
1211        put("fr", OrdinalityFamily.FAMILY_2); // French
1212        put("fur", OrdinalityFamily.FAMILY_1); // Friulian (no CLDR data available)
1213        put("fy", OrdinalityFamily.FAMILY_1); // Western Frisian
1214        put("ga", OrdinalityFamily.FAMILY_2); // Irish
1215        put("gd", OrdinalityFamily.FAMILY_1); // Scottish Gaelic (no CLDR data available)
1216        put("gl", OrdinalityFamily.FAMILY_1); // Galician
1217        put("gsw", OrdinalityFamily.FAMILY_1); // Swiss German
1218        put("gu", OrdinalityFamily.FAMILY_4); // Gujarati
1219        put("guw", OrdinalityFamily.FAMILY_1); // Gun (no CLDR data available)
1220        put("gv", OrdinalityFamily.FAMILY_1); // Manx (no CLDR data available)
1221        put("ha", OrdinalityFamily.FAMILY_1); // Hausa (no CLDR data available)
1222        put("haw", OrdinalityFamily.FAMILY_1); // Hawaiian (no CLDR data available)
1223        put("he", OrdinalityFamily.FAMILY_1); // Hebrew
1224        put("hi", OrdinalityFamily.FAMILY_4); // Hindi
1225        put("hr", OrdinalityFamily.FAMILY_1); // Croatian
1226        put("hsb", OrdinalityFamily.FAMILY_1); // Upper Sorbian
1227        put("hu", OrdinalityFamily.FAMILY_10); // Hungarian
1228        put("hy", OrdinalityFamily.FAMILY_2); // Armenian
1229        put("id", OrdinalityFamily.FAMILY_1); // Indonesian
1230        put("ig", OrdinalityFamily.FAMILY_1); // Igbo (no CLDR data available)
1231        put("ii", OrdinalityFamily.FAMILY_1); // Sichuan Yi (no CLDR data available)
1232        put("is", OrdinalityFamily.FAMILY_1); // Icelandic
1233        put("it", OrdinalityFamily.FAMILY_11); // Italian
1234        put("iu", OrdinalityFamily.FAMILY_1); // Inuktitut (no CLDR data available)
1235        put("ja", OrdinalityFamily.FAMILY_1); // Japanese
1236        put("jbo", OrdinalityFamily.FAMILY_1); // Lojban (no CLDR data available)
1237        put("jgo", OrdinalityFamily.FAMILY_1); // Ngomba (no CLDR data available)
1238        put("jmc", OrdinalityFamily.FAMILY_1); // Machame (no CLDR data available)
1239        put("jv", OrdinalityFamily.FAMILY_1); // Javanese (no CLDR data available)
1240        put("jw", OrdinalityFamily.FAMILY_1); // Javanese (no CLDR data available)
1241        put("ka", OrdinalityFamily.FAMILY_12); // Georgian
1242        put("kab", OrdinalityFamily.FAMILY_1); // Kabyle (no CLDR data available)
1243        put("kaj", OrdinalityFamily.FAMILY_1); // Jju (no CLDR data available)
1244        put("kcg", OrdinalityFamily.FAMILY_1); // Tyap (no CLDR data available)
1245        put("kde", OrdinalityFamily.FAMILY_1); // Makonde (no CLDR data available)
1246        put("kea", OrdinalityFamily.FAMILY_1); // Kabuverdianu (no CLDR data available)
1247        put("kk", OrdinalityFamily.FAMILY_13); // Kazakh
1248        put("kkj", OrdinalityFamily.FAMILY_1); // Kako (no CLDR data available)
1249        put("kl", OrdinalityFamily.FAMILY_1); // Greenlandic (no CLDR data available)
1250        put("km", OrdinalityFamily.FAMILY_1); // Khmer
1251        put("kn", OrdinalityFamily.FAMILY_1); // Kannada
1252        put("ko", OrdinalityFamily.FAMILY_1); // Korean
1253        put("ks", OrdinalityFamily.FAMILY_1); // Kashmiri (no CLDR data available)
1254        put("ksb", OrdinalityFamily.FAMILY_1); // Shambala (no CLDR data available)
1255        put("ksh", OrdinalityFamily.FAMILY_1); // Colognian (no CLDR data available)
1256        put("ku", OrdinalityFamily.FAMILY_1); // Kurdish (no CLDR data available)
1257        put("kw", OrdinalityFamily.FAMILY_1); // Cornish (no CLDR data available)
1258        put("ky", OrdinalityFamily.FAMILY_1); // Kirghiz
1259        put("lag", OrdinalityFamily.FAMILY_1); // Langi (no CLDR data available)
1260        put("lb", OrdinalityFamily.FAMILY_1); // Luxembourgish (no CLDR data available)
1261        put("lg", OrdinalityFamily.FAMILY_1); // Ganda (no CLDR data available)
1262        put("lkt", OrdinalityFamily.FAMILY_1); // Lakota (no CLDR data available)
1263        put("ln", OrdinalityFamily.FAMILY_1); // Lingala (no CLDR data available)
1264        put("lo", OrdinalityFamily.FAMILY_2); // Lao
1265        put("lt", OrdinalityFamily.FAMILY_1); // Lithuanian
1266        put("lv", OrdinalityFamily.FAMILY_1); // Latvian
1267        put("mas", OrdinalityFamily.FAMILY_1); // Masai (no CLDR data available)
1268        put("mg", OrdinalityFamily.FAMILY_1); // Malagasy (no CLDR data available)
1269        put("mgo", OrdinalityFamily.FAMILY_1); // Metaʼ (no CLDR data available)
1270        put("mk", OrdinalityFamily.FAMILY_14); // Macedonian
1271        put("ml", OrdinalityFamily.FAMILY_1); // Malayalam
1272        put("mn", OrdinalityFamily.FAMILY_1); // Mongolian
1273        put("mo", OrdinalityFamily.FAMILY_2); // Moldovan
1274        put("mr", OrdinalityFamily.FAMILY_15); // Marathi
1275        put("ms", OrdinalityFamily.FAMILY_2); // Malay
1276        put("mt", OrdinalityFamily.FAMILY_1); // Maltese (no CLDR data available)
1277        put("my", OrdinalityFamily.FAMILY_1); // Burmese
1278        put("nah", OrdinalityFamily.FAMILY_1); // Nahuatl (no CLDR data available)
1279        put("naq", OrdinalityFamily.FAMILY_1); // Nama (no CLDR data available)
1280        put("nb", OrdinalityFamily.FAMILY_1); // Norwegian Bokmål
1281        put("nd", OrdinalityFamily.FAMILY_1); // North Ndebele (no CLDR data available)
1282        put("ne", OrdinalityFamily.FAMILY_16); // Nepali
1283        put("nl", OrdinalityFamily.FAMILY_1); // Dutch
1284        put("nn", OrdinalityFamily.FAMILY_1); // Norwegian Nynorsk (no CLDR data available)
1285        put("nnh", OrdinalityFamily.FAMILY_1); // Ngiemboon (no CLDR data available)
1286        put("no", OrdinalityFamily.FAMILY_1); // Norwegian (no CLDR data available)
1287        put("nqo", OrdinalityFamily.FAMILY_1); // N’Ko (no CLDR data available)
1288        put("nr", OrdinalityFamily.FAMILY_1); // South Ndebele (no CLDR data available)
1289        put("nso", OrdinalityFamily.FAMILY_1); // Northern Sotho (no CLDR data available)
1290        put("ny", OrdinalityFamily.FAMILY_1); // Nyanja (no CLDR data available)
1291        put("nyn", OrdinalityFamily.FAMILY_1); // Nyankole (no CLDR data available)
1292        put("om", OrdinalityFamily.FAMILY_1); // Oromo (no CLDR data available)
1293        put("or", OrdinalityFamily.FAMILY_1); // Odia (no CLDR data available)
1294        put("os", OrdinalityFamily.FAMILY_1); // Ossetian (no CLDR data available)
1295        put("pa", OrdinalityFamily.FAMILY_1); // Punjabi
1296        put("pap", OrdinalityFamily.FAMILY_1); // Papiamento (no CLDR data available)
1297        put("pl", OrdinalityFamily.FAMILY_1); // Polish
1298        put("prg", OrdinalityFamily.FAMILY_1); // Prussian
1299        put("ps", OrdinalityFamily.FAMILY_1); // Pushto (no CLDR data available)
1300        put("pt", OrdinalityFamily.FAMILY_1); // Portuguese
1301        put("rm", OrdinalityFamily.FAMILY_1); // Romansh (no CLDR data available)
1302        put("ro", OrdinalityFamily.FAMILY_2); // Romanian
1303        put("rof", OrdinalityFamily.FAMILY_1); // Rombo (no CLDR data available)
1304        put("root", OrdinalityFamily.FAMILY_1); // Root
1305        put("ru", OrdinalityFamily.FAMILY_1); // Russian
1306        put("rwk", OrdinalityFamily.FAMILY_1); // Rwa (no CLDR data available)
1307        put("sah", OrdinalityFamily.FAMILY_1); // Sakha (no CLDR data available)
1308        put("saq", OrdinalityFamily.FAMILY_1); // Samburu (no CLDR data available)
1309        put("sdh", OrdinalityFamily.FAMILY_1); // Southern Kurdish (no CLDR data available)
1310        put("se", OrdinalityFamily.FAMILY_1); // Northern Sami (no CLDR data available)
1311        put("seh", OrdinalityFamily.FAMILY_1); // Sena (no CLDR data available)
1312        put("ses", OrdinalityFamily.FAMILY_1); // Koyraboro Senni (no CLDR data available)
1313        put("sg", OrdinalityFamily.FAMILY_1); // Sango (no CLDR data available)
1314        put("sh", OrdinalityFamily.FAMILY_1); // Serbo-Croatian
1315        put("shi", OrdinalityFamily.FAMILY_1); // Tachelhit (no CLDR data available)
1316        put("si", OrdinalityFamily.FAMILY_1); // Sinhalese
1317        put("sk", OrdinalityFamily.FAMILY_1); // Slovak
1318        put("sl", OrdinalityFamily.FAMILY_1); // Slovenian
1319        put("sma", OrdinalityFamily.FAMILY_1); // Southern Sami (no CLDR data available)
1320        put("smi", OrdinalityFamily.FAMILY_1); // Sami (no CLDR data available)
1321        put("smj", OrdinalityFamily.FAMILY_1); // Lule Sami (no CLDR data available)
1322        put("smn", OrdinalityFamily.FAMILY_1); // Inari Sami (no CLDR data available)
1323        put("sms", OrdinalityFamily.FAMILY_1); // Skolt Sami (no CLDR data available)
1324        put("sn", OrdinalityFamily.FAMILY_1); // Shona (no CLDR data available)
1325        put("so", OrdinalityFamily.FAMILY_1); // Somali (no CLDR data available)
1326        put("sq", OrdinalityFamily.FAMILY_17); // Albanian
1327        put("sr", OrdinalityFamily.FAMILY_1); // Serbian
1328        put("ss", OrdinalityFamily.FAMILY_1); // Swati (no CLDR data available)
1329        put("ssy", OrdinalityFamily.FAMILY_1); // Saho (no CLDR data available)
1330        put("st", OrdinalityFamily.FAMILY_1); // Southern Sotho (no CLDR data available)
1331        put("sv", OrdinalityFamily.FAMILY_18); // Swedish
1332        put("sw", OrdinalityFamily.FAMILY_1); // Swahili
1333        put("syr", OrdinalityFamily.FAMILY_1); // Syriac (no CLDR data available)
1334        put("ta", OrdinalityFamily.FAMILY_1); // Tamil
1335        put("te", OrdinalityFamily.FAMILY_1); // Telugu
1336        put("teo", OrdinalityFamily.FAMILY_1); // Teso (no CLDR data available)
1337        put("th", OrdinalityFamily.FAMILY_1); // Thai
1338        put("ti", OrdinalityFamily.FAMILY_1); // Tigrinya (no CLDR data available)
1339        put("tig", OrdinalityFamily.FAMILY_1); // Tigre (no CLDR data available)
1340        put("tk", OrdinalityFamily.FAMILY_1); // Turkmen (no CLDR data available)
1341        put("tl", OrdinalityFamily.FAMILY_2); // Tagalog
1342        put("tn", OrdinalityFamily.FAMILY_1); // Tswana (no CLDR data available)
1343        put("to", OrdinalityFamily.FAMILY_1); // Tongan (no CLDR data available)
1344        put("tr", OrdinalityFamily.FAMILY_1); // Turkish
1345        put("ts", OrdinalityFamily.FAMILY_1); // Tsonga (no CLDR data available)
1346        put("tzm", OrdinalityFamily.FAMILY_1); // Central Atlas Tamazight (no CLDR data available)
1347        put("ug", OrdinalityFamily.FAMILY_1); // Uighur (no CLDR data available)
1348        put("uk", OrdinalityFamily.FAMILY_19); // Ukrainian
1349        put("ur", OrdinalityFamily.FAMILY_1); // Urdu
1350        put("uz", OrdinalityFamily.FAMILY_1); // Uzbek
1351        put("ve", OrdinalityFamily.FAMILY_1); // Venda (no CLDR data available)
1352        put("vi", OrdinalityFamily.FAMILY_2); // Vietnamese
1353        put("vo", OrdinalityFamily.FAMILY_1); // Volapük (no CLDR data available)
1354        put("vun", OrdinalityFamily.FAMILY_1); // Vunjo (no CLDR data available)
1355        put("wa", OrdinalityFamily.FAMILY_1); // Walloon (no CLDR data available)
1356        put("wae", OrdinalityFamily.FAMILY_1); // Walser (no CLDR data available)
1357        put("wo", OrdinalityFamily.FAMILY_1); // Wolof (no CLDR data available)
1358        put("xh", OrdinalityFamily.FAMILY_1); // Xhosa (no CLDR data available)
1359        put("xog", OrdinalityFamily.FAMILY_1); // Soga (no CLDR data available)
1360        put("yi", OrdinalityFamily.FAMILY_1); // Yiddish (no CLDR data available)
1361        put("yo", OrdinalityFamily.FAMILY_1); // Yoruba (no CLDR data available)
1362        put("yue", OrdinalityFamily.FAMILY_1); // Cantonese
1363        put("zh", OrdinalityFamily.FAMILY_1); // Mandarin Chinese
1364        put("zu", OrdinalityFamily.FAMILY_1); // Zulu
1365      }});
1366
1367      // Language codes are in English - force collation for sorting
1368      SortedSet<@NonNull String> supportedLanguageCodes = new TreeSet<>(Collator.getInstance(Locale.ENGLISH));
1369      supportedLanguageCodes.addAll(ORDINALITY_FAMILIES_BY_LANGUAGE_CODE.keySet());
1370
1371      SUPPORTED_LANGUAGE_CODES = Collections.unmodifiableSortedSet(supportedLanguageCodes);
1372    }
1373
1374    /**
1375     * Gets the ordinality-determining function for this ordinality family.
1376     * <p>
1377     * The function takes a numeric value as input and returns the appropriate ordinal form.
1378     * <p>
1379     * The function's input must not be null and its output is guaranteed non-null.
1380     *
1381     * @return the ordinality-determining function for this ordinality family, not null
1382     */
1383    @NonNull
1384    public Function<BigDecimal, Ordinality> getOrdinalityFunction() {
1385      return ordinalityFunction;
1386    }
1387
1388    /**
1389     * Gets the ordinalities supported by this ordinality family.
1390     * <p>
1391     * There will always be at least one value - {@link Ordinality#OTHER} - in the set.
1392     * <p>
1393     * The set's values are sorted by the natural ordering of the {@link Ordinality} enumeration.
1394     *
1395     * @return the ordinalities supported by this ordinality family, not null
1396     */
1397    @NonNull
1398    SortedSet<@NonNull Ordinality> getSupportedOrdinalities() {
1399      return supportedOrdinalities;
1400    }
1401
1402    /**
1403     * Gets a mapping of ordinalities to example integer values for this ordinality family.
1404     * <p>
1405     * The map may be empty.
1406     * <p>
1407     * The map's keys are sorted by the natural ordering of the {@link Ordinality} enumeration.
1408     *
1409     * @return a mapping of ordinalities to example integer values, not null
1410     */
1411    @NonNull
1412    SortedMap<@NonNull Ordinality, @NonNull Range<@NonNull Integer>> getExampleIntegerValuesByOrdinality() {
1413      return exampleIntegerValuesByOrdinality;
1414    }
1415
1416    /**
1417     * Gets the ISO 639 language codes for which ordinality operations are supported.
1418     * <p>
1419     * The set's values are ISO 639 codes and therefore sorted using English collation.
1420     *
1421     * @return the ISO 639 language codes for which ordinality operations are supported, not null
1422     */
1423    @NonNull
1424    static SortedSet<@NonNull String> getSupportedLanguageCodes() {
1425      return SUPPORTED_LANGUAGE_CODES;
1426    }
1427
1428    /**
1429     * Gets an appropriate plural ordinality family for the given locale.
1430     *
1431     * @param locale the locale to check, not null
1432     * @return the appropriate plural ordinality family (if one exists) for the given locale, not null
1433     */
1434    @NonNull
1435    static Optional<OrdinalityFamily> ordinalityFamilyForLocale(@NonNull Locale locale) {
1436      requireNonNull(locale);
1437
1438      String language = LocaleUtils.normalizedLanguage(locale).orElse(null);
1439      String country = locale.getCountry();
1440
1441      OrdinalityFamily ordinalityFamily = null;
1442
1443      if (language != null && country != null)
1444        ordinalityFamily = ORDINALITY_FAMILIES_BY_LANGUAGE_CODE.get(format("%s-%s", language, country));
1445
1446      if (ordinalityFamily != null)
1447        return Optional.of(ordinalityFamily);
1448
1449      if (language != null)
1450        ordinalityFamily = ORDINALITY_FAMILIES_BY_LANGUAGE_CODE.get(language);
1451
1452      return Optional.ofNullable(ordinalityFamily);
1453    }
1454  }
1455}