use std::fmt::{Display, Write as _};
use yog_abi::YogStr;
pub fn translation(key: &str) -> Option<String> {
let api = crate::registry::global_api();
if api.is_null() {
return None;
}
let owned = unsafe { ((*api).translate)((*api).ctx, YogStr::from_str(key)) };
crate::registry::take_owned(unsafe { (*api).free_str }, owned)
}
pub fn tr(key: &str) -> String {
match translation(key) {
Some(text) => format_translation(&text, &[]),
None => key.to_string(),
}
}
pub fn tr_args(key: &str, args: &[&dyn Display]) -> String {
format_translation(&translation(key).unwrap_or_else(|| key.to_string()), args)
}
pub fn format_translation(pattern: &str, args: &[&dyn Display]) -> String {
let mut out = String::with_capacity(pattern.len());
let mut next = 0;
let mut rest = pattern;
while let Some(pos) = rest.find('%') {
out.push_str(&rest[..pos]);
let spec = &rest[pos + 1..];
if let Some(after) = spec.strip_prefix('%') {
out.push('%');
rest = after;
continue;
}
if let Some(after) = spec.strip_prefix(['s', 'd']) {
match args.get(next) {
Some(arg) => {
let _ = write!(out, "{arg}");
}
None => out.push_str(&rest[pos..pos + 2]),
}
next += 1;
rest = after;
continue;
}
let digits = spec.bytes().take_while(u8::is_ascii_digit).count();
let numbered = (digits > 0)
.then(|| spec[digits..].strip_prefix('$'))
.flatten()
.and_then(|s| s.strip_prefix(['s', 'd']));
match (numbered, spec[..digits].parse::<usize>()) {
(Some(after), Ok(n)) if n >= 1 => {
match args.get(n - 1) {
Some(arg) => {
let _ = write!(out, "{arg}");
}
None => out.push_str(&rest[pos..rest.len() - after.len()]),
}
rest = after;
}
_ => {
out.push('%');
rest = spec;
}
}
}
out.push_str(rest);
out
}
#[macro_export]
macro_rules! tr {
($key:expr_2021 $(,)?) => {
$crate::tr($key)
};
($key:expr_2021, $($arg:expr_2021),+ $(,)?) => {
$crate::tr_args($key, &[$(&$arg as &dyn ::std::fmt::Display),+])
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_like_minecraft() {
assert_eq!(
format_translation("%s of %s mods", &[&3, &"58"]),
"3 of 58 mods"
);
assert_eq!(format_translation("%2$s, %1$s", &[&"a", &"b"]), "b, a");
assert_eq!(format_translation("100%% by %s", &[&"X"]), "100% by X");
assert_eq!(format_translation("%d items", &[&5]), "5 items");
assert_eq!(
format_translation("missing %s and %3$s", &[]),
"missing %s and %3$s"
);
assert_eq!(format_translation("50% off %x", &[&1]), "50% off %x");
assert_eq!(format_translation("trailing %", &[]), "trailing %");
assert_eq!(format_translation("Привет, %s!", &[&"мир"]), "Привет, мир!");
}
#[test]
fn unknown_key_without_runtime_is_the_key() {
assert_eq!(tr("mymod.nothing"), "mymod.nothing");
assert_eq!(crate::tr!("count %s", 4), "count 4");
}
}