#实时 #简单-i18n

sorrow-i18n

简单的实时国际化实现,能够实时更新本地化

3 个版本

0.1.2 2021年11月7日
0.1.1 2021年11月6日
0.1.0 2021年10月31日

#150 in 国际化 (i18n)

MIT 许可证

44KB
555

简单国际化

ci docs.rs crates
简单加载区域设置的实现

依赖

基础

基本使用假设您在项目中不使用任何功能。

[dependencies]
sorrow-i18n = "0.1.0"

功能

incl_dir

一个简单的功能,用于将静态文件加载到项目中。

用法

如果使用 incl_dir 功能,添加了用于静态内核初始化的宏 init!init_dir!。初始化后,在任何地方使用宏: i18n!

用法

文档

用法

基础用法与静态文件

首先在项目中添加依赖 sorrow-i18n。文件结构的一个典型视图是 FileStructure。数据方案 - scheme/locale-scheme.json。因此,我们将创建实际的最小工作文件

kind: I18N
locale: EN
description: test en
data:
  name: "Test"

其次,第二个本地化

kind: I18N
locale: RU
description: test ru
data:
  name: "Тест"

现在是我们创建的代码库,我们将之前创建的文件放在其中,例如,在 locale/

use sorrow_i18n::{GetData, InternationalCore};
let core = InternationalCore::new("locale/");

创建核心后,我们可以获取我们的本地化并与之交互。

let eu = core.get_by_locale("EN")?;
let ru = core.get_by_locale("RU")?;

OR

let eu = core.get_by_locale("EN")?;
let ru = core.get_by_locale("RU")?;

这些方法之间有什么区别?首先,当调用 get_by_locale 方法时,返回可变数据的引用,而在 get_by_locale 的情况下,你只是获取数据的引用(在两种情况下,你都在与包装器一起工作,它不能从外部更改)。此外,在第一种方法中,如果你的本地化在程序执行期间可以更改,那么你会收到最新的数据,但会有阻塞的开销。另外,在第二种方法中,如果你的文件是静态的,被加载到项目中,并且不能更改,那么在这种情况下,你只需与 HashMap 一起工作。
但是,我们如何跟踪或选择文件跟踪策略?我们将如何获取这些更新?提供商将帮助我们完成这项工作!默认情况下,我们为您提供了几个此类提供商,具体如下

  • StaticFileProvider - 静态文件。它没有被监控。如果文件结构中没有指定提供商,这是默认选项
  • FileProvider - 文件的动态监控器。如果未指定提供商,默认为 StaticFileProvider。问题仍然是,如何选择此提供商?很简单,下面是一个显式指定提供商的示例
kind: I18N
locale: RU
description: test ru
provider: FileProvider
data:
  name: "Тест"

OR

kind: I18N
locale: RU
description: test ru
provider: StaticFileProvider
data:
  name: "Тест"

您可以在下面了解更多有关提供商的信息。
最后,我们得到了我们的区域设置,接下来要得到我们想要的!即:data.name

 assert_eq!("Test", eu.get("data.name")?);
 assert_eq!("Тест", ru.get("data.name")?);

如您所见,一切都很简单,也有类似的默认值获取方法(这将与您请求的键相同)

 assert_eq!("Test", eu.get_or_default("data.name"));
 assert_eq!("Тест", ru.get_or_default("data.name"));
 assert_eq!("keykey", eu.get_or_default("keykey"));

您可以在examples/*中看到更多示例

提供者

如我们之前所述,提供者负责数据更新策略。它的主要方法是watch。

pub trait WatchProvider {
    /// The main observer method that is called to observe the state.
    fn watch(&mut self) -> Result<(), Error>;

    /// Setter for data reference.
    fn set_data(&mut self, data: Arc<RwLock<HashMap<String, String>>>) -> Result<(), Error>;
}

StaticFileProvider

它是观察数据变化的。对于静态观察者,我们有一个空方法,因为我们不需要观察。

impl WatchProvider for StaticFileProvider {
    fn watch(&mut self) -> Result<(), Error> {
        Ok(())
    }

    fn set_data(&mut self, _data: Arc<RwLock<HashMap<String, String>>>) -> Result<(), Error> {
        Ok(())
    }
}

FileProvider

但是,与FileProvider相比,并没有复杂多少。使用notify库不断监视文件的状态。唯一跟踪的事件是文件更改。

let event = result.map_err(|e| Error::WatchError { message: e.to_string() }).unwrap();
if event.kind.is_modify() {
    ...
}

每次我们更改文件,我们都会对我们的数据进行锁定,但首先我们需要加载更新的文件本身(以验证结构)。实际上,以这种方式阻塞是非常非常困难的。

// Validation file
let structure = load_struct(&path.clone()).unwrap();

// Lock data and clear
let mut w_holder = holder.write().unwrap();
w_holder.clear();

// Clone internal state.
let l_holder = structure.messages.write().unwrap().clone();
w_holder.extend(l_holder);

自定义提供者

在某些情况下是必要的,例如,首先加载项目区域设置,然后保持与数据库或其他数据源的连接,以不断更新数据本身。为此,我们可以创建自己的数据提供者!最简单和说明性的示例在examples/custom_provider.rs中。
好吧,现在,一点一点地,首先,让我们创建一个简单的结构来监视我们的数据。

pub struct CustomProvider {
    data: Arc<RwLock<HashMap<String, String>>>,
}

我们将为它实现我们的提供者

impl WatchProvider for CustomProvider {
    fn watch(&mut self) -> Result<(), sorrow_i18n::Error> {
        println!("Accepted custom provider");
        let data = self.data.write();
        let mut un = data.unwrap();
        // Print all current data
        un.iter().for_each(|kv| {
            println!("Key: {}, Value: {}", kv.0, kv.1);
        });
        // Add new key
        un.insert("Hello".to_string(), "World".to_string());
        // Print all data, current data has been contains key "Hello"
        un.iter().for_each(|kv| {
            println!("Key: {}, Value: {}", kv.0, kv.1);
        });
        Ok(())
    }

    fn set_data(&mut self, data: Arc<RwLock<HashMap<String, String>>>) -> Result<(), Error> {
        self.data = data;
        println!("Data has been set");
        Ok(())
    }
}

只剩下一点,将我们的数据提供者添加到任何区域。

    let mut core = InternationalCore::new("locale/");
    core.add_provider("EN", Box::new(CustomProvider::new()))?;

如果存在这样的区域,将执行以下操作

  • holder -> 获取当前数据
  • provider -> set_data(current_data_in_holder)
  • provider -> watch()

宏使用

添加依赖项

[dependencies]
sorrow-i18n = { version = "0.1.0", features = ["macro"] }

初始设置

有两种初始化方法(取决于是否包含incl_dir)。

  • init_i18n! - 允许您初始化i18n核心的宏。(InternationalCore)
    • 使用示例:init_i18n!("my_locale_folder");
  • init_i18n_static_dir! - 与此相同,但仅针对incl_dir功能。
    • 使用示例:const PROJECT_DIR: Dir = include_dir!("resources/en_ru"); init_i18n_static_dir!(PROJECT_DIR);

用法

交互的主要宏将是i18n!。第一个参数是区域,第二个参数是键。如果找不到区域或键,则返回键本身而不是值。

使用示例

    let manifest = format!("{}{}", env!("CARGO_MANIFEST_DIR"), "/resources/en_ru");
    // Init i18n core
    init_i18n!(manifest);
    // Getting data.name key by ru locale
    let test = i18n!("RU", "data.name");
    println!("test: {}", &*test);
    assert_eq!("Тест", &*test);

找不到区域或键的情况

    let not_found_data = i18n!("RANDOM_LOCALE_NAME", "data.not_found_me");
    println!("data not found: {}", &*not_found_data);
    assert_eq!("data.not_found_me", &*not_found_data);

带有自定义数据提供者的示例

incl_dir的使用

添加依赖项

[dependencies]
sorrow-i18n = { version = "0.1.0", features = ["incl_dir"] }

初始设置和使用

const PROJECT_DIR: Dir = include_dir!("resources/en_ru");
let core = InternationalCore::from(PROJECT_DIR);

依赖项

~3–13MB
~146K SLoC