///|
fn array_contains_string(values : Array[String], expected : String) -> Bool {
for value in values {
if value == expected {
return true
}
}
false
}
///|
/// Explain how a numeric choice selects a branch.
pub fn select_plural_branch(
locale : String,
value : Int,
ordinal : Bool,
selectors : Array[String],
) -> PluralSelection raise MessageError {
let category = if ordinal {
ordinal_category(locale, value)
} else {
plural_category(locale, value)
}
let exact_selector = "=" + value.to_string()
let used_exact = array_contains_string(selectors, exact_selector)
let selected_selector = if used_exact {
exact_selector
} else if array_contains_string(selectors, category) {
category
} else if array_contains_string(selectors, "other") {
"other"
} else {
raise MissingChoice(category)
}
{
locale: canonicalize_locale_lossy(locale),
value,
ordinal,
category,
exact_selector,
selected_selector,
used_exact,
used_other: selected_selector == "other" && category != "other",
}
}
///|
pub fn PluralSelection::locale(self : PluralSelection) -> String {
self.locale
}
///|
pub fn PluralSelection::value(self : PluralSelection) -> Int {
self.value
}
///|
pub fn PluralSelection::ordinal(self : PluralSelection) -> Bool {
self.ordinal
}
///|
pub fn PluralSelection::category(self : PluralSelection) -> String {
self.category
}
///|
pub fn PluralSelection::exact_selector(self : PluralSelection) -> String {
self.exact_selector
}
///|
pub fn PluralSelection::selected_selector(self : PluralSelection) -> String {
self.selected_selector
}
///|
pub fn PluralSelection::used_exact(self : PluralSelection) -> Bool {
self.used_exact
}
///|
pub fn PluralSelection::used_other(self : PluralSelection) -> Bool {
self.used_other
}
///|
pub fn PluralSelection::summary(self : PluralSelection) -> String {
let mode = if self.ordinal { "ordinal" } else { "cardinal" }
"\{self.locale} \{mode} \{self.value}: category=\{self.category}, selected=\{self.selected_selector}"
}
///|
fn categories_for_mode(locale : String, ordinal : Bool) -> Array[String] {
if ordinal {
required_ordinal_categories(locale)
} else {
required_cardinal_categories(locale)
}
}
///|
fn category_for_mode(locale : String, value : Int, ordinal : Bool) -> String {
if ordinal {
ordinal_category(locale, value)
} else {
plural_category(locale, value)
}
}
///|
fn normalized_range(start : Int, end : Int) -> (Int, Int) {
if start <= end {
(start, end)
} else {
(end, start)
}
}
///|
/// Collect representative integer values for each plural category.
pub fn plural_examples(
locale : String,
start : Int,
end : Int,
ordinal? : Bool = false,
limit_per_category? : Int = 5,
) -> Array[PluralExamples] {
let (lower, upper) = normalized_range(start, end)
let categories = categories_for_mode(locale, ordinal)
let values : Map[String, Array[Int]] = Map([])
for category in categories {
values[category] = []
}
for value in lower..<=upper {
let category = category_for_mode(locale, value, ordinal)
match values.get(category) {
Some(examples) =>
if examples.length() < limit_per_category {
examples.push(value)
}
None => values[category] = [value]
}
}
let output : Array[PluralExamples] = []
for category in categories {
output.push({ category, values: values[category] })
}
output
}
///|
pub fn PluralExamples::category(self : PluralExamples) -> String {
self.category
}
///|
pub fn PluralExamples::values(self : PluralExamples) -> Array[Int] {
self.values
}
///|
pub fn PluralExamples::is_empty(self : PluralExamples) -> Bool {
self.values.length() == 0
}
///|
pub fn PluralExamples::summary(self : PluralExamples) -> String {
let values : Array[String] = []
for value in self.values {
values.push(value.to_string())
}
"\{self.category}: \{values.join(", ")}"
}
///|
/// Return every category observed over an inclusive integer range.
pub fn observed_plural_categories(
locale : String,
start : Int,
end : Int,
ordinal? : Bool = false,
) -> Array[String] {
let (lower, upper) = normalized_range(start, end)
let seen : Map[String, Unit] = Map([])
for value in lower..<=upper {
seen[category_for_mode(locale, value, ordinal)] = ()
}
let output : Array[String] = []
for category, _ in seen {
output.push(category)
}
output.sort()
output
}
///|
/// Check whether selectors cover all categories declared for a locale.
pub fn missing_plural_categories(
locale : String,
selectors : Array[String],
ordinal? : Bool = false,
) -> Array[String] {
let missing : Array[String] = []
for category in categories_for_mode(locale, ordinal) {
if !array_contains_string(selectors, category) {
missing.push(category)
}
}
missing
}