aboutsummaryrefslogtreecommitdiff
path: root/shakehand/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'shakehand/src/lib.rs')
-rw-r--r--shakehand/src/lib.rs145
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)
+}