1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
9pub struct I18nText {
10 pub key: String,
11 #[serde(default, skip_serializing_if = "Option::is_none")]
12 pub args: Option<BTreeMap<String, Value>>,
13}
14
15pub type ResolvedI18nMap = BTreeMap<String, String>;
17
18pub fn resolve_i18n_text(
19 fallback: &str,
20 text: Option<&I18nText>,
21 resolved: Option<&ResolvedI18nMap>,
22) -> String {
23 resolve_i18n_text_with_locale(fallback, text, resolved, None, None)
24}
25
26pub fn resolve_i18n_text_with_locale(
27 fallback: &str,
28 text: Option<&I18nText>,
29 resolved: Option<&ResolvedI18nMap>,
30 requested_locale: Option<&str>,
31 default_locale: Option<&str>,
32) -> String {
33 let Some(text) = text else {
34 return fallback.to_string();
35 };
36 let Some(resolved) = resolved else {
37 return fallback.to_string();
38 };
39 let Some(base) = resolve_by_locale(resolved, &text.key, requested_locale, default_locale)
40 else {
41 return fallback.to_string();
42 };
43
44 interpolate_args(base, text.args.as_ref())
45}
46
47fn resolve_by_locale<'a>(
48 resolved: &'a ResolvedI18nMap,
49 key: &str,
50 requested_locale: Option<&str>,
51 default_locale: Option<&str>,
52) -> Option<&'a str> {
53 for locale in [requested_locale, default_locale].iter().flatten() {
54 if let Some(value) = resolved.get(&format!("{}:{}", locale, key)) {
55 return Some(value);
56 }
57 if let Some(value) = resolved.get(&format!("{}/{}", locale, key)) {
58 return Some(value);
59 }
60 }
61 resolved.get(key).map(String::as_str)
62}
63
64fn interpolate_args(template: &str, args: Option<&BTreeMap<String, Value>>) -> String {
65 let Some(args) = args else {
66 return template.to_string();
67 };
68 let mut output = template.to_string();
69 for (name, value) in args {
70 let token = format!("{{{}}}", name);
71 let value_text = match value {
72 Value::String(v) => v.clone(),
73 _ => value.to_string(),
74 };
75 output = output.replace(&token, &value_text);
76 }
77 output
78}