1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

i18n: utilities to format short dates and times

This commit is contained in:
Quentin Gliech
2024-02-01 18:06:38 +01:00
parent b498e5971d
commit 36ebbc4d70
5 changed files with 296 additions and 0 deletions

View File

@@ -13,12 +13,15 @@ workspace = true
[dependencies]
camino.workspace = true
icu_calendar = { version = "1.4.0", features = ["compiled_data", "std"] }
icu_datetime = { version = "1.4.0", features = ["compiled_data", "std"] }
icu_list = { version = "1.4.0", features = ["compiled_data", "std"] }
icu_locid = { version = "1.4.0", features = ["std",] }
icu_locid_transform = { version = "1.4.0", features = ["compiled_data", "std"] }
icu_plurals = { version = "1.4.0", features = ["compiled_data", "std"] }
icu_provider = { version = "1.4.0", features = ["std", "sync"] }
icu_provider_adapters = { version = "1.4.0", features = ["std"] }
icu_relativetime = { version = "0.1.4", features = ["compiled_data", "std"] }
pad = "0.1.6"
pest = "2.7.6"
pest_derive = "2.7.6"

View File

@@ -16,6 +16,8 @@ pub mod sprintf;
pub mod translations;
mod translator;
pub use icu_calendar;
pub use icu_datetime;
pub use icu_locid::locale;
pub use icu_provider::DataLocale;

View File

@@ -24,6 +24,7 @@ use icu_provider::{
DataRequest, DataRequestMetadata,
};
use icu_provider_adapters::fallback::LocaleFallbackProvider;
use icu_relativetime::{options::Numeric, RelativeTimeFormatter, RelativeTimeFormatterOptions};
use thiserror::Error;
use writeable::Writeable;
@@ -298,6 +299,58 @@ impl Translator {
Ok(list)
}
/// Format a relative date
///
/// # Parameters
///
/// * `locale` - The locale to use.
/// * `days` - The number of days to format, where 0 = today, 1 = tomorrow,
/// -1 = yesterday, etc.
///
/// # Errors
///
/// Returns an error if the requested locale is not found.
pub fn relative_date(
&self,
locale: &DataLocale,
days: i64,
) -> Result<String, icu_relativetime::RelativeTimeError> {
// TODO: this is not using the fallbacker
let formatter = RelativeTimeFormatter::try_new_long_day(
locale,
RelativeTimeFormatterOptions {
numeric: Numeric::Auto,
},
)?;
let date = formatter.format(days.into());
Ok(date.write_to_string().into_owned())
}
/// Format time
///
/// # Parameters
///
/// * `locale` - The locale to use.
/// * `time` - The time to format.
///
/// # Errors
///
/// Returns an error if the requested locale is not found.
pub fn short_time<T: icu_datetime::input::IsoTimeInput>(
&self,
locale: &DataLocale,
time: &T,
) -> Result<String, icu_datetime::DateTimeError> {
// TODO: this is not using the fallbacker
let formatter = icu_datetime::TimeFormatter::try_new_with_length(
locale,
icu_datetime::options::length::Time::Short,
)?;
Ok(formatter.format_to_string(time))
}
/// Get a list of available locales.
#[must_use]
pub fn available_locales(&self) -> Vec<&DataLocale> {

View File

@@ -325,6 +325,93 @@ impl Object for TranslateFunc {
Ok(Value::from_safe_string(buf))
}
fn call_method(&self, _state: &State, name: &str, args: &[Value]) -> Result<Value, Error> {
match name {
"relative_date" => {
let (date,): (String,) = from_args(args)?;
let date: chrono::DateTime<chrono::Utc> = date.parse().map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
"Invalid date while calling function `relative_date`",
)
.with_source(e)
})?;
// TODO: grab the clock somewhere
#[allow(clippy::disallowed_methods)]
let now = chrono::Utc::now();
let diff = (date - now).num_days();
Ok(Value::from(
self.translator
.relative_date(&self.lang, diff)
.map_err(|_e| {
Error::new(
ErrorKind::InvalidOperation,
"Failed to format relative date",
)
})?,
))
}
"short_time" => {
let (date,): (String,) = from_args(args)?;
let date: chrono::DateTime<chrono::Utc> = date.parse().map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
"Invalid date while calling function `time`",
)
.with_source(e)
})?;
// TODO: we should use the user's timezone here
let time = date.time();
Ok(Value::from(
self.translator
.short_time(&self.lang, &TimeAdapter(time))
.map_err(|_e| {
Error::new(ErrorKind::InvalidOperation, "Failed to format time")
})?,
))
}
_ => Err(Error::new(
ErrorKind::InvalidOperation,
"Invalid method on include_asset",
)),
}
}
}
/// An adapter to make a [`Timelike`] implement [`IsoTimeInput`]
///
/// [`Timelike`]: chrono::Timelike
/// [`IsoTimeInput`]: mas_i18n::icu_datetime::input::IsoTimeInput
struct TimeAdapter<T>(T);
impl<T: chrono::Timelike> mas_i18n::icu_datetime::input::IsoTimeInput for TimeAdapter<T> {
fn hour(&self) -> Option<mas_i18n::icu_calendar::types::IsoHour> {
let hour: usize = chrono::Timelike::hour(&self.0).try_into().ok()?;
hour.try_into().ok()
}
fn minute(&self) -> Option<mas_i18n::icu_calendar::types::IsoMinute> {
let minute: usize = chrono::Timelike::minute(&self.0).try_into().ok()?;
minute.try_into().ok()
}
fn second(&self) -> Option<mas_i18n::icu_calendar::types::IsoSecond> {
let second: usize = chrono::Timelike::second(&self.0).try_into().ok()?;
second.try_into().ok()
}
fn nanosecond(&self) -> Option<mas_i18n::icu_calendar::types::NanoSecond> {
let nanosecond: usize = chrono::Timelike::nanosecond(&self.0).try_into().ok()?;
nanosecond.try_into().ok()
}
}
struct IncludeAsset {