From b9d7d7a6d7fd03483f52845489d17296488772db Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 28 Aug 2023 14:51:03 +0800 Subject: [PATCH] Add to support translated text with multiple locales in one file. (#50) - Add to support translated text with multiple locales. - Add `_version` for locale file for special the file format. - `_version: 1` - Split each language into difference files (default). - `_version: 2` - All language in same file. - Update `cargo i18n` command for output `_version: 2` format. - Improve generator output to remove leading `---` in YAML file. --- Cargo.toml | 6 +- Makefile | 4 +- README.md | 142 ++++++++++++++--------- crates/extract/Cargo.toml | 8 +- crates/extract/src/generator.rs | 199 ++++++++++++++++++++++++-------- crates/support/Cargo.toml | 6 +- crates/support/src/lib.rs | 170 ++++++++++++++++++++++++++- src/main.rs | 9 +- tests/integration_tests.rs | 41 ++++++- tests/locales/v2.yml | 13 +++ 10 files changed, 476 insertions(+), 122 deletions(-) create mode 100644 tests/locales/v2.yml diff --git a/Cargo.toml b/Cargo.toml index c9868d7..ed5ee86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" name = "rust-i18n" readme = "README.md" repository = "https://github.com/longbridgeapp/rust-i18n" -version = "2.1.1-alpha.0" +version = "2.2.0-alpha.0" [dependencies] anyhow = {version = "1", optional = true} @@ -18,8 +18,8 @@ clap = {version = "2.32", optional = true} itertools = {version = "0.10.3", optional = true} once_cell = "1.10.0" quote = {version = "1", optional = true} -rust-i18n-extract = {path = "./crates/extract", version = "2.0.0", optional = true} -rust-i18n-support = {path = "./crates/support", version = "2.0.0"} +rust-i18n-extract = {path = "./crates/extract", version = "2.1.0", optional = true} +rust-i18n-support = {path = "./crates/support", version = "2.1.0"} rust-i18n-macro = {path = "./crates/macro", version = "2.0.0"} serde = "1" serde_derive = "1" diff --git a/Makefile b/Makefile index 5a56c87..73db3b8 100644 --- a/Makefile +++ b/Makefile @@ -7,5 +7,5 @@ release\:extract: release: cargo release test: - RUST_TEST_THREADS=1 cargo test --workspace - RUST_TEST_THREADS=1 cargo test --manifest-path examples/app-workspace/Cargo.toml --workspace + cargo test --workspace + cargo test --manifest-path examples/app-workspace/Cargo.toml --workspace diff --git a/README.md b/README.md index 8c9bb82..0b0f985 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The API of this crate is inspired by [ruby-i18n](https://github.com/ruby-i18n/i1 - Global `t!` macro for loading localized text in everywhere. - Use YAML (default), JSON or TOML format for mapping localized text, and support mutiple files merging. - `cargo i18n` Command line tool for checking and extract untranslated texts into YAML files. +- Support all localized texts in one file, or split into difference files by locale. ## Usage @@ -23,10 +24,10 @@ Add crate dependencies in your Cargo.toml and setup I18n config: ```toml [dependencies] -rust-i18n = "1" +rust-i18n = "2" ``` -Load macro and init translations in `lib.rs` +Load macro and init translations in `lib.rs` or `main.rs`: ```rs // Load I18n macro, for allow you use `t!` macro in anywhere. @@ -60,59 +61,43 @@ fn main() { } ``` -Make sure all localized files (containing the localized mappings) are located in the `locales/` folder of the project root directory: +## Locale file + +You can use `_version` key to specify the version of the locale file, and the default value is `1`. + +### Split Localized Texts into Difference Files -> πŸ’‘Since: v2.0.0, the localized files supports use multiple formats, including `*.{yml,yaml,json,toml}`, and will merge all them. +> _version: 1 + +You can also split the each language into difference files, and you can choise (YAML, JSON, TOML), for example: `en.json`: ```bash . β”œβ”€β”€ Cargo.lock β”œβ”€β”€ Cargo.toml β”œβ”€β”€ locales -β”‚ β”œβ”€β”€ en.yml β”‚ β”œβ”€β”€ zh-CN.yml -β”‚ └── zh-HK.yml +β”‚ β”œβ”€β”€ en.yml └── src β”‚ └── main.rs -└── sub_app -β”‚ └── locales -β”‚ β”‚ └── en.yml -β”‚ β”‚ └── zh-CN.yml -β”‚ β”‚ └── zh-HK.yml -β”‚ └── src -β”‚ β”‚ └── main.rs -β”‚ └── Cargo.toml ``` -In the localized files, specify the localization keys and their corresponding values, for example, in `en.yml`: - -```yml -hello: Hello world # A simple key -> value mapping -messages: - hello: Hello, %{name} # A nested key.sub_key -> value mapping, in this case "messages.hello" maps to "Hello, %{name}" -``` - -And example of the `zh-CN.yml`: - ```yml -hello: δ½ ε₯½δΈ–η•Œ -messages: - hello: δ½ ε₯½οΌŒ%{name} (%{count}) +_version: 1 +hello: "Hello world" +messages.hello: "Hello, %{name}" ``` -If you wants use JSON format, just rename the file to `en.json` and the content is like this: +Or use JSON or TOML format, just rename the file to `en.json` or `en.toml`, and the content is like this: ```json { + "_version": 1, "hello": "Hello world", - "messages": { - "hello": "Hello, %{name}" - } + "messages.hello": "Hello, %{name}" } ``` -Or use TOML format, just rename the file to `en.toml` and the content is like this: - ```toml hello = "Hello world" @@ -120,7 +105,47 @@ hello = "Hello world" hello = "Hello, %{name}" ``` -### Loading Localized Strings in Rust +### All Localized Texts in One File + +> _version: 2 + +Make sure all localized files (containing the localized mappings) are located in the `locales/` folder of the project root directory: + +```bash +. +β”œβ”€β”€ Cargo.lock +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ locales +β”‚ β”œβ”€β”€ app.yml +β”‚ β”œβ”€β”€ some-module.yml +└── src +β”‚ └── main.rs +└── sub_app +β”‚ └── locales +β”‚ β”‚ └── app.yml +β”‚ └── src +β”‚ β”‚ └── main.rs +β”‚ └── Cargo.toml +``` + +In the localized files, specify the localization keys and their corresponding values, for example, in `app.yml`: + + +```yml +_version: 2 +hello: + en: Hello world + zh-CN: δ½ ε₯½δΈ–η•Œ +messages.hello: + en: Hello, %{name} + zh-CN: δ½ ε₯½οΌŒ%{name} +``` + +This is useful when you use [GitHub Copilot](https://github.com/features/copilot), after you write a first translated text, then Copilot will auto generate other locale's translations for you. + + + +### Get Localized Strings in Rust Import the `t!` macro from this crate into your current scope: @@ -150,7 +175,7 @@ t!("messages.hello", locale = "zh-CN", "name" => "Jason", "count" => 3 + 2); // => "δ½ ε₯½οΌŒJason (5)" ``` -### Setting and Getting the Global Locale +### Current Locale You can use `rust_i18n::set_locale` to set the global locale at runtime, so that you don't have to specify the locale on each `t!` invocation. @@ -161,16 +186,6 @@ let locale = rust_i18n::locale(); assert_eq!(locale, "zh-CN"); ``` -## Extractor - -We provided a `cargo i18n` command line tool for help you extract the untranslated texts from the source code and then write into YAML file. - -You can install it via `cargo install rust-i18n`, then you get `cargo i18n` command. - -```bash -$ cargo install rust-i18n -``` - ### Extend Backend Since v2.0.0 rust-i18n support extend backend for cusomize your translation implementation. @@ -219,7 +234,32 @@ This also will load local translates from ./locales path, but your own `RemoteI1 Now you call `t!` will lookup translates from your own backend first, if not found, will lookup from local files. -### Configuration for `cargo i18n` command +## Example + +A minimal example of using rust-i18n can be found [here](https://github.com/longbridgeapp/rust-i18n/tree/main/examples). + +## I18n Ally + +I18n Ally is a VS Code extension for helping you translate your Rust project. + +You can add [i18n-ally-custom-framework.yml](https://github.com/longbridgeapp/rust-i18n/blob/main/.vscode/i18n-ally-custom-framework.yml) to your project `.vscode` directory, and then use I18n Ally can parse `t!` marco to show translate text in VS Code editor. + + +## Extractor + +> __Experimental__ + +We provided a `cargo i18n` command line tool for help you extract the untranslated texts from the source code and then write into YAML file. + +> In current only output YAML, and use `_version: 2` format. + +You can install it via `cargo install rust-i18n`, then you get `cargo i18n` command. + +```bash +$ cargo install rust-i18n +``` + +### Extractor Config πŸ’‘ NOTE: `package.metadata.i18n` config section in Cargo.toml is just work for `cargo i18n` command, if you don't use that, you don't need this config. @@ -303,16 +343,6 @@ The `RUST_I18N_DEBUG` environment variable can be used to print out some debuggi $ RUST_I18N_DEBUG=1 cargo build ``` -## Example - -A minimal example of using rust-i18n can be found [here](https://github.com/longbridgeapp/rust-i18n/tree/main/examples). - -## I18n Ally - -I18n Ally is a VS Code extension for helping you translate your Rust project. - -You can add [i18n-ally-custom-framework.yml](https://github.com/longbridgeapp/rust-i18n/blob/main/.vscode/i18n-ally-custom-framework.yml) to your project `.vscode` directory, and then use I18n Ally can parse `t!` marco to show translate text in VS Code editor. - ## Benchmark Benchmark `t!` method, result on Apple M1: diff --git a/crates/extract/Cargo.toml b/crates/extract/Cargo.toml index ec26505..5db5f5b 100644 --- a/crates/extract/Cargo.toml +++ b/crates/extract/Cargo.toml @@ -5,7 +5,7 @@ license = "MIT" name = "rust-i18n-extract" readme = "../../README.md" repository = "https://github.com/longbridgeapp/rust-i18n" -version = "2.0.0" +version = "2.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,8 +15,12 @@ ignore = "0.4" proc-macro2 = { version = "1", features = ["span-locations"] } quote = "1" regex = "1" -rust-i18n-support = { path = "../support", version = "2.0.0" } +rust-i18n-support = { path = "../support", version = "2.1.0" } serde = "1" serde_json = "1" serde_yaml = "0.8" syn = { version = "2.0.18", features = ["full"] } +toml = "0.7.4" + +[dev-dependencies] +indoc = "1" diff --git a/crates/extract/src/generator.rs b/crates/extract/src/generator.rs index 4300b82..4fc2ffc 100644 --- a/crates/extract/src/generator.rs +++ b/crates/extract/src/generator.rs @@ -5,73 +5,182 @@ use std::io::prelude::*; use std::io::Result; use std::path::Path; +type Translations = HashMap>; + pub fn generate<'a, P: AsRef>( - output: P, - locale: &str, - messages: impl IntoIterator, + output_path: P, + all_locales: &Vec, + messages: impl IntoIterator + Clone, ) -> Result<()> { - println!("Checking [{}] and generating untranslated texts...", locale); - - // TODO.en.yml - let filename = format!("TODO.{}.yml", locale); - // ~/work/my-project/locales - let output_path = output.as_ref().display().to_string(); - - let ignore_file = |fname: &str| fname.ends_with(&filename); - let data = load_locales(&output_path, ignore_file); - - let mut new_values: HashMap = HashMap::new(); - - for m in messages { - if !m.locations.is_empty() { - for _l in &m.locations { - // TODO: write file and line as YAML comment - } - } - - if let Some(trs) = data.get(locale) { - if trs.get(&m.key).is_some() { - continue; - } - } - - let value = m.key.split('.').last().unwrap_or_default(); + let filename = "TODO.yml"; + let format = "yaml"; - new_values - .entry(m.key.clone()) - .or_insert_with(|| value.into()); - } + let trs = generate_result(&output_path, filename, all_locales, messages); - write_file(&output, &filename, &new_values)?; - - if new_values.is_empty() { + if trs.is_empty() { println!("All thing done.\n"); return Ok(()); } - eprintln!("Found {} new texts need to translate.", new_values.len()); + eprintln!("Found {} new texts need to translate.", trs.len()); eprintln!("----------------------------------------"); eprintln!("Writing to {}\n", filename); - write_file(&output, &filename, &new_values)?; + let text = convert_text(&trs, format); + write_file(&output_path, &filename, &text)?; // Finally, return error for let CI fail let err = std::io::Error::new(std::io::ErrorKind::Other, ""); Err(err) } -fn write_file>( - output: &P, - filename: &str, - translations: &HashMap, -) -> Result<()> { +fn convert_text(trs: &Translations, format: &str) -> String { + let mut value = serde_json::Value::Object(serde_json::Map::new()); + value["_version"] = serde_json::Value::Number(serde_json::Number::from(2)); + + for (key, val) in trs { + let mut obj = serde_json::Value::Object(serde_json::Map::new()); + for (locale, text) in val { + obj[locale] = serde_json::Value::String(text.clone()); + } + value[key] = obj; + } + + match format { + "json" => serde_json::to_string_pretty(&value).unwrap(), + "yaml" | "yml" => { + let text = serde_yaml::to_string(&value).unwrap(); + // Remove leading `---` + text.trim_start_matches("---").trim_start().to_string() + } + "toml" => toml::to_string_pretty(&value).unwrap(), + _ => unreachable!(), + } +} + +fn generate_result<'a, P: AsRef>( + output_path: P, + output_filename: &str, + all_locales: &Vec, + messages: impl IntoIterator + Clone, +) -> Translations { + let mut trs = Translations::new(); + + for locale in all_locales { + println!("Checking [{}] and generating untranslated texts...", locale); + + // ~/work/my-project/locales + let output_path = output_path.as_ref().display().to_string(); + + let ignore_file = |fname: &str| fname.ends_with(&output_filename); + let data = load_locales(&output_path, ignore_file); + + for m in messages.clone() { + if !m.locations.is_empty() { + for _l in &m.locations { + // TODO: write file and line as YAML comment + // Reason: serde_yaml not support write comments + // https://github.com/dtolnay/serde-yaml/issues/145 + } + } + + if let Some(trs) = data.get(locale) { + if trs.get(&m.key).is_some() { + continue; + } + } + + let value = m.key.split('.').last().unwrap_or_default(); + + trs.entry(m.key.clone()) + .or_insert_with(HashMap::new) + .insert(locale.to_string(), value.to_string()); + } + } + + trs +} + +fn write_file>(output: &P, filename: &str, data: &str) -> Result<()> { let output_file = std::path::Path::new(output.as_ref()).join(String::from(filename)); + let folder = output_file.parent().unwrap(); + + // Ensure create folder + if !folder.exists() { + std::fs::create_dir_all(folder).unwrap(); + } + let mut output = ::std::fs::File::create(&output_file) .unwrap_or_else(|_| panic!("Unable to create {} file", &output_file.display())); - writeln!(output, "{}", serde_yaml::to_string(&translations).unwrap()) - .expect("Write YAML file error"); + writeln!(output, "{}", data).expect("Write YAML file error"); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + fn assert_eq_json(left: &str, right: &str) { + let left: serde_json::Value = serde_json::from_str(left).unwrap(); + let right: serde_json::Value = serde_json::from_str(right).unwrap(); + assert_eq!(left, right); + } + + #[test] + fn test_convert_text() { + let mut trs = Translations::new(); + let format = "json"; + + let result = convert_text(&trs, format); + let expect = r#" + { + "_version": 2 + } + "#; + assert_eq_json(&result, &expect); + + trs.insert("hello".to_string(), { + let mut map = HashMap::new(); + map.insert("en".to_string(), "Hello".to_string()); + map.insert("zh".to_string(), "δ½ ε₯½".to_string()); + map + }); + + let result = convert_text(&trs, format); + let expect = r#" + { + "_version": 2, + "hello": { + "en": "Hello", + "zh": "δ½ ε₯½" + } + } + "#; + assert_eq_json(&result, &expect); + + let format = "yaml"; + let result = convert_text(&trs, format); + let expect = indoc! {r#" + _version: 2 + hello: + en: Hello + zh: δ½ ε₯½ + "#}; + assert_eq!(&result, &expect); + + let format = "toml"; + let result = convert_text(&trs, format); + let expect = indoc! {r#" + _version = 2 + + [hello] + en = "Hello" + zh = "δ½ ε₯½" + "#}; + assert_eq!(&result, &expect); + } +} diff --git a/crates/support/Cargo.toml b/crates/support/Cargo.toml index 5a0f2c0..31eb085 100644 --- a/crates/support/Cargo.toml +++ b/crates/support/Cargo.toml @@ -5,7 +5,7 @@ license = "MIT" name = "rust-i18n-support" readme = "../../README.md" repository = "https://github.com/longbridgeapp/rust-i18n" -version = "2.0.1" +version = "2.1.0" [dependencies] globwalk = "0.8.1" @@ -15,4 +15,6 @@ serde = "1" serde_json = "1" serde_yaml = "0.8" toml = "0.7.4" -normpath = "1.1.1" \ No newline at end of file +normpath = "1.1.1" +lazy_static = "1" +regex = "1" diff --git a/crates/support/src/lib.rs b/crates/support/src/lib.rs index afd83cc..c574b08 100644 --- a/crates/support/src/lib.rs +++ b/crates/support/src/lib.rs @@ -124,11 +124,126 @@ fn parse_file(content: &str, ext: &str, locale: &str) -> Result Ok(Translations::from([(locale.to_string(), v)])), + Ok(v) => match get_version(&v) { + 2 => { + if let Some(trs) = parse_file_v2("", &v) { + return Ok(trs); + } + + return Err("Invalid locale file format, please check the version field".into()); + } + _ => { + return Ok(parse_file_v1(locale, &v)); + } + }, Err(e) => Err(e), } } +/// Locale file format v1 +/// +/// For example: +/// ```yml +/// welcome: Welcome +/// foo: Foo bar +/// ``` +fn parse_file_v1(locale: &str, data: &serde_json::Value) -> Translations { + return Translations::from([(locale.to_string(), data.clone())]); +} + +/// Locale file format v2 +/// Iter all nested keys, if the value is not a object (Map), then convert into multiple locale translations +/// +/// If the final value is Map, then convert them and insert into trs +/// +/// For example (only support 1 level): +/// +/// ```yml +/// _version: 2 +/// welcome.first: +/// en: Welcome +/// zh-CN: 欒迎 +/// welcome1: +/// en: Welcome 1 +/// zh-CN: 欒迎 1 +/// ``` +/// +/// into +/// +/// ```yml +/// en.welcome.first: Welcome +/// zh-CN.welcome.first: 欒迎 +/// en.welcome1: Welcome 1 +/// zh-CN.welcome1: 欒迎 1 +/// ``` +fn parse_file_v2(key_prefix: &str, data: &serde_json::Value) -> Option { + let mut trs = Translations::new(); + + if let serde_json::Value::Object(messages) = data { + for (key, value) in messages { + if let serde_json::Value::Object(sub_messages) = value { + // If all values are string, then convert them into multiple locale translations + for (locale, text) in sub_messages { + // Ignore if the locale is not a locale + // e.g: + // en: Welcome + // zh-CN: 欒迎 + if text.is_string() { + let key = format_keys(&[&key_prefix, &key]); + let sub_trs = HashMap::from([(key, text.clone())]); + let sub_value = serde_json::to_value(&sub_trs).unwrap(); + + trs.entry(locale.clone()) + .and_modify(|old_value| merge_value(old_value, &sub_value)) + .or_insert(sub_value); + continue; + } + + if text.is_object() { + // Parse the nested keys + // If the value is object (Map), iter them and convert them and insert into trs + let key = format_keys(&[&key_prefix, &key]); + if let Some(sub_trs) = parse_file_v2(&key, &value) { + // println!("--------------- sub_trs:\n{:?}", sub_trs); + // Merge the sub_trs into trs + for (locale, sub_value) in sub_trs { + trs.entry(locale) + .and_modify(|old_value| merge_value(old_value, &sub_value)) + .or_insert(sub_value); + } + } + } + } + } + } + } + + if !trs.is_empty() { + return Some(trs); + } + + None +} + +/// Get `_version` from JSON root +/// If `_version` is not found, then return 1 as default. +fn get_version(data: &serde_json::Value) -> usize { + if let Some(version) = data.get("_version") { + return version.as_u64().unwrap_or(1) as usize; + } + + return 1; +} + +/// Join the keys with dot, if any key is empty, omit it. +fn format_keys(keys: &[&str]) -> String { + keys.iter() + .filter(|k| !k.is_empty()) + .map(|k| k.to_string()) + .collect::>() + .join(".") +} + fn flatten_keys(prefix: &str, trs: &Value) -> HashMap { let mut v = HashMap::::new(); let prefix = prefix.to_string(); @@ -228,4 +343,57 @@ mod tests { assert_eq!(trs["en"]["foo"], "Foo"); assert_eq!(trs["en"]["bar"], "Bar"); } + + #[test] + fn test_get_version() { + let json = serde_yaml::from_str::("_version: 2").unwrap(); + assert_eq!(super::get_version(&json), 2); + + let json = serde_yaml::from_str::("_version: 1").unwrap(); + assert_eq!(super::get_version(&json), 1); + + // Default fallback to 1 + let json = serde_yaml::from_str::("foo: Foo").unwrap(); + assert_eq!(super::get_version(&json), 1); + } + + #[test] + fn test_parse_file_in_json_with_nested_locale_texts() { + let content = r#"{ + "_version": 2, + "welcome": { + "en": "Welcome", + "zh-CN": "欒迎", + "zh-HK": "歑迎" + } + }"#; + + let trs = parse_file(content, "json", "filename").expect("Should ok"); + assert_eq!(trs["en"]["welcome"], "Welcome"); + assert_eq!(trs["zh-CN"]["welcome"], "欒迎"); + assert_eq!(trs["zh-HK"]["welcome"], "歑迎"); + } + + #[test] + fn test_parse_file_in_yaml_with_nested_locale_texts() { + let content = r#" + _version: 2 + welcome: + en: Welcome + zh-CN: 欒迎 + jp: γ‚ˆγ†γ“γ + welcome.sub: + en: Welcome 1 + zh-CN: 欒迎 1 + jp: γ‚ˆγ†γ“γ 1 + "#; + + let trs = parse_file(content, "yml", "filename").expect("Should ok"); + assert_eq!(trs["en"]["welcome"], "Welcome"); + assert_eq!(trs["zh-CN"]["welcome"], "欒迎"); + assert_eq!(trs["jp"]["welcome"], "γ‚ˆγ†γ“γ"); + assert_eq!(trs["en"]["welcome.sub"], "Welcome 1"); + assert_eq!(trs["zh-CN"]["welcome.sub"], "欒迎 1"); + assert_eq!(trs["jp"]["welcome.sub"], "γ‚ˆγ†γ“γ 1"); + } } diff --git a/src/main.rs b/src/main.rs index 849169d..951dac9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,11 +54,10 @@ fn main() -> Result<(), Error> { let output_path = Path::new(source_path).join(&cfg.load_path); - for available_locale in cfg.available_locales.into_iter() { - let result = generator::generate(&output_path, &available_locale, messages.clone()); - if result.is_err() { - has_error = true; - } + let result = + generator::generate(&output_path, &cfg.available_locales, messages.clone()); + if result.is_err() { + has_error = true; } if has_error { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index ed3629c..2ec5d00 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -96,7 +96,10 @@ mod tests { #[test] fn test_available_locales() { - assert_eq!(rust_i18n::available_locales!(), &["en", "pt", "zh-CN"]); + assert_eq!( + rust_i18n::available_locales!(), + &["en", "ja", "pt", "zh-CN"] + ); } #[test] @@ -212,11 +215,7 @@ mod tests { assert_eq!(t!("messages.hello", locale = locale), "Hello, %{name}!"); assert_eq!( - t!("messages.hello", name = name, locale = locale), - "Hello, Jason Lee!" - ); - assert_eq!( - t!("messages.hello", name = name, locale = locale), + t!("messages.hello", locale = locale, name = name), "Hello, Jason Lee!" ); } @@ -258,4 +257,34 @@ mod tests { fn test_extend_backend() { assert_eq!(t!("foo", locale = "pt"), "pt-fake.foo") } + + #[test] + fn test_nested_locale_texts() { + assert_eq!(t!("nested_locale_test", locale = "en"), "Hello test"); + assert_eq!(t!("nested_locale_test", locale = "zh-CN"), "δ½ ε₯½ test"); + assert_eq!(t!("nested_locale_test", locale = "ja"), "こんにけは test"); + + assert_eq!(t!("nested_locale_test.hello", locale = "en"), "Hello test2"); + assert_eq!( + t!("nested_locale_test.hello", locale = "zh-CN"), + "δ½ ε₯½ test2" + ); + assert_eq!( + t!("nested_locale_test.hello", locale = "ja"), + "こんにけは test2" + ); + + assert_eq!( + t!("nested_locale_test.hello.world", locale = "en"), + "Hello test3" + ); + assert_eq!( + t!("nested_locale_test.hello.world", locale = "zh-CN"), + "δ½ ε₯½ test3" + ); + assert_eq!( + t!("nested_locale_test.hello.world", locale = "ja"), + "こんにけは test3" + ); + } } diff --git a/tests/locales/v2.yml b/tests/locales/v2.yml new file mode 100644 index 0000000..f32d082 --- /dev/null +++ b/tests/locales/v2.yml @@ -0,0 +1,13 @@ +_version: 2 +nested_locale_test: + en: "Hello test" + ja: "こんにけは test" + zh-CN: "δ½ ε₯½ test" + hello: + en: "Hello test2" + ja: "こんにけは test2" + zh-CN: "δ½ ε₯½ test2" + world: + en: "Hello test3" + ja: "こんにけは test3" + zh-CN: "δ½ ε₯½ test3"