1use serde_json::Value;
2use std::sync::OnceLock;
3use std::{collections::HashMap, env};
4use string_template::Template;
5use structs::I18n;
6
7pub use structs::TranslationConfig;
8
9mod structs;
10
11static RESOURCES_KEY: &'static str = "INTL_RS_RESOURCES";
12
13static DEFAULT_LANGUAGE: &'static str = "INTL_RS_LANG";
14
15pub static I18N: OnceLock<I18n> = OnceLock::new();
16
17fn init_i18n() -> I18n {
18 let resources_path = env::var(RESOURCES_KEY).unwrap_or("src/i18n".to_owned());
19 let default_language = env::var(DEFAULT_LANGUAGE).unwrap_or("en".to_owned());
20 let inner = I18n::init(resources_path, default_language);
21 inner
22}
23
24pub fn format_message<S: Into<String>>(key: S, config: Option<&TranslationConfig>) -> String {
25 let i18n = I18N.get_or_init(init_i18n);
26 let key = key.into();
27 let default_config = TranslationConfig::default();
28 let TranslationConfig {
29 accept_language,
30 default_message,
31 args,
32 } = config.unwrap_or(&default_config);
33 let common_languages = accept_language::intersection(
34 accept_language.as_ref().unwrap_or(&i18n.default_language),
35 i18n.supported_languages.iter().map(|s| s as &str).collect(),
36 );
37 let language = common_languages.first().unwrap_or(&i18n.default_language);
38
39 let configs: &Value = &i18n.inner[language];
40
41 let template_string = match key
42 .split('.')
43 .fold(configs, |result: &Value, k| &result[&k])
44 {
45 Value::Null => default_message.clone(),
46 message => message.as_str().map(|other_str| other_str.to_string()),
47 }
48 .unwrap_or(key.to_string());
49 let template = Template::new(&template_string);
50 template.render(
51 &args
52 .as_ref()
53 .unwrap_or(&HashMap::new())
54 .iter()
55 .map(|(a, b)| (a.as_str(), b.as_str()))
56 .collect(),
57 )
58}
59
60#[macro_export]
61macro_rules! t {
62 ($key:expr) => {
63 $crate::format_message($key, None)
64 };
65 ($key:expr,accept_language:$accept_language:expr) => {
66 $crate::format_message(
67 $key,
68 Some(&$crate::TranslationConfig {
69 accept_language: Some($accept_language.into()),
70 ..Default::default()
71 }),
72 )
73 };
74 ($key:expr,accept_language:$accept_language:expr,args:$args:expr) => {{
75 let accept_language: String = $accept_language.into();
76 let mut accept_language_option = None;
77 if !accept_language.is_empty() {
78 accept_language_option = Some(accept_language)
79 }
80 $crate::format_message(
81 $key,
82 Some(&$crate::TranslationConfig {
83 accept_language: accept_language_option,
84 args: Some($args),
85 ..Default::default()
86 }),
87 )
88 }};
89 ($key:expr,config:$config:expr) => {
90 $crate::format_message($key, Some($config))
91 };
92}