loon-embed 0.5.0

I18n for Rust, support embed YAML, JSON.
Documentation
/*!
[![CI](https://github.com/longbridgeapp/loon/actions/workflows/ci.yml/badge.svg)](https://github.com/longbridgeapp/loon/actions/workflows/ci.yml) [![Docs](https://docs.rs/loon-embed/badge.svg)](https://docs.rs/loon-embed/) [![Crates.io](https://img.shields.io/crates/v/loon-embed.svg)](https://crates.io/crates/loon-embed)

Loon is a localization/internationalization library, inspired by [ruby-i18n](https://github.com/ruby-i18n/i18n).

It use [rust-embed](https://crates.io/crates/rust-embed) for embed the localization assets into your binary.

### Usage

Load locales assets by RustEmbed and init Loon in your `lib.rs`

```ignore
use rust_embed::RustEmbed;

// Load Loon macro, for allow you use `t!` macro in anywhere.
#[macro_use]
extern crate loon_embed;

// Use RustEmbed to locale assets
#[derive(RustEmbed)]
#[folder = "locales/"]
#[include = "*.yml"]
struct Asset;

fn main() {
    loon_embed::init::<Asset>("en");
}
```

You must put I18n YAML files in `locales/` folder.

```bash
locales/
├── en.yml
├── zh-CN.yml
```

For example of `en.yml`:

```yml
greeting: Hello world
messages:
  hello: Hello, {}
```

Now you can use `t!` macro in anywhere.

```ignore
t!("greeting");
// => "Hello world"

t!("messages.hello", "world");
// => "Hello, world"
```

You can use `loon_embed::set_locale` or call `loon_embed::init` agian to change the current locale in runtime.

```rs
loon_embed::set_locale("zh-CN");
loon_embed::locale();
// => "zh-CN"
```
*/
mod config;
mod dict;
mod err;
mod key;
mod opts;
use std::sync::Once;

/// Get I18n text with current locale
///
/// ```ignore
/// t!("greeting"); // greeting: "Hello world" => "Hello world"
/// t!("messages.hello", "world"); // messages.hello: "Hello, {}" => "Hello, world"
/// ```
#[macro_export]
macro_rules! t {
    ($key:expr) => {
        $crate::translate($key)
    };
    ($key:expr, $($arg:tt)+) => {
        {
            let mut message = $crate::translate($key);
            $(
                message = message.replace("{}", $arg);
            )+
            message
        }
    };
}

use config::Config;
use std::sync::Mutex;

#[macro_use]
extern crate lazy_static;

struct CurrentDictionary {
    default_locale: String,
    dict: dict::Dictionary,
}

impl Default for CurrentDictionary {
    fn default() -> Self {
        let default_locale = "en".to_string();
        let dict = Config::default().finish().unwrap();

        CurrentDictionary {
            default_locale,
            dict,
        }
    }
}

impl CurrentDictionary {
    fn set_locale(&mut self, locale: &str) {
        self.default_locale = locale.to_string();
        self.dict.default_locale = locale.to_string();
    }
}

lazy_static! {
    static ref _CURRENT_DICTIONARY: Mutex<CurrentDictionary> =
        Mutex::new(CurrentDictionary::default());
}

static INITIALIZED: Once = Once::new();

/// Init I18n locales assets from Rust Embed and setup default_locale.
///
/// This method only init asset dictionary once,
/// when Assets has been initialized, just update default_locale.
///
/// ```ignore
/// use rust_embed::RustEmbed;
///
/// // Use RustEmbed to locale assets
/// #[derive(RustEmbed)]
/// #[folder = "locales/"]
/// #[include = "*.yml"]
/// struct Asset;

/// loon_embed::init::<Asset>("en");
/// ```
pub fn init<T: rust_embed::RustEmbed>(default_locale: &str) {
    let mut current_dict = _CURRENT_DICTIONARY.lock().unwrap();
    INITIALIZED.call_once(|| {
        current_dict.dict = Config::default().with_embed::<T>().finish().unwrap();
    });

    current_dict.set_locale(default_locale);
}

/// Set current locale
///
/// ```
/// loon_embed::set_locale("en");
/// // or
/// loon_embed::set_locale("zh-CN");
/// ```
pub fn set_locale(locale: &str) {
    _CURRENT_DICTIONARY.lock().unwrap().set_locale(locale);
}

/// Get current locale
///
/// ```
/// loon_embed::set_locale("zh-CN");
/// loon_embed::locale();
/// // "zh-CN"
/// ```
pub fn locale() -> String {
    _CURRENT_DICTIONARY.lock().unwrap().default_locale.clone()
}

#[allow(dead_code)]
pub fn translate(key: &str) -> String {
    return _CURRENT_DICTIONARY
        .lock()
        .unwrap()
        .dict
        .t(key, None)
        .unwrap_or_else(|_| key.to_string());
}

#[cfg(test)]
mod tests {
    use rust_embed::RustEmbed;

    #[derive(RustEmbed)]
    #[folder = "examples/locales/"]
    struct Asset;

    #[test]
    fn test_translate() {
        crate::init::<Asset>("en");
        assert_eq!(t!("messages.zero"), "You have no messages.");
        assert_eq!(t!("messages.hello", "world"), "Hello, world!");

        crate::set_locale("de");
        assert_eq!(t!("messages.hello", "world"), "messages.hello");
    }
}