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}