1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! # 🔞
//!
//! `r18` is a crate intends to simplify the internationalisation of Rust
//! projects.
//!
//! `MSRV >= 1.70.0 (stable)`
//!
//! ## Usage
//!
//! Add `r18` as your project dependency:
//!
//! ```toml
//! [dependencies]
//! r18 = "*"
//! ```
//! Create a `JSON` translation file whose filename follows
//!  [IETF BCP 47](https://www.wikiwand.com/en/IETF_language_tag)
//! language tag, like below:
//!
//! ```json
//! // PATH: ./tr/zh-CN.json
//! {
//!     "Hello, {}": "你好,{}"
//! }
//! ```
//!
//! Then add [`init`] to the global scope of your code with
//! the directory where translation files in (in following example is `./tr`).
//!
//! ```ignore
//! r18::init!("tr");
//! ```
//!
//! After initialising the `r18`, use [`auto_detect`] to detect locale and load
//! translation model automatically.  
//! If you want, you can use [`set_locale`] to set locale manually.  
//! After above process, use [`tr`] to get your text which has been translated.
//!
//! ```ignore
//! r18::init!("tr");
//!
//! fn main() {
//!     r18::auto_detect!(); // get locale & set
//!
//!     let name = "ho-229";
//!     println!("{}", r18::tr!("Hello, {}", name));
//!
//!     // reset locale to disable translation
//!     r18::set_locale!("");
//!     assert_eq!("Hello, ho-229", r18::tr!("Hello, {}", name));
//! }
//! ```
//!
//! ### Fallback Configuration
//!
//! Sometimes your translation may not fully match the user's locale,
//! but usually, this doesn't mean that your translations cannot be used.
//! In that case, we need the fallback feature.
//!
//! By default, if the translation does not match the user's locale,
//! `r18` will fallback to the translation which is the same language
//! by the highest alphabetical order.
//!
//! You can also specify a fallback translation for a language in `config.json`
//! which placed with other translation files.
//!
//! eg.
//! ```json
//! {
//!     "fallback": {
//!         "zh": "zh-TW"
//!     }
//! }
//! ```

use std::sync::{Mutex, OnceLock};

#[doc(hidden)]
pub use dynfmt::{Format, SimpleCurlyFormat};
#[doc(hidden)]
pub use oxilangtag::{LanguageTag, LanguageTagParseError};
#[doc(hidden)]
pub use phf;
pub use r18_proc_macros::init;
#[doc(hidden)]
pub use sys_locale::get_locale;

mod_use::mod_use!(macros);

#[doc(hidden)]
pub struct Locale {
    pub name: &'static str,
    pub translate: phf::Map<&'static str, &'static str>,
}

#[doc(hidden)]
pub static CURRENT_LOCALE: OnceLock<Mutex<Option<&'static Locale>>> = OnceLock::new();

/// Translate content with the locale setting and given prefix.
///
/// We recommend using [`tr!`] instead of [`translate`] for translate your
/// content.
pub fn translate(prefix: impl AsRef<str>, content: &str) -> &str {
    let locale = CURRENT_LOCALE
        .get_or_init(|| Mutex::new(None))
        .lock()
        .unwrap();
    let Some(locale) = *locale else {
        return content;
    };

    match locale
        .translate
        .get(format!("{} {}", prefix.as_ref(), content).as_str())
    {
        Some(tr) => tr,
        None => content,
    }
}