///|
pub(all) enum ListType {
And
Or
Unit
} derive(Eq, Debug)
///|
pub(all) enum ListWidth {
Long
Short
Narrow
} derive(Eq, Debug)
///|
pub struct ListFormatter {
locale : Locale
list_type : ListType
width : ListWidth
} derive(Eq, Debug)
///|
pub fn ListFormatter::new(locale : Locale) -> ListFormatter {
{ locale, list_type: And, width: Long }
}
///|
pub fn ListFormatter::with_type(
self : ListFormatter,
list_type : ListType,
) -> ListFormatter {
{ ..self, list_type, }
}
///|
pub fn ListFormatter::with_width(
self : ListFormatter,
width : ListWidth,
) -> ListFormatter {
{ ..self, width, }
}
///|
fn list_word(
locale : Locale,
list_type : ListType,
width : ListWidth,
) -> String {
match (locale.language(), list_type, width) {
("zh", And, _) => "\u{548c}"
("zh", Or, _) => "\u{6216}"
("ja", And, _) => "\u{3068}"
("ja", Or, _) => "\u{307e}\u{305f}\u{306f}"
("fr", And, _) => "et"
("fr", Or, _) => "ou"
("de", And, _) => "und"
("de", Or, _) => "oder"
("es", And, _) => "y"
("es", Or, _) => "o"
("it", And, _) => "e"
("it", Or, _) => "o"
(_, And, Short) => "&"
(_, And, _) => "and"
(_, Or, _) => "or"
(_, Unit, _) => ""
}
}
///|
fn list_separator(locale : Locale, width : ListWidth) -> String {
if locale.language() == "zh" || locale.language() == "ja" {
"\u{3001}"
} else {
match width {
Narrow => " "
_ => ", "
}
}
}
///|
fn list_pair_separator(
locale : Locale,
list_type : ListType,
width : ListWidth,
) -> String {
if list_type == Unit {
match width {
Narrow => " "
_ => ", "
}
} else {
let word = list_word(locale, list_type, width)
if locale.language() == "zh" || locale.language() == "ja" {
word
} else {
" \{word} "
}
}
}
///|
fn list_final_separator(
locale : Locale,
list_type : ListType,
width : ListWidth,
) -> String {
if list_type == Unit {
return list_pair_separator(locale, list_type, width)
}
let word = list_word(locale, list_type, width)
if locale.language() == "zh" || locale.language() == "ja" {
word
} else if locale.language() == "en" && width != Narrow {
", \{word} "
} else {
" \{word} "
}
}
///|
/// Joins already-rendered values using locale punctuation and conjunctions.
pub fn ListFormatter::format(
self : ListFormatter,
values : Array[String],
) -> String {
match values.length() {
0 => ""
1 => values[0]
2 =>
values[0] +
list_pair_separator(self.locale, self.list_type, self.width) +
values[1]
length => {
let output = StringBuilder::new()
for index = 0; index < length; index = index + 1 {
if index > 0 {
if index == length - 1 {
output.write_string(
list_final_separator(self.locale, self.list_type, self.width),
)
} else {
output.write_string(list_separator(self.locale, self.width))
}
}
output.write_string(values[index])
}
output.to_string()
}
}
}
///|
pub fn format_list(locale : Locale, values : Array[String]) -> String {
ListFormatter::new(locale).format(values)
}
///|
pub(all) enum RelativeUnit {
Second
Minute
Hour
Day
Week
Month
Quarter
Year
} derive(Eq, Debug)
///|
pub(all) enum RelativeNumeric {
Always
Auto
} derive(Eq, Debug)
///|
pub struct RelativeTimeFormatter {
locale : Locale
numeric : RelativeNumeric
width : ListWidth
} derive(Eq, Debug)
///|
pub fn RelativeTimeFormatter::new(locale : Locale) -> RelativeTimeFormatter {
{ locale, numeric: Auto, width: Long }
}
///|
pub fn RelativeTimeFormatter::with_numeric(
self : RelativeTimeFormatter,
numeric : RelativeNumeric,
) -> RelativeTimeFormatter {
{ ..self, numeric, }
}
///|
pub fn RelativeTimeFormatter::with_width(
self : RelativeTimeFormatter,
width : ListWidth,
) -> RelativeTimeFormatter {
{ ..self, width, }
}
///|
fn relative_special(
locale : Locale,
value : Int,
unit : RelativeUnit,
) -> String? {
let language = locale.language()
match (language, unit, value) {
("zh", Second, 0) => Some("\u{73b0}\u{5728}")
("zh", Day, -1) => Some("\u{6628}\u{5929}")
("zh", Day, 0) => Some("\u{4eca}\u{5929}")
("zh", Day, 1) => Some("\u{660e}\u{5929}")
("zh", Week, -1) => Some("\u{4e0a}\u{5468}")
("zh", Week, 0) => Some("\u{672c}\u{5468}")
("zh", Week, 1) => Some("\u{4e0b}\u{5468}")
("fr", Second, 0) => Some("maintenant")
("fr", Day, -1) => Some("hier")
("fr", Day, 0) => Some("aujourd'hui")
("fr", Day, 1) => Some("demain")
("de", Second, 0) => Some("jetzt")
("de", Day, -1) => Some("gestern")
("de", Day, 0) => Some("heute")
("de", Day, 1) => Some("morgen")
(_, Second, 0) => Some("now")
(_, Day, -1) => Some("yesterday")
(_, Day, 0) => Some("today")
(_, Day, 1) => Some("tomorrow")
(_, Week, -1) => Some("last week")
(_, Week, 0) => Some("this week")
(_, Week, 1) => Some("next week")
(_, Month, -1) => Some("last month")
(_, Month, 0) => Some("this month")
(_, Month, 1) => Some("next month")
(_, Year, -1) => Some("last year")
(_, Year, 0) => Some("this year")
(_, Year, 1) => Some("next year")
_ => None
}
}
///|
fn english_unit(
unit : RelativeUnit,
width : ListWidth,
plural : Bool,
) -> String {
match width {
Narrow =>
match unit {
Second => "s"
Minute => "m"
Hour => "h"
Day => "d"
Week => "w"
Month => "mo"
Quarter => "q"
Year => "y"
}
Short =>
match unit {
Second => "sec."
Minute => "min."
Hour => "hr."
Day => "day"
Week => "wk."
Month => "mo."
Quarter => "qtr."
Year => "yr."
}
Long => {
let root = match unit {
Second => "second"
Minute => "minute"
Hour => "hour"
Day => "day"
Week => "week"
Month => "month"
Quarter => "quarter"
Year => "year"
}
if plural {
root + "s"
} else {
root
}
}
}
}
///|
fn localized_unit(
locale : Locale,
unit : RelativeUnit,
width : ListWidth,
amount : Int,
) -> String {
if width != Long {
return english_unit(unit, width, amount != 1)
}
match locale.language() {
"zh" =>
match unit {
Second => "\u{79d2}"
Minute => "\u{5206}\u{949f}"
Hour => "\u{5c0f}\u{65f6}"
Day => "\u{5929}"
Week => "\u{5468}"
Month => "\u{4e2a}\u{6708}"
Quarter => "\u{4e2a}\u{5b63}\u{5ea6}"
Year => "\u{5e74}"
}
"fr" =>
match unit {
Second => if amount == 1 { "seconde" } else { "secondes" }
Minute => if amount == 1 { "minute" } else { "minutes" }
Hour => if amount == 1 { "heure" } else { "heures" }
Day => if amount == 1 { "jour" } else { "jours" }
Week => if amount == 1 { "semaine" } else { "semaines" }
Month => "mois"
Quarter => if amount == 1 { "trimestre" } else { "trimestres" }
Year => if amount == 1 { "an" } else { "ans" }
}
"de" =>
match unit {
Second => if amount == 1 { "Sekunde" } else { "Sekunden" }
Minute => if amount == 1 { "Minute" } else { "Minuten" }
Hour => if amount == 1 { "Stunde" } else { "Stunden" }
Day => if amount == 1 { "Tag" } else { "Tagen" }
Week => if amount == 1 { "Woche" } else { "Wochen" }
Month => if amount == 1 { "Monat" } else { "Monaten" }
Quarter => if amount == 1 { "Quartal" } else { "Quartalen" }
Year => if amount == 1 { "Jahr" } else { "Jahren" }
}
_ => english_unit(unit, width, amount != 1)
}
}
///|
fn relative_number(locale : Locale, amount : Int) -> String {
NumberFormatter::new(locale).with_grouping(false).format_int(amount)
}
///|
/// Formats a signed offset, where negative values are in the past.
pub fn RelativeTimeFormatter::format(
self : RelativeTimeFormatter,
value : Int,
unit : RelativeUnit,
) -> String {
if self.numeric == Auto {
match relative_special(self.locale, value, unit) {
Some(rendered) => return rendered
None => ()
}
}
let amount = positive_int(value)
let number = relative_number(self.locale, amount)
let unit_name = localized_unit(self.locale, unit, self.width, amount)
match self.locale.language() {
"zh" =>
if value < 0 {
"\{number}\{unit_name}\u{524d}"
} else {
"\{number}\{unit_name}\u{540e}"
}
"fr" =>
if value < 0 {
"il y a \{number} \{unit_name}"
} else {
"dans \{number} \{unit_name}"
}
"de" =>
if value < 0 {
"vor \{number} \{unit_name}"
} else {
"in \{number} \{unit_name}"
}
_ =>
if value < 0 {
"\{number} \{unit_name} ago"
} else {
"in \{number} \{unit_name}"
}
}
}