diff options
Diffstat (limited to 'shakehand/src/lib.rs')
| -rw-r--r-- | shakehand/src/lib.rs | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/shakehand/src/lib.rs b/shakehand/src/lib.rs new file mode 100644 index 0000000..419e088 --- /dev/null +++ b/shakehand/src/lib.rs @@ -0,0 +1,145 @@ +//! "Shake Hand!" +//! +//! > Let different languages shake hands with each other! 🤝 +//! +//! This is a **purely compile-time** Rust internationalization library. **All** localized strings are fully embedded into the binary at compile time, with only minimal runtime overhead. +//! +//! # How to Use? +//! +//! ## 1. Add Dependency +//! +//! Add the following to your `Cargo.toml`: +//! +//! ```toml +//! shakehand = "0.1" +//! ``` +//! +//! ## 2. Write Configuration Files +//! +//! Create a directory under your project to serve as the root for translations, e.g. `./locale/`. +//! Create any `toml` file inside this directory, e.g. `./locale/global.toml`, representing a translation file. +//! +//! In the file, group translations by language section. The key is the translation key, and the value is the translated text. +//! Text with parameters uses `%{parameter_name}` as placeholders, where the parameter name directly maps to the generated function's parameter name: +//! +//! ```toml +//! [en] +//! world = "world" +//! greeting = "Hello, %{someone}!" +//! +//! [zh_CN] +//! world = "世界" +//! greeting = "你好,%{someone}!" +//! ``` +//! +//! ## 3. Load Translations +//! +//! In your Rust code, use `shakehand::locale!` to hardcode the entire directory into a module: +//! +//! ```rust +//! pub mod translation { +//! shakehand::locale!("./test-locale", fallback = "en"); +//! } +//! ``` +//! +//! The generated module will contain: +//! - A `Languages` enum listing all languages +//! - A `set_lang` function for switching the current language +//! - A unit struct `Global` (named after `global.toml`), whose associated functions are the translations for each key +//! +//! ## 4. Call Translations +//! +//! Call translations just like ordinary functions, passing parameters by placeholder name: +//! +//! ```rust +//! use crate::translation::{Global, Languages, set_lang}; +//! +//! fn main() { +//! set_lang(Languages::en); +//! let greeting = Global::greeting("World"); +//! println!("{}", greeting); // Hello, World! +//! +//! set_lang(Languages::zh_CN); +//! let greeting = Global::greeting("世界"); +//! println!("{}", greeting); // 你好,世界! +//! } +//! +//! # pub mod translation { +//! # shakehand::locale!("./test-locale", fallback = "en"); +//! # } +//! ``` +//! +//! You can also pass the return value of another translation as a parameter, since each parameterless translation function returns a `&'static str`, completely allocation-free: +//! +//! ```rust +//! # use shakehand::locale; +//! # use a::Global; +//! # pub mod a { shakehand::locale!("./test-locale"); }; +//! # a::set_lang(a::Languages::en); +//! let greeting = Global::greeting(Global::world()); +//! ``` +//! +//! # Contributing +//! +//! Directly open a PR to the [repository](https://github.com/catilgrass/shakehand) and mention [@Weicao-CatilGrass](https://github.com/Weicao-CatilGrass). +//! +//! # License +//! +//! MIT or Apache 2.0 + +use std::path::Path; + +use proc_macro::TokenStream; +use syn::parse_macro_input; + +mod analyzer; +mod shakehand; + +/// `locale!` macro: Generate an i18n module from toml files at compile time +/// +/// # Usage +/// +/// ``` +/// pub mod my_i18n { +/// shakehand::locale!("./test-locale/", fallback = "en"); +/// } +/// ``` +/// +/// # Parameters +/// +/// - `path` - i18n directory path (relative to `Cargo.toml`) +/// - `fallback` - optional, default language (defaults to `"en"`) +#[proc_macro] +pub fn locale(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as analyzer::ShakehandInput); + + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()); + + let base_path = Path::new(&manifest_dir).join(&input.path); + let base_path = just_fmt::fmt_path::fmt_path(&base_path).unwrap_or_else(|_| base_path.clone()); + + let scanned_files = analyzer::scan_toml_files(&base_path); + + let mut parsed_files = Vec::new(); + let mut all_languages: std::collections::BTreeSet<String> = std::collections::BTreeSet::new(); + + for (mod_path, file_path) in &scanned_files { + if let Some(mut toml_file) = analyzer::parse_toml_file(file_path) { + toml_file.module_path = mod_path.clone(); + all_languages.extend(toml_file.all_languages.clone()); + parsed_files.push(toml_file); + } + } + + if parsed_files.is_empty() { + let lang_enum = + shakehand::generate_module(parsed_files, all_languages, input.fallback, &input.path); + return TokenStream::from(quote::quote! { + #lang_enum + }); + } + + let generated = + shakehand::generate_module(parsed_files, all_languages, input.fallback, &input.path); + TokenStream::from(generated) +} |
