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 org.jspecify.annotations.NonNull; 020 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.Locale; 024import java.util.Map; 025import java.util.SortedMap; 026import java.util.SortedSet; 027import java.util.stream.Collectors; 028 029import static java.util.Objects.requireNonNull; 030 031/** 032 * Language plural ordinality forms. 033 * <p> 034 * For example, English has four: {@code 1st, 2nd, 3rd, 4th}, while Swedish has two: {@code 1:a, 3:e}. 035 * <p> 036 * See the <a href="http://cldr.unicode.org/index/cldr-spec/plural-rules">Unicode Common Locale Data Repository</a> 037 * and its <a href="https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html">Language Plural Rules</a> for details. 038 * <p> 039 * CLDR category names are mnemonics whose membership is locale-specific; they do not imply cardinal singular or plural 040 * meaning. For English ordinals, values ending in 1, 2, or 3 normally select {@link #ONE}, {@link #TWO}, or 041 * {@link #FEW}, except for values ending in 11, 12, or 13; remaining values select {@link #OTHER}. French ordinals use 042 * {@link #ONE} for 1 and {@link #OTHER} for other values. Applications should always use the generated locale rules 043 * rather than inferring ordinal categories from their names. 044 * 045 * @author <a href="https://revetkn.com">Mark Allen</a> 046 */ 047public enum Ordinality implements LanguageForm { 048 /** 049 * Normally the form used with 0, if it is limited to numbers whose integer values end with 0. 050 * <p> 051 * For example: the Welsh {@code 0fed ci} means "{@code 0th dog}" in English. 052 */ 053 ZERO, 054 /** 055 * The form used with 1. 056 * <p> 057 * For example: the Welsh {@code ci 1af} means {@code 1st dog} in English. 058 */ 059 ONE, 060 /** 061 * Normally the form used with 2, if it is limited to numbers whose integer values end with 2. 062 * <p> 063 * For example: the Welsh {@code 2il gi} means {@code 2nd dog} in English. 064 */ 065 TWO, 066 /** 067 * The form that falls between {@code TWO} and {@code MANY}. 068 * <p> 069 * For example: the Welsh {@code 3ydd ci} means {@code 3rd dog} in English. 070 */ 071 FEW, 072 /** 073 * The form that falls between {@code FEW} and {@code OTHER}. 074 * <p> 075 * For example: the Welsh {@code 5ed ci} means {@code 5th dog} in English. 076 */ 077 MANY, 078 /** 079 * General "catchall" form which comprises any cases not handled by the other forms. 080 * <p> 081 * For example: the Welsh {@code ci rhif 10} means {@code 10th dog} in English. 082 */ 083 OTHER; 084 085 @NonNull 086 static final Map<@NonNull String, @NonNull Ordinality> ORDINALITIES_BY_NAME; 087 088 static { 089 ORDINALITIES_BY_NAME = Collections.unmodifiableMap(Arrays.stream( 090 Ordinality.values()).collect(Collectors.toMap(ordinality -> ordinality.name(), ordinality -> ordinality))); 091 } 092 093 /** 094 * Gets an appropriate plural ordinality for the given number and locale. 095 * <p> 096 * Negative numbers are evaluated using their absolute value. 097 * Supported {@link Number} implementations and their conversion semantics are documented by 098 * {@link PluralOperands#forNumber(Number)}. 099 * This convenience method uses {@link TranslationRuntimeLimits#defaults()}. To apply different limits, construct 100 * {@link PluralOperands} with {@link PluralOperands.Builder#runtimeLimits(TranslationRuntimeLimits)} and call 101 * {@link #forOperands(PluralOperands, Locale)}. 102 * <p> 103 * See <a href="https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html">CLDR 48 Language Plural Rules</a> 104 * for a cheat sheet. 105 * 106 * @param number the number that drives pluralization, not null 107 * @param locale the locale that drives pluralization, not null 108 * @return an appropriate plural ordinality, not null 109 * @throws UnsupportedLocaleException if the locale is not supported 110 * @throws IllegalArgumentException if the locale is malformed, the number implementation is unsupported, the 111 * number is non-finite, or the number exceeds the safety limits documented by 112 * {@link PluralOperands} 113 */ 114 @NonNull 115 public static Ordinality forNumber(@NonNull Number number, @NonNull Locale locale) { 116 requireNonNull(number); 117 requireNonNull(locale); 118 119 return forOperands(PluralOperands.forNumber(number).build(), locale); 120 } 121 122 /** 123 * Gets an appropriate plural ordinality for the given CLDR plural operands and locale. 124 * <p> 125 * Most applications should use {@link #forNumber(Number, Locale)}. Use this overload when the displayed number has 126 * details that are not fully represented by the Java {@link Number}, such as a compact-decimal exponent. 127 * <p> 128 * See <a href="https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html">CLDR 48 Language Plural Rules</a> 129 * for a cheat sheet. 130 * 131 * @param operands the CLDR plural operands that drive pluralization, not null 132 * @param locale the locale that drives pluralization, not null 133 * @return an appropriate plural ordinality, not null 134 * @throws UnsupportedLocaleException if the locale is not supported 135 * @throws IllegalArgumentException if the locale is malformed 136 * @since 3.0.0 137 */ 138 @NonNull 139 public static Ordinality forOperands(@NonNull PluralOperands operands, @NonNull Locale locale) { 140 requireNonNull(operands); 141 requireNonNull(locale); 142 143 return CldrPluralRules.ordinalityForOperands(operands, locale); 144 } 145 146 /** 147 * Gets the set of ordinalities supported for the given locale. 148 * <p> 149 * The empty set will be returned if the locale is not supported. 150 * <p> 151 * The set's values are sorted by the natural ordering of the {@link Ordinality} enumeration. 152 * 153 * @param locale the locale to use for lookup, not null 154 * @return the ordinalities supported by the given locale, not null 155 * @throws IllegalArgumentException if the locale is malformed 156 */ 157 @NonNull 158 public static SortedSet<@NonNull Ordinality> supportedOrdinalitiesForLocale(@NonNull Locale locale) { 159 requireNonNull(locale); 160 return CldrPluralRules.supportedOrdinalitiesForLocale(locale); 161 } 162 163 /** 164 * Gets a mapping of ordinalities to example integer values for the given locale. 165 * <p> 166 * The empty map will be returned if the locale is not supported or if no example values are available. 167 * <p> 168 * The map's keys are sorted by the natural ordering of the {@link Ordinality} enumeration. 169 * 170 * @param locale the locale to use for lookup, not null 171 * @return a mapping of ordinalities to example integer values, not null 172 * @throws IllegalArgumentException if the locale is malformed 173 */ 174 @NonNull 175 public static SortedMap<@NonNull Ordinality, @NonNull Range<@NonNull Integer>> exampleIntegerValuesForLocale(@NonNull Locale locale) { 176 requireNonNull(locale); 177 return CldrPluralRules.ordinalityIntegerExamplesForLocale(locale); 178 } 179 180 /** 181 * Gets the BCP 47 locale tags represented directly in the generated CLDR plural-rule data and supported for 182 * ordinality operations. 183 * <p> 184 * This is not an exhaustive list of concrete locale tags accepted by ordinality operations. A locale with 185 * additional region or script subtags may be supported through fallback to a less-specific rule tag. Use 186 * {@link #supportedOrdinalitiesForLocale(Locale)} to check a concrete locale. 187 * <p> 188 * The set's values are sorted by natural string ordering. 189 * 190 * @return the directly represented BCP 47 locale tags supported for ordinality operations, not null 191 * @since 3.0.0 192 */ 193 @NonNull 194 public static SortedSet<@NonNull String> getSupportedLocaleTags() { 195 return CldrPluralRules.ordinalitySupportedLocales(); 196 } 197 198 /** 199 * Gets the mapping of ordinality names to values. 200 * 201 * @return the mapping of ordinality names to values, not null 202 */ 203 @NonNull 204 static Map<@NonNull String, @NonNull Ordinality> getOrdinalitiesByName() { 205 return ORDINALITIES_BY_NAME; 206 } 207}