///|
/// Controls when a localized number includes a sign.
pub(all) enum SignDisplay {
Auto
Always
Never
ExceptZero
} derive(Eq, Debug)
///|
/// Symbols and grouping rules used by a numbering system.
pub struct NumberSymbols {
decimal : String
group : String
minus : String
plus : String
primary_group : Int
secondary_group : Int
} derive(Eq, Debug)
///|
pub fn NumberSymbols::decimal(self : NumberSymbols) -> String {
self.decimal
}
///|
pub fn NumberSymbols::group(self : NumberSymbols) -> String {
self.group
}
///|
pub fn NumberSymbols::minus(self : NumberSymbols) -> String {
self.minus
}
///|
pub fn NumberSymbols::plus(self : NumberSymbols) -> String {
self.plus
}
///|
pub fn NumberSymbols::primary_group(self : NumberSymbols) -> Int {
self.primary_group
}
///|
pub fn NumberSymbols::secondary_group(self : NumberSymbols) -> Int {
self.secondary_group
}
///|
/// Returns practical decimal symbols for a locale.
///
/// MoonLocale intentionally keeps Latin digits as its portable default while
/// selecting punctuation and grouping conventions from the locale.
pub fn number_symbols(locale : Locale) -> NumberSymbols {
match locale.language() {
"fr" =>
{
decimal: ",",
group: "\u{202f}",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 3,
}
"de" | "es" | "it" | "pt" | "nl" | "da" =>
{
decimal: ",",
group: ".",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 3,
}
"ru" | "uk" | "pl" | "sv" | "no" | "fi" | "cs" | "sk" =>
{
decimal: ",",
group: "\u{a0}",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 3,
}
"hi" | "bn" | "mr" | "gu" | "ta" | "te" =>
{
decimal: ".",
group: ",",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 2,
}
"ar" =>
{
decimal: "\u{66b}",
group: "\u{66c}",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 3,
}
_ =>
{
decimal: ".",
group: ",",
minus: "-",
plus: "+",
primary_group: 3,
secondary_group: 3,
}
}
}
///|
/// Immutable configuration for localized number output.
pub struct NumberFormatter {
locale : Locale
grouping : Bool
minimum_integer_digits : Int
sign_display : SignDisplay
} derive(Eq, Debug)
///|
pub fn NumberFormatter::new(locale : Locale) -> NumberFormatter {
{ locale, grouping: true, minimum_integer_digits: 1, sign_display: Auto }
}
///|
pub fn NumberFormatter::locale(self : NumberFormatter) -> Locale {
self.locale
}
///|
pub fn NumberFormatter::with_grouping(
self : NumberFormatter,
enabled : Bool,
) -> NumberFormatter {
{ ..self, grouping: enabled }
}
///|
/// Sets the minimum number of integer digits, clamped to the range 1–21.
pub fn NumberFormatter::with_minimum_integer_digits(
self : NumberFormatter,
digits : Int,
) -> NumberFormatter {
let clamped = if digits < 1 { 1 } else if digits > 21 { 21 } else { digits }
{ ..self, minimum_integer_digits: clamped }
}
///|
pub fn NumberFormatter::with_sign_display(
self : NumberFormatter,
display : SignDisplay,
) -> NumberFormatter {
{ ..self, sign_display: display }
}
///|
fn repeat_string(value : String, count : Int) -> String {
let output = StringBuilder::new()
for index = 0; index < count; index = index + 1 {
output.write_string(value)
}
output.to_string()
}
///|
fn integer_parts(value : Int) -> (Bool, String) {
let rendered = value.to_string()
if rendered[0] == 45 {
(true, "\{rendered[1:]}")
} else {
(false, rendered)
}
}
///|
fn group_digits(
digits : String,
separator : String,
primary : Int,
secondary : Int,
) -> String {
if digits.length() <= primary {
return digits
}
let output = StringBuilder::new()
let length = digits.length()
for index = 0; index < length; index = index + 1 {
if index > 0 {
let remaining = length - index
if remaining == primary ||
(remaining > primary && (remaining - primary) % secondary == 0) {
output.write_string(separator)
}
}
output.write_string("\{digits[index:index + 1]}")
}
output.to_string()
}
///|
fn number_sign(
negative : Bool,
zero : Bool,
display : SignDisplay,
symbols : NumberSymbols,
) -> String {
match display {
Never => ""
Always => if negative { symbols.minus } else { symbols.plus }
ExceptZero =>
if zero {
""
} else if negative {
symbols.minus
} else {
symbols.plus
}
Auto => if negative { symbols.minus } else { "" }
}
}
///|
/// Formats an integer using locale punctuation and grouping.
pub fn NumberFormatter::format_int(
self : NumberFormatter,
value : Int,
) -> String {
let (negative, raw_digits) = integer_parts(value)
let padding = self.minimum_integer_digits - raw_digits.length()
let digits = if padding > 0 {
repeat_string("0", padding) + raw_digits
} else {
raw_digits
}
let symbols = number_symbols(self.locale)
let body = if self.grouping {
group_digits(
digits,
symbols.group,
symbols.primary_group,
symbols.secondary_group,
)
} else {
digits
}
number_sign(negative, value == 0, self.sign_display, symbols) + body
}
///|
/// Convenience entry point with default options.
pub fn format_int(locale : Locale, value : Int) -> String {
NumberFormatter::new(locale).format_int(value)
}