icu_plurals/
lib.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Determine the plural category appropriate for a given number in a given language.
6//!
7//! This module is published as its own crate ([`icu_plurals`](https://docs.rs/icu_plurals/latest/icu_plurals/))
8//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
9//!
10//! For example in English, when constructing a message
11//! such as `{ num } items`, the user has to prepare
12//! two variants of the message:
13//!
14//! * `1 item`
15//! * `0 items`, `2 items`, `5 items`, `0.5 items` etc.
16//!
17//! The former variant is used when the placeholder variable has value `1`,
18//! while the latter is used for all other values of the variable.
19//!
20//! Unicode defines [Language Plural Rules] as a mechanism to codify those
21//! variants and provides data and algorithms to calculate
22//! appropriate [`PluralCategory`].
23//!
24//! # Examples
25//!
26//! ```
27//! use icu::locale::locale;
28//! use icu::plurals::{PluralCategory, PluralRules};
29//!
30//! let pr = PluralRules::try_new(locale!("en").into(), Default::default())
31//!     .expect("locale should be present");
32//!
33//! assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
34//! ```
35//!
36//! ## Plural Rules
37//!
38//! The crate provides the main struct [`PluralRules`] which handles selection
39//! of the correct [`PluralCategory`] for a given language and [`PluralRuleType`].
40//!
41//! ## Plural Category
42//!
43//! Every number in every language belongs to a certain [`PluralCategory`].
44//! For example, the Polish language uses four:
45//!
46//! * [`One`](PluralCategory::One): `1 miesiąc`
47//! * [`Few`](PluralCategory::Few): `2 miesiące`
48//! * [`Many`](PluralCategory::Many): `5 miesięcy`
49//! * [`Other`](PluralCategory::Other): `1.5 miesiąca`
50//!
51//! ## `PluralRuleType`
52//!
53//! Plural rules depend on the use case. This crate supports two types of plural rules:
54//!
55//! * [`Cardinal`](PluralRuleType::Cardinal): `3 doors`, `1 month`, `10 dollars`
56//! * [`Ordinal`](PluralRuleType::Ordinal): `1st place`, `10th day`, `11th floor`
57//!
58//! [Language Plural Rules]: https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
59
60// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
61#![cfg_attr(not(any(test, doc)), no_std)]
62#![cfg_attr(
63    not(test),
64    deny(
65        clippy::indexing_slicing,
66        clippy::unwrap_used,
67        clippy::expect_used,
68        clippy::panic,
69        clippy::exhaustive_structs,
70        clippy::exhaustive_enums,
71        clippy::trivially_copy_pass_by_ref,
72        missing_debug_implementations,
73    )
74)]
75#![warn(missing_docs)]
76
77extern crate alloc;
78
79mod operands;
80mod options;
81pub mod provider;
82
83// Need to expose it for datagen, but we don't
84// have a reason to make it fully public, so hiding docs for now.
85#[cfg(feature = "experimental")]
86mod raw_operands;
87
88#[cfg(feature = "experimental")]
89pub use raw_operands::RawPluralOperands;
90
91use core::cmp::{Ord, PartialOrd};
92use core::convert::Infallible;
93use icu_locale_core::preferences::define_preferences;
94use icu_provider::marker::ErasedMarker;
95use icu_provider::prelude::*;
96pub use operands::PluralOperands;
97pub use options::*;
98use provider::rules::runtime::test_rule;
99use provider::PluralRulesData;
100use provider::PluralsCardinalV1;
101use provider::PluralsOrdinalV1;
102
103#[cfg(feature = "experimental")]
104use provider::PluralsRangesV1;
105#[cfg(feature = "experimental")]
106use provider::UnvalidatedPluralRange;
107
108/// The plural categories are used to format messages with numeric placeholders, expressed as decimal numbers.
109///
110/// The fundamental rule for determining plural categories is the existence of minimal pairs: whenever two different
111/// numbers may require different versions of the same message, then the numbers have different plural categories.
112///
113/// All languages supported by `ICU4X` can match any number to one of the categories.
114///
115/// # Examples
116///
117/// ```
118/// use icu::locale::locale;
119/// use icu::plurals::{PluralCategory, PluralRules};
120///
121/// let pr = PluralRules::try_new(locale!("en").into(), Default::default())
122///     .expect("locale should be present");
123///
124/// assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
125/// ```
126#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd)]
127#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
128#[cfg_attr(feature = "datagen", databake(path = icu_plurals))]
129#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
130#[repr(u8)]
131#[zerovec::make_ule(PluralCategoryULE)]
132#[allow(clippy::exhaustive_enums)] // this type is mostly stable. new categories may potentially be added in the future,
133                                   // but at a cadence slower than the ICU4X release cycle
134pub enum PluralCategory {
135    /// CLDR "zero" plural category. Used in Arabic and Latvian, among others.
136    ///
137    /// Examples of numbers having this category:
138    ///
139    /// - 0 in Arabic (ar), Latvian (lv)
140    /// - 10~20, 30, 40, 50, ... in Latvian (lv)
141    Zero = 0,
142    /// CLDR "one" plural category. Signifies the singular form in many languages.
143    ///
144    /// Examples of numbers having this category:
145    ///
146    /// - 0 in French (fr), Portuguese (pt), ...
147    /// - 1 in English (en) and most other languages
148    /// - 2.1 in Filipino (fil), Croatian (hr), Latvian (lv), Serbian (sr)
149    /// - 2, 3, 5, 7, 8, ... in Filipino (fil)
150    One = 1,
151    /// CLDR "two" plural category. Used in Arabic, Hebrew, and Slovenian, among others.
152    ///
153    /// Examples of numbers having this category:
154    ///
155    /// - 2 in Arabic (ar), Hebrew (iw), Slovenian (sl)
156    /// - 2.0 in Arabic (ar)
157    Two = 2,
158    /// CLDR "few" plural category. Used in Romanian, Polish, Russian, and others.
159    ///
160    /// Examples of numbers having this category:
161    ///
162    /// - 0 in Romanian (ro)
163    /// - 1.2 in Croatian (hr), Romanian (ro), Slovenian (sl), Serbian (sr)
164    /// - 2 in Polish (pl), Russian (ru), Czech (cs), ...
165    /// - 5 in Arabic (ar), Lithuanian (lt), Romanian (ro)
166    Few = 3,
167    /// CLDR "many" plural category. Used in Polish, Russian, Ukrainian, and others.
168    ///
169    /// Examples of numbers having this category:
170    ///
171    /// - 0 in Polish (pl)
172    /// - 1.0 in Czech (cs), Slovak (sk)
173    /// - 1.1 in Czech (cs), Lithuanian (lt), Slovak (sk)
174    /// - 15 in Arabic (ar), Polish (pl), Russian (ru), Ukrainian (uk)
175    Many = 4,
176    /// CLDR "other" plural category, used as a catch-all. Each language supports it, and it
177    /// is also used as a fail safe result for in case no better match can be identified.
178    ///
179    /// In some languages, such as Japanese, Chinese, Korean, and Thai, this is the only
180    /// plural category.
181    ///
182    /// Examples of numbers having this category:
183    ///
184    /// - 0 in English (en), German (de), Spanish (es), ...
185    /// - 1 in Japanese (ja), Korean (ko), Chinese (zh), Thai (th), ...
186    /// - 2 in English (en), German (de), Spanish (es), ...
187    Other = 5,
188}
189
190impl PluralCategory {
191    /// Returns an ordered iterator over variants of [`Plural Categories`].
192    ///
193    /// Categories are returned in alphabetical order.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use icu::plurals::PluralCategory;
199    ///
200    /// let mut categories = PluralCategory::all();
201    ///
202    /// assert_eq!(categories.next(), Some(PluralCategory::Few));
203    /// assert_eq!(categories.next(), Some(PluralCategory::Many));
204    /// assert_eq!(categories.next(), Some(PluralCategory::One));
205    /// assert_eq!(categories.next(), Some(PluralCategory::Other));
206    /// assert_eq!(categories.next(), Some(PluralCategory::Two));
207    /// assert_eq!(categories.next(), Some(PluralCategory::Zero));
208    /// assert_eq!(categories.next(), None);
209    /// ```
210    ///
211    /// [`Plural Categories`]: PluralCategory
212    pub fn all() -> impl ExactSizeIterator<Item = Self> {
213        [
214            Self::Few,
215            Self::Many,
216            Self::One,
217            Self::Other,
218            Self::Two,
219            Self::Zero,
220        ]
221        .iter()
222        .copied()
223    }
224
225    /// Returns the PluralCategory corresponding to given TR35 string.
226    pub fn get_for_cldr_string(category: &str) -> Option<PluralCategory> {
227        Self::get_for_cldr_bytes(category.as_bytes())
228    }
229    /// Returns the PluralCategory corresponding to given TR35 string as bytes
230    pub fn get_for_cldr_bytes(category: &[u8]) -> Option<PluralCategory> {
231        match category {
232            b"zero" => Some(PluralCategory::Zero),
233            b"one" => Some(PluralCategory::One),
234            b"two" => Some(PluralCategory::Two),
235            b"few" => Some(PluralCategory::Few),
236            b"many" => Some(PluralCategory::Many),
237            b"other" => Some(PluralCategory::Other),
238            _ => None,
239        }
240    }
241}
242
243define_preferences!(
244    /// The preferences for plural rules.
245    [Copy]
246    PluralRulesPreferences,
247    {}
248);
249
250/// A struct which provides an ability to retrieve an appropriate
251/// [`Plural Category`] for a given number.
252///
253/// # Examples
254///
255/// ```
256/// use icu::locale::locale;
257/// use icu::plurals::{PluralCategory, PluralRules};
258///
259/// let pr = PluralRules::try_new(locale!("en").into(), Default::default())
260///     .expect("locale should be present");
261///
262/// assert_eq!(pr.category_for(5_usize), PluralCategory::Other);
263/// ```
264///
265/// [`ICU4X`]: ../icu/index.html
266/// [`Plural Type`]: PluralRuleType
267/// [`Plural Category`]: PluralCategory
268#[derive(Debug)]
269pub struct PluralRules(DataPayload<ErasedMarker<PluralRulesData<'static>>>);
270
271impl AsRef<PluralRules> for PluralRules {
272    fn as_ref(&self) -> &PluralRules {
273        self
274    }
275}
276
277impl PluralRules {
278    icu_provider::gen_buffer_data_constructors!(
279        (prefs: PluralRulesPreferences, options: PluralRulesOptions) -> error: DataError,
280        /// Constructs a new `PluralRules` for a given locale and type using compiled data.
281        ///
282        /// # Examples
283        ///
284        /// ```
285        /// use icu::locale::locale;
286        /// use icu::plurals::PluralRules;
287        ///
288        /// let _ = PluralRules::try_new(
289        ///     locale!("en").into(),
290        ///     Default::default(),
291        /// ).expect("locale should be present");
292        /// ```
293        ///
294        /// [`data provider`]: icu_provider
295    );
296
297    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
298    pub fn try_new_unstable(
299        provider: &(impl DataProvider<PluralsCardinalV1> + DataProvider<PluralsOrdinalV1> + ?Sized),
300        prefs: PluralRulesPreferences,
301        options: PluralRulesOptions,
302    ) -> Result<Self, DataError> {
303        match options.rule_type.unwrap_or_default() {
304            PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, prefs),
305            PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, prefs),
306        }
307    }
308
309    icu_provider::gen_buffer_data_constructors!(
310        (prefs: PluralRulesPreferences) -> error: DataError,
311        /// Constructs a new `PluralRules` for a given locale for cardinal numbers using compiled data.
312        ///
313        /// Cardinal plural forms express quantities of units such as time, currency or distance,
314        /// used in conjunction with a number expressed in decimal digits (i.e. "2", not "two").
315        ///
316        /// For example, English has two forms for cardinals:
317        ///
318        /// * [`One`]: `1 day`
319        /// * [`Other`]: `0 days`, `2 days`, `10 days`, `0.3 days`
320        ///
321        /// # Examples
322        ///
323        /// ```
324        /// use icu::locale::locale;
325        /// use icu::plurals::{PluralCategory, PluralRules};
326        ///
327        /// let rules = PluralRules::try_new_cardinal(locale!("ru").into()).expect("locale should be present");
328        ///
329        /// assert_eq!(rules.category_for(2_usize), PluralCategory::Few);
330        /// ```
331        ///
332        /// [`One`]: PluralCategory::One
333        /// [`Other`]: PluralCategory::Other
334        functions: [
335            try_new_cardinal,
336            try_new_cardinal_with_buffer_provider,
337            try_new_cardinal_unstable,
338            Self,
339        ]
340    );
341
342    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
343    pub fn try_new_cardinal_unstable(
344        provider: &(impl DataProvider<PluralsCardinalV1> + ?Sized),
345        prefs: PluralRulesPreferences,
346    ) -> Result<Self, DataError> {
347        let locale = PluralsCardinalV1::make_locale(prefs.locale_preferences);
348        Ok(Self(
349            provider
350                .load(DataRequest {
351                    id: DataIdentifierBorrowed::for_locale(&locale),
352                    ..Default::default()
353                })?
354                .payload
355                .cast(),
356        ))
357    }
358
359    icu_provider::gen_buffer_data_constructors!(
360        (prefs: PluralRulesPreferences) -> error: DataError,
361        /// Constructs a new `PluralRules` for a given locale for ordinal numbers using compiled data.
362        ///
363        /// Ordinal plural forms denote the order of items in a set and are always integers.
364        ///
365        /// For example, English has four forms for ordinals:
366        ///
367        /// * [`One`]: `1st floor`, `21st floor`, `101st floor`
368        /// * [`Two`]: `2nd floor`, `22nd floor`, `102nd floor`
369        /// * [`Few`]: `3rd floor`, `23rd floor`, `103rd floor`
370        /// * [`Other`]: `4th floor`, `11th floor`, `96th floor`
371        ///
372        /// # Examples
373        ///
374        /// ```
375        /// use icu::locale::locale;
376        /// use icu::plurals::{PluralCategory, PluralRules};
377        ///
378        /// let rules = PluralRules::try_new_ordinal(
379        ///     locale!("ru").into(),
380        /// )
381        /// .expect("locale should be present");
382        ///
383        /// assert_eq!(rules.category_for(2_usize), PluralCategory::Other);
384        /// ```
385        ///
386        /// [`One`]: PluralCategory::One
387        /// [`Two`]: PluralCategory::Two
388        /// [`Few`]: PluralCategory::Few
389        /// [`Other`]: PluralCategory::Other
390        functions: [
391            try_new_ordinal,
392            try_new_ordinal_with_buffer_provider,
393            try_new_ordinal_unstable,
394            Self,
395        ]
396    );
397
398    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
399    pub fn try_new_ordinal_unstable(
400        provider: &(impl DataProvider<PluralsOrdinalV1> + ?Sized),
401        prefs: PluralRulesPreferences,
402    ) -> Result<Self, DataError> {
403        let locale = PluralsOrdinalV1::make_locale(prefs.locale_preferences);
404        Ok(Self(
405            provider
406                .load(DataRequest {
407                    id: DataIdentifierBorrowed::for_locale(&locale),
408                    ..Default::default()
409                })?
410                .payload
411                .cast(),
412        ))
413    }
414
415    /// Returns the [`Plural Category`] appropriate for the given number.
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use icu::locale::locale;
421    /// use icu::plurals::{PluralCategory, PluralRules};
422    ///
423    /// let pr = PluralRules::try_new(locale!("en").into(), Default::default())
424    ///     .expect("locale should be present");
425    ///
426    /// match pr.category_for(1_usize) {
427    ///     PluralCategory::One => "One item",
428    ///     PluralCategory::Other => "Many items",
429    ///     _ => unreachable!(),
430    /// };
431    /// ```
432    ///
433    /// The method accepts any input that can be calculated into [`Plural Operands`].
434    /// All unsigned primitive number types can infallibly be converted so they can be
435    /// used as an input.
436    ///
437    /// For signed numbers and strings, [`Plural Operands`] implement [`TryFrom`]
438    /// and [`FromStr`](std::str::FromStr), which should be used before passing the result to
439    /// [`category_for()`](PluralRules::category_for()).
440    ///
441    /// # Examples
442    ///
443    /// ```
444    /// use icu::locale::locale;
445    /// use icu::plurals::{PluralRules, PluralCategory, PluralOperands};
446    /// #
447    /// # let pr = PluralRules::try_new(locale!("en").into(), Default::default())
448    /// #     .expect("locale should be present");
449    ///
450    /// let operands = PluralOperands::try_from(-5).expect("Failed to parse to operands.");
451    /// let operands2: PluralOperands = "5.10".parse().expect("Failed to parse to operands.");
452    ///
453    /// assert_eq!(pr.category_for(operands), PluralCategory::Other);
454    /// assert_eq!(pr.category_for(operands2), PluralCategory::Other);
455    /// ```
456    ///
457    /// [`Plural Category`]: PluralCategory
458    /// [`Plural Operands`]: operands::PluralOperands
459    pub fn category_for<I: Into<PluralOperands>>(&self, input: I) -> PluralCategory {
460        let rules = self.0.get();
461        let input = input.into();
462
463        macro_rules! test_rule {
464            ($rule:ident, $cat:ident) => {
465                rules
466                    .$rule
467                    .as_ref()
468                    .and_then(|r| test_rule(r, &input).then(|| PluralCategory::$cat))
469            };
470        }
471
472        test_rule!(zero, Zero)
473            .or_else(|| test_rule!(one, One))
474            .or_else(|| test_rule!(two, Two))
475            .or_else(|| test_rule!(few, Few))
476            .or_else(|| test_rule!(many, Many))
477            .unwrap_or(PluralCategory::Other)
478    }
479
480    /// Returns all [`Plural Categories`] appropriate for a [`PluralRules`] object
481    /// based on the [`LanguageIdentifier`](icu::locale::{LanguageIdentifier}) and [`PluralRuleType`].
482    ///
483    /// The [`Plural Categories`] are returned in UTS 35 sorted order.
484    ///
485    /// The category [`PluralCategory::Other`] is always included.
486    ///
487    /// # Examples
488    ///
489    /// ```
490    /// use icu::locale::locale;
491    /// use icu::plurals::{PluralCategory, PluralRules};
492    ///
493    /// let pr = PluralRules::try_new(locale!("fr").into(), Default::default())
494    ///     .expect("locale should be present");
495    ///
496    /// let mut categories = pr.categories();
497    /// assert_eq!(categories.next(), Some(PluralCategory::One));
498    /// assert_eq!(categories.next(), Some(PluralCategory::Many));
499    /// assert_eq!(categories.next(), Some(PluralCategory::Other));
500    /// assert_eq!(categories.next(), None);
501    /// ```
502    ///
503    /// [`Plural Categories`]: PluralCategory
504    pub fn categories(&self) -> impl Iterator<Item = PluralCategory> + '_ {
505        let rules = self.0.get();
506
507        macro_rules! test_rule {
508            ($rule:ident, $cat:ident) => {
509                rules
510                    .$rule
511                    .as_ref()
512                    .map(|_| PluralCategory::$cat)
513                    .into_iter()
514            };
515        }
516
517        test_rule!(zero, Zero)
518            .chain(test_rule!(one, One))
519            .chain(test_rule!(two, Two))
520            .chain(test_rule!(few, Few))
521            .chain(test_rule!(many, Many))
522            .chain(Some(PluralCategory::Other))
523    }
524}
525
526/// A [`PluralRules`] that also has the ability to retrieve an appropriate [`Plural Category`] for a
527/// range.
528///
529/// ✨ *Enabled with the `experimental` Cargo feature.*
530///
531/// <div class="stab unstable">
532/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
533/// including in SemVer minor releases. Use with caution.
534/// <a href="https://github.com/unicode-org/icu4x/issues/4140">#4140</a>
535/// </div>
536///
537/// # Examples
538///
539/// ```
540/// use icu::locale::locale;
541/// use icu::plurals::{PluralCategory, PluralOperands, PluralRulesWithRanges};
542///
543/// let ranges = PluralRulesWithRanges::try_new(
544///     locale!("ar").into(),
545///     Default::default(),
546/// )
547/// .expect("locale should be present");
548///
549/// let operands = PluralOperands::from(1_usize);
550/// let operands2: PluralOperands =
551///     "2.0".parse().expect("parsing to operands should succeed");
552///
553/// assert_eq!(
554///     ranges.category_for_range(operands, operands2),
555///     PluralCategory::Other
556/// );
557/// ```
558///
559/// [`Plural Category`]: PluralCategory
560#[cfg(feature = "experimental")]
561#[derive(Debug)]
562pub struct PluralRulesWithRanges<R> {
563    rules: R,
564    ranges: DataPayload<PluralsRangesV1>,
565}
566
567#[cfg(feature = "experimental")]
568impl PluralRulesWithRanges<PluralRules> {
569    icu_provider::gen_buffer_data_constructors!(
570
571        (prefs: PluralRulesPreferences, options: PluralRulesOptions) -> error: DataError,
572        /// Constructs a new `PluralRulesWithRanges` for a given locale using compiled data.
573        ///
574        /// # Examples
575        ///
576        /// ```
577        /// use icu::locale::locale;
578        /// use icu::plurals::PluralRulesWithRanges;
579        ///
580        /// let _ = PluralRulesWithRanges::try_new(
581        ///     locale!("en").into(),
582        ///     Default::default(),
583        /// ).expect("locale should be present");
584        /// ```
585    );
586
587    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
588    pub fn try_new_unstable(
589        provider: &(impl DataProvider<PluralsRangesV1>
590              + DataProvider<PluralsCardinalV1>
591              + DataProvider<PluralsOrdinalV1>
592              + ?Sized),
593        prefs: PluralRulesPreferences,
594        options: PluralRulesOptions,
595    ) -> Result<Self, DataError> {
596        match options.rule_type.unwrap_or_default() {
597            PluralRuleType::Cardinal => Self::try_new_cardinal_unstable(provider, prefs),
598            PluralRuleType::Ordinal => Self::try_new_ordinal_unstable(provider, prefs),
599        }
600    }
601
602    icu_provider::gen_buffer_data_constructors!(
603        (prefs: PluralRulesPreferences) -> error: DataError,
604        /// Constructs a new `PluralRulesWithRanges` for a given locale for cardinal numbers using
605        /// compiled data.
606        ///
607        /// See [`PluralRules::try_new_cardinal`] for more information.
608        ///
609        /// # Examples
610        ///
611        /// ```
612        /// use icu::locale::locale;
613        /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
614        ///
615        /// let rules = PluralRulesWithRanges::try_new_cardinal(locale!("ru").into())
616        ///     .expect("locale should be present");
617        ///
618        /// assert_eq!(rules.category_for_range(0_usize, 2_usize), PluralCategory::Few);
619        /// ```
620        functions: [
621            try_new_cardinal,
622            try_new_cardinal_with_buffer_provider,
623            try_new_cardinal_unstable,
624            Self,
625        ]
626    );
627
628    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_cardinal)]
629    pub fn try_new_cardinal_unstable(
630        provider: &(impl DataProvider<PluralsCardinalV1> + DataProvider<PluralsRangesV1> + ?Sized),
631        prefs: PluralRulesPreferences,
632    ) -> Result<Self, DataError> {
633        let rules = PluralRules::try_new_cardinal_unstable(provider, prefs)?;
634
635        PluralRulesWithRanges::try_new_with_rules_unstable(provider, prefs, rules)
636    }
637
638    icu_provider::gen_buffer_data_constructors!(
639        (prefs: PluralRulesPreferences) -> error: DataError,
640        /// Constructs a new `PluralRulesWithRanges` for a given locale for ordinal numbers using
641        /// compiled data.
642        ///
643        /// See [`PluralRules::try_new_ordinal`] for more information.
644        ///
645        /// # Examples
646        ///
647        /// ```
648        /// use icu::locale::locale;
649        /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
650        ///
651        /// let rules = PluralRulesWithRanges::try_new_ordinal(
652        ///     locale!("ru").into(),
653        /// )
654        /// .expect("locale should be present");
655        ///
656        /// assert_eq!(rules.category_for_range(0_usize, 2_usize), PluralCategory::Other);
657        /// ```
658        functions: [
659            try_new_ordinal,
660            try_new_ordinal_with_buffer_provider,
661            try_new_ordinal_unstable,
662            Self,
663        ]
664    );
665
666    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_ordinal)]
667    pub fn try_new_ordinal_unstable(
668        provider: &(impl DataProvider<PluralsOrdinalV1> + DataProvider<PluralsRangesV1> + ?Sized),
669        prefs: PluralRulesPreferences,
670    ) -> Result<Self, DataError> {
671        let rules = PluralRules::try_new_ordinal_unstable(provider, prefs)?;
672
673        PluralRulesWithRanges::try_new_with_rules_unstable(provider, prefs, rules)
674    }
675}
676
677#[cfg(feature = "experimental")]
678impl<R> PluralRulesWithRanges<R>
679where
680    R: AsRef<PluralRules>,
681{
682    icu_provider::gen_buffer_data_constructors!(
683        (prefs: PluralRulesPreferences, rules: R) -> error: DataError,
684        /// Constructs a new `PluralRulesWithRanges` for a given locale from an existing
685        /// `PluralRules` (either owned or as a reference) and compiled data.
686        ///
687        /// # ⚠️ Warning
688        ///
689        /// The provided `locale` **MUST** be the same as the locale provided to the constructor
690        /// of `rules`. Otherwise, [`Self::category_for_range`] will return incorrect results.
691        ///
692        /// # Examples
693        ///
694        /// ```
695        /// use icu::locale::locale;
696        /// use icu::plurals::{PluralRulesWithRanges, PluralRules};
697        ///
698        /// let rules = PluralRules::try_new(locale!("en").into(), Default::default())
699        ///     .expect("locale should be present");
700        ///
701        /// let _ =
702        ///     PluralRulesWithRanges::try_new_with_rules(locale!("en").into(), rules)
703        ///         .expect("locale should be present");
704        /// ```
705        functions: [
706            try_new_with_rules,
707            try_new_with_rules_with_buffer_provider,
708            try_new_with_rules_unstable,
709            Self,
710        ]
711    );
712
713    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_rules)]
714    pub fn try_new_with_rules_unstable(
715        provider: &(impl DataProvider<PluralsRangesV1> + ?Sized),
716        prefs: PluralRulesPreferences,
717        rules: R,
718    ) -> Result<Self, DataError> {
719        let locale = PluralsRangesV1::make_locale(prefs.locale_preferences);
720        let ranges = provider
721            .load(DataRequest {
722                id: DataIdentifierBorrowed::for_locale(&locale),
723                ..Default::default()
724            })?
725            .payload;
726
727        Ok(Self { rules, ranges })
728    }
729
730    /// Gets a reference to the inner `PluralRules`.
731    ///
732    /// # Examples
733    ///
734    /// ```
735    /// use icu::locale::locale;
736    /// use icu::plurals::{PluralCategory, PluralRulesWithRanges};
737    ///
738    /// let ranges = PluralRulesWithRanges::try_new_cardinal(locale!("en").into())
739    ///     .expect("locale should be present");
740    ///
741    /// let rules = ranges.rules();
742    ///
743    /// assert_eq!(rules.category_for(1u8), PluralCategory::One);
744    /// ```
745    pub fn rules(&self) -> &PluralRules {
746        self.rules.as_ref()
747    }
748
749    /// Returns the [`Plural Category`] appropriate for a range.
750    ///
751    /// Note that the returned category is correct only if the range fulfills the following requirements:
752    /// - The start value is strictly less than the end value.
753    /// - Both values are positive.
754    ///
755    /// # Examples
756    ///
757    /// ```
758    /// use icu::locale::locale;
759    /// use icu::plurals::{PluralCategory, PluralOperands, PluralRulesWithRanges};
760    ///
761    /// let ranges = PluralRulesWithRanges::try_new(
762    ///     locale!("ro").into(),
763    ///     Default::default(),
764    /// )
765    /// .expect("locale should be present");
766    /// let operands: PluralOperands =
767    ///     "0.5".parse().expect("parsing to operands should succeed");
768    /// let operands2 = PluralOperands::from(1_usize);
769    ///
770    /// assert_eq!(
771    ///     ranges.category_for_range(operands, operands2),
772    ///     PluralCategory::Few
773    /// );
774    /// ```
775    ///
776    /// [`Plural Category`]: PluralCategory
777    pub fn category_for_range<S: Into<PluralOperands>, E: Into<PluralOperands>>(
778        &self,
779        start: S,
780        end: E,
781    ) -> PluralCategory {
782        let rules = self.rules.as_ref();
783        let start = rules.category_for(start);
784        let end = rules.category_for(end);
785
786        self.resolve_range(start, end)
787    }
788
789    /// Returns the [`Plural Category`] appropriate for a range from the categories of its endpoints.
790    ///
791    /// Note that the returned category is correct only if the original numeric range fulfills the
792    /// following requirements:
793    /// - The start value is strictly less than the end value.
794    /// - Both values are positive.
795    ///
796    /// # Examples
797    ///
798    /// ```
799    /// use icu::locale::locale;
800    /// use icu::plurals::{
801    ///     PluralCategory, PluralRuleType, PluralRulesOptions,
802    ///     PluralRulesWithRanges,
803    /// };
804    ///
805    /// let ranges = PluralRulesWithRanges::try_new(
806    ///     locale!("sl").into(),
807    ///     PluralRulesOptions::default().with_type(PluralRuleType::Ordinal),
808    /// )
809    /// .expect("locale should be present");
810    ///
811    /// assert_eq!(
812    ///     ranges.resolve_range(PluralCategory::Other, PluralCategory::One),
813    ///     PluralCategory::Few
814    /// );
815    /// ```
816    ///
817    /// [`Plural Category`]: PluralCategory
818    pub fn resolve_range(&self, start: PluralCategory, end: PluralCategory) -> PluralCategory {
819        self.ranges
820            .get()
821            .ranges
822            .get_copied(&UnvalidatedPluralRange::from_range(
823                start.into(),
824                end.into(),
825            ))
826            .map(PluralCategory::from)
827            .unwrap_or(end)
828    }
829}
830
831#[derive(Debug, Clone, PartialEq, Eq)]
832/// A bag of values for different plural cases.
833pub struct PluralElements<T>(PluralElementsInner<T>);
834
835#[derive(Debug, Clone, PartialEq, Eq)]
836#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
837#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
838pub(crate) struct PluralElementsInner<T> {
839    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
840    zero: Option<T>,
841    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
842    one: Option<T>,
843    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
844    two: Option<T>,
845    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
846    few: Option<T>,
847    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
848    many: Option<T>,
849    other: T,
850    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
851    explicit_zero: Option<T>,
852    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
853    explicit_one: Option<T>,
854}
855
856impl<T> PluralElements<T> {
857    /// Creates a new [`PluralElements`] with the given default value.
858    pub fn new(other: T) -> Self {
859        Self(PluralElementsInner {
860            other,
861            zero: None,
862            one: None,
863            two: None,
864            few: None,
865            many: None,
866            explicit_zero: None,
867            explicit_one: None,
868        })
869    }
870
871    /// The value for [`PluralCategory::Zero`]
872    pub fn zero(&self) -> &T {
873        self.0.zero.as_ref().unwrap_or(&self.0.other)
874    }
875
876    /// The value for [`PluralCategory::One`]
877    pub fn one(&self) -> &T {
878        self.0.one.as_ref().unwrap_or(&self.0.other)
879    }
880
881    /// The value for [`PluralCategory::Two`]
882    pub fn two(&self) -> &T {
883        self.0.two.as_ref().unwrap_or(&self.0.other)
884    }
885
886    /// The value for [`PluralCategory::Few`]
887    pub fn few(&self) -> &T {
888        self.0.few.as_ref().unwrap_or(&self.0.other)
889    }
890
891    /// The value for [`PluralCategory::Many`]
892    pub fn many(&self) -> &T {
893        self.0.many.as_ref().unwrap_or(&self.0.other)
894    }
895
896    /// The value for [`PluralCategory::Other`]
897    pub fn other(&self) -> &T {
898        &self.0.other
899    }
900
901    /// If the only variant is `other`, returns `Some(other)`.
902    ///
903    /// # Examples
904    ///
905    /// ```
906    /// use icu_plurals::PluralElements;
907    ///
908    /// let mut only_other = PluralElements::new("abc").with_one_value(Some("abc"));
909    /// assert_eq!(only_other.try_into_other(), Some("abc"));
910    ///
911    /// let mut multi = PluralElements::new("abc").with_one_value(Some("def"));
912    /// assert_eq!(multi.try_into_other(), None);
913    /// ```
914    pub fn try_into_other(self) -> Option<T> {
915        match self.0 {
916            PluralElementsInner {
917                zero: None,
918                one: None,
919                two: None,
920                few: None,
921                many: None,
922                other,
923                explicit_zero: None,
924                explicit_one: None,
925            } => Some(other),
926            _ => None,
927        }
928    }
929
930    /// The value used when the [`PluralOperands`] are exactly 0.
931    pub fn explicit_zero(&self) -> Option<&T> {
932        self.0.explicit_zero.as_ref()
933    }
934
935    /// The value used when the [`PluralOperands`] are exactly 1.
936    pub fn explicit_one(&self) -> Option<&T> {
937        self.0.explicit_one.as_ref()
938    }
939
940    /// Applies a function `f` to convert all values to another type.
941    ///
942    /// # Examples
943    ///
944    /// ```
945    /// use icu_plurals::PluralElements;
946    ///
947    /// let x = PluralElements::new(11).with_one_value(Some(15));
948    /// let y = x.map(|i| i * 2);
949    ///
950    /// assert_eq!(*y.other(), 22);
951    /// assert_eq!(*y.one(), 30);
952    /// ```
953    pub fn map<B, F: FnMut(T) -> B>(self, mut f: F) -> PluralElements<B> {
954        let Ok(x) = self.try_map(move |x| Ok::<B, Infallible>(f(x)));
955        x
956    }
957
958    /// Applies a function `f` to convert all values to another type,
959    /// propagating a possible error.
960    pub fn try_map<B, E, F: FnMut(T) -> Result<B, E>>(
961        self,
962        mut f: F,
963    ) -> Result<PluralElements<B>, E> {
964        let plural_elements = PluralElements(PluralElementsInner {
965            other: f(self.0.other)?,
966            zero: self.0.zero.map(&mut f).transpose()?,
967            one: self.0.one.map(&mut f).transpose()?,
968            two: self.0.two.map(&mut f).transpose()?,
969            few: self.0.few.map(&mut f).transpose()?,
970            many: self.0.many.map(&mut f).transpose()?,
971            explicit_zero: self.0.explicit_zero.map(&mut f).transpose()?,
972            explicit_one: self.0.explicit_one.map(&mut f).transpose()?,
973        });
974        Ok(plural_elements)
975    }
976
977    /// Immutably applies a function `f` to each value.
978    pub fn for_each<F: FnMut(&T)>(&self, mut f: F) {
979        #[allow(clippy::unit_arg)] // consistency with map and one-liner
980        let Ok(()) = self.try_for_each(move |x| Ok::<(), Infallible>(f(x)));
981    }
982
983    /// Immutably applies a function `f` to each value,
984    /// propagating a possible error.
985    pub fn try_for_each<E, F: FnMut(&T) -> Result<(), E>>(&self, mut f: F) -> Result<(), E> {
986        // Use a structure to create compile errors if another field is added
987        let _ = PluralElements(PluralElementsInner {
988            other: f(&self.0.other)?,
989            zero: self.0.zero.as_ref().map(&mut f).transpose()?,
990            one: self.0.one.as_ref().map(&mut f).transpose()?,
991            two: self.0.two.as_ref().map(&mut f).transpose()?,
992            few: self.0.few.as_ref().map(&mut f).transpose()?,
993            many: self.0.many.as_ref().map(&mut f).transpose()?,
994            explicit_zero: self.0.explicit_zero.as_ref().map(&mut f).transpose()?,
995            explicit_one: self.0.explicit_one.as_ref().map(&mut f).transpose()?,
996        });
997        Ok(())
998    }
999
1000    /// Mutably applies a function `f` to each value.
1001    ///
1002    /// # Examples
1003    ///
1004    /// ```
1005    /// use icu_plurals::PluralElements;
1006    ///
1007    /// let mut x = PluralElements::new(11).with_one_value(Some(15));
1008    /// x.for_each_mut(|i| *i *= 2);
1009    ///
1010    /// assert_eq!(*x.other(), 22);
1011    /// assert_eq!(*x.one(), 30);
1012    /// ```
1013    pub fn for_each_mut<F: FnMut(&mut T)>(&mut self, mut f: F) {
1014        #[allow(clippy::unit_arg)] // consistency with map and one-liner
1015        let Ok(()) = self.try_for_each_mut(move |x| Ok::<(), Infallible>(f(x)));
1016    }
1017
1018    /// Mutably applies a function `f` to each value,
1019    /// propagating a possible error.
1020    pub fn try_for_each_mut<E, F: FnMut(&mut T) -> Result<(), E>>(
1021        &mut self,
1022        mut f: F,
1023    ) -> Result<(), E> {
1024        // Use a structure to create compile errors if another field is added
1025        let _ = PluralElements(PluralElementsInner {
1026            other: f(&mut self.0.other)?,
1027            zero: self.0.zero.as_mut().map(&mut f).transpose()?,
1028            one: self.0.one.as_mut().map(&mut f).transpose()?,
1029            two: self.0.two.as_mut().map(&mut f).transpose()?,
1030            few: self.0.few.as_mut().map(&mut f).transpose()?,
1031            many: self.0.many.as_mut().map(&mut f).transpose()?,
1032            explicit_zero: self.0.explicit_zero.as_mut().map(&mut f).transpose()?,
1033            explicit_one: self.0.explicit_one.as_mut().map(&mut f).transpose()?,
1034        });
1035        Ok(())
1036    }
1037
1038    /// Converts from `&PluralElements<T>` to `PluralElements<&T>`.
1039    pub fn as_ref(&self) -> PluralElements<&T> {
1040        PluralElements(PluralElementsInner {
1041            other: &self.0.other,
1042            zero: self.0.zero.as_ref(),
1043            one: self.0.one.as_ref(),
1044            two: self.0.two.as_ref(),
1045            few: self.0.few.as_ref(),
1046            many: self.0.many.as_ref(),
1047            explicit_zero: self.0.explicit_zero.as_ref(),
1048            explicit_one: self.0.explicit_one.as_ref(),
1049        })
1050    }
1051}
1052
1053impl<T: PartialEq> PluralElements<T> {
1054    /// Sets the value for [`PluralCategory::Zero`].
1055    pub fn with_zero_value(self, zero: Option<T>) -> Self {
1056        Self(PluralElementsInner {
1057            zero: zero.filter(|t| *t != self.0.other),
1058            ..self.0
1059        })
1060    }
1061
1062    /// Sets the value for [`PluralCategory::One`].
1063    pub fn with_one_value(self, one: Option<T>) -> Self {
1064        Self(PluralElementsInner {
1065            one: one.filter(|t| *t != self.0.other),
1066            ..self.0
1067        })
1068    }
1069
1070    /// Sets the value for [`PluralCategory::Two`].
1071    pub fn with_two_value(self, two: Option<T>) -> Self {
1072        Self(PluralElementsInner {
1073            two: two.filter(|t| *t != self.0.other),
1074            ..self.0
1075        })
1076    }
1077
1078    /// Sets the value for [`PluralCategory::Few`].
1079    pub fn with_few_value(self, few: Option<T>) -> Self {
1080        Self(PluralElementsInner {
1081            few: few.filter(|t| *t != self.0.other),
1082            ..self.0
1083        })
1084    }
1085
1086    /// Sets the value for [`PluralCategory::Many`].
1087    pub fn with_many_value(self, many: Option<T>) -> Self {
1088        Self(PluralElementsInner {
1089            many: many.filter(|t| *t != self.0.other),
1090            ..self.0
1091        })
1092    }
1093
1094    /// Sets the value for explicit 0.
1095    pub fn with_explicit_zero_value(self, explicit_zero: Option<T>) -> Self {
1096        Self(PluralElementsInner {
1097            explicit_zero,
1098            ..self.0
1099        })
1100    }
1101
1102    /// Sets the value for explicit 1.
1103    pub fn with_explicit_one_value(self, explicit_one: Option<T>) -> Self {
1104        Self(PluralElementsInner {
1105            explicit_one,
1106            ..self.0
1107        })
1108    }
1109}