From c38d117b5803c65ad57a4c4704ee0d897d9fb3cb Mon Sep 17 00:00:00 2001 From: copi143 Date: Sat, 17 Jan 2026 04:02:47 +0800 Subject: init --- derive/src/lib.rs | 957 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 957 insertions(+) create mode 100644 derive/src/lib.rs (limited to 'derive/src/lib.rs') diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..35c98ee --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,957 @@ +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use std::collections::HashMap; +use std::fs; +use std::path::Path; +use std::sync::LazyLock; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{Expr, LitStr, Token, parse_macro_input}; + +struct LangConfig { + name: String, + fallback: Option, +} + +struct Config { + path: String, + base: String, + langs: Vec, +} + +struct F16nInput { + fmt: LitStr, + args: Punctuated, +} + +struct WriteL10nInput { + target: Expr, + key: LitStr, +} + +struct WriteF16nInput { + target: Expr, + fmt: LitStr, + args: Punctuated, +} + +struct L10nAssertInput { + cond: Expr, + key: LitStr, +} + +struct L10nAssertEqInput { + left: Expr, + right: Expr, + key: LitStr, +} + +struct L10nAssertNeInput { + left: Expr, + right: Expr, + key: LitStr, +} + +struct F16nAssertInput { + cond: Expr, + fmt: LitStr, + args: Punctuated, +} + +struct F16nAssertEqInput { + left: Expr, + right: Expr, + fmt: LitStr, + args: Punctuated, +} + +struct F16nAssertNeInput { + left: Expr, + right: Expr, + fmt: LitStr, + args: Punctuated, +} + +impl Parse for F16nInput { + fn parse(input: ParseStream) -> syn::Result { + let fmt: LitStr = input.parse()?; + let args = if input.is_empty() { + Punctuated::new() + } else { + input.parse::()?; + Punctuated::parse_terminated(input)? + }; + Ok(F16nInput { fmt, args }) + } +} + +impl Parse for L10nAssertInput { + fn parse(input: ParseStream) -> syn::Result { + let cond: Expr = input.parse()?; + input.parse::()?; + let key: LitStr = input.parse()?; + Ok(L10nAssertInput { cond, key }) + } +} + +impl Parse for L10nAssertEqInput { + fn parse(input: ParseStream) -> syn::Result { + let left: Expr = input.parse()?; + input.parse::()?; + let right: Expr = input.parse()?; + input.parse::()?; + let key: LitStr = input.parse()?; + Ok(L10nAssertEqInput { left, right, key }) + } +} + +impl Parse for L10nAssertNeInput { + fn parse(input: ParseStream) -> syn::Result { + let left: Expr = input.parse()?; + input.parse::()?; + let right: Expr = input.parse()?; + input.parse::()?; + let key: LitStr = input.parse()?; + Ok(L10nAssertNeInput { left, right, key }) + } +} + +impl Parse for F16nAssertInput { + fn parse(input: ParseStream) -> syn::Result { + let cond: Expr = input.parse()?; + input.parse::()?; + let fmt: LitStr = input.parse()?; + let args = if input.is_empty() { + Punctuated::new() + } else { + input.parse::()?; + Punctuated::parse_terminated(input)? + }; + Ok(F16nAssertInput { cond, fmt, args }) + } +} + +impl Parse for F16nAssertEqInput { + fn parse(input: ParseStream) -> syn::Result { + let left: Expr = input.parse()?; + input.parse::()?; + let right: Expr = input.parse()?; + input.parse::()?; + let fmt: LitStr = input.parse()?; + let args = if input.is_empty() { + Punctuated::new() + } else { + input.parse::()?; + Punctuated::parse_terminated(input)? + }; + Ok(F16nAssertEqInput { + left, + right, + fmt, + args, + }) + } +} + +impl Parse for F16nAssertNeInput { + fn parse(input: ParseStream) -> syn::Result { + let left: Expr = input.parse()?; + input.parse::()?; + let right: Expr = input.parse()?; + input.parse::()?; + let fmt: LitStr = input.parse()?; + let args = if input.is_empty() { + Punctuated::new() + } else { + input.parse::()?; + Punctuated::parse_terminated(input)? + }; + Ok(F16nAssertNeInput { + left, + right, + fmt, + args, + }) + } +} + +impl Parse for WriteL10nInput { + fn parse(input: ParseStream) -> syn::Result { + let target: Expr = input.parse()?; + input.parse::()?; + let key: LitStr = input.parse()?; + Ok(WriteL10nInput { target, key }) + } +} + +impl Parse for WriteF16nInput { + fn parse(input: ParseStream) -> syn::Result { + let target: Expr = input.parse()?; + input.parse::()?; + let fmt: LitStr = input.parse()?; + let args = if input.is_empty() { + Punctuated::new() + } else { + input.parse::()?; + Punctuated::parse_terminated(input)? + }; + Ok(WriteF16nInput { target, fmt, args }) + } +} + +fn parse_metadata(cfg: &toml::Value) -> Option { + let path = cfg.get("path")?.as_str()?.to_string(); + let base = cfg.get("base")?.as_str()?.to_string(); + + let langs_value = cfg.get("langs")?; + let mut langs = Vec::new(); + + if let Some(array) = langs_value.as_array() { + for lang in array { + if let Some(name) = lang.as_str() { + langs.push(LangConfig { + name: name.to_string(), + fallback: None, + }); + } else if let Some(table) = lang.as_table() { + let name = table.get("name")?.as_str()?.to_string(); + let fallback = table + .get("fallback") + .and_then(|f| f.as_str()) + .map(|s| s.to_string()); + langs.push(LangConfig { name, fallback }); + } + } + } + + Some(Config { path, base, langs }) +} + +fn get_metadata() -> Config { + let manifest_path = Path::new(&*MANIFEST_DIR).join("Cargo.toml"); + let manifest_content = fs::read_to_string(manifest_path).unwrap(); + let manifest = toml::from_str::(&manifest_content).unwrap(); + + let cfg = manifest + .get("package") + .and_then(|pkg| pkg.get("metadata")) + .and_then(|meta| meta.get("static-l10n")) + .expect("static-l10n metadata not found in Cargo.toml"); + + parse_metadata(cfg).expect("Invalid static-l10n metadata format") +} + +static MANIFEST_DIR: LazyLock = + LazyLock::new(|| std::env::var("CARGO_MANIFEST_DIR").unwrap()); +static CONFIG: LazyLock = LazyLock::new(|| get_metadata()); +static TRANSLATION_PATH: LazyLock = LazyLock::new(|| { + let base_path = Path::new(&*MANIFEST_DIR).join(&CONFIG.path); + base_path.to_string_lossy().to_string() +}); +static TRANSLATIONS: LazyLock> = + LazyLock::new(|| parse_translations()); + +fn read_translate_files() -> Vec { + let file_paths = list_translate_file_paths(); + let mut files = Vec::new(); + for path in file_paths { + if let Ok(content) = fs::read_to_string(&path) { + files.push(content); + } + } + files +} + +fn list_translate_file_paths() -> Vec { + let mut files = Vec::new(); + + let base_path = Path::new(&*TRANSLATION_PATH).to_path_buf(); + if !base_path.exists() || !base_path.is_dir() { + return files; + } + + let mut stack = vec![base_path]; + while let Some(current_dir) = stack.pop() { + if let Ok(entries) = fs::read_dir(current_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + stack.push(path); + } else if path.is_file() { + files.push(path.to_string_lossy().to_string()); + } + } + } + } + + files.sort(); + files +} + +fn parse_translations() -> HashMap<(String, String), String> { + let files = read_translate_files(); + let mut translations = HashMap::new(); + + for content in files { + if let Ok(toml_value) = toml::from_str::(&content) { + if let Some(table) = toml_value.as_table() { + for (key, value) in table { + let _ = translations + .insert((key.clone(), CONFIG.base.clone()), key.clone()) + .is_none_or(|_| { + panic!( + "Duplicate translation for key '{}' and lang '{}'", + key, CONFIG.base + ) + }); + if let Some(subtable) = value.as_table() { + for (lang, translation) in subtable { + if let Some(translation_str) = translation.as_str() { + let _ = translations + .insert( + (key.clone(), lang.clone()), + translation_str.to_string(), + ) + .is_none_or(|_| { + panic!( + "Duplicate translation for key '{}' and lang '{}'", + key, lang + ) + }); + } + } + } + } + } + } + } + + translations +} + +fn get_translations(key: &str) -> HashMap> { + let mut result = HashMap::new(); + for lang in &CONFIG.langs { + let mut fallback = lang.fallback.clone(); + let mut lang = lang.name.clone(); + let mut translation = TRANSLATIONS.get(&(key.to_string(), lang.clone())).cloned(); + while translation.is_none() { + let Some(ref fb) = fallback else { + break; + }; + let fallbacked = CONFIG.langs.iter().find(|l| &l.name == fb).unwrap(); + fallback = fallbacked.fallback.clone(); + lang = fallbacked.name.clone(); + translation = TRANSLATIONS.get(&(key.to_string(), lang.clone())).cloned(); + } + result.insert(lang.clone(), translation); + } + result +} + +#[proc_macro] +pub fn debug_print_metadata(item: TokenStream) -> TokenStream { + if !item.is_empty() { + return quote! { + compile_error!("debug_print_metadata does not take any arguments"); + } + .into(); + } + let span = proc_macro::Span::call_site(); + println!("Manifest dir: {}", *MANIFEST_DIR); + println!("Debug info from file: {}", span.file()); + println!("static-l10n metadata:"); + println!(" path: {}", CONFIG.path); + println!(" langs:"); + for lang in &CONFIG.langs { + if let Some(fallback) = &lang.fallback { + println!(" - name: {}, fallback: {}", lang.name, fallback); + } else { + println!(" - name: {}", lang.name); + } + } + println!("Loaded translations:"); + for ((key, lang), translation) in &*TRANSLATIONS { + println!( + " - key: {}, lang: {}, translation: {}", + key, lang, translation + ); + } + quote! {}.into() +} + +#[proc_macro] +pub fn main(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + if !item.is_empty() { + return quote! { + compile_error!("main does not take any arguments"); + } + .into(); + } + let include_paths = list_translate_file_paths(); + let include_files = include_paths + .iter() + .map(|path| LitStr::new(path, proc_macro2::Span::call_site())) + .map(|path| { + quote! { + const _: &str = include_str!(#path); + } + }); + let default_lang = &CONFIG.base; + quote! { + #(#include_files)* + static __STATIC_L10N_LANG__: ::std::sync::Mutex<&'static str> = + ::std::sync::Mutex::new(#default_lang); + } + .into() +} + +#[proc_macro] +pub fn lang(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let lang = syn::parse_macro_input!(item as syn::LitStr).value(); + quote! { + { + *crate::__STATIC_L10N_LANG__.lock().unwrap() = #lang; + } + } + .into() +} + +#[proc_macro] +pub fn l10n(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_expr(input).into() +} + +// 格式化到 format 的数字翻译宏,支持 f16n!("xxx: {}", xxx) +#[proc_macro] +pub fn f16n(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_expr(input).into() +} + +#[proc_macro] +pub fn l10n_args(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_args_expr(input).into() +} + +#[proc_macro] +pub fn f16n_args(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_args_expr(input).into() +} + +#[proc_macro] +pub fn l10n_print(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_print_stmt(input, quote!(::std::print!)).into() +} + +#[proc_macro] +pub fn l10n_println(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_print_stmt(input, quote!(::std::println!)).into() +} + +#[proc_macro] +pub fn l10n_eprint(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_print_stmt(input, quote!(::std::eprint!)).into() +} + +#[proc_macro] +pub fn l10n_eprintln(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_print_stmt(input, quote!(::std::eprintln!)).into() +} + +#[proc_macro] +pub fn l10n_write(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as WriteL10nInput); + expand_l10n_write_stmt(input, quote!(::std::write!)).into() +} + +#[proc_macro] +pub fn l10n_writeln(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as WriteL10nInput); + expand_l10n_write_stmt(input, quote!(::std::writeln!)).into() +} + +#[proc_macro] +pub fn l10n_panic(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as LitStr); + expand_l10n_panic_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_assert(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertInput); + expand_l10n_assert_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_assert_eq(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertEqInput); + expand_l10n_assert_eq_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_assert_ne(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertNeInput); + expand_l10n_assert_ne_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_debug_assert(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertInput); + expand_l10n_debug_assert_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_debug_assert_eq(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertEqInput); + expand_l10n_debug_assert_eq_stmt(input).into() +} + +#[proc_macro] +pub fn l10n_debug_assert_ne(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as L10nAssertNeInput); + expand_l10n_debug_assert_ne_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_print(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_print_stmt(input, quote!(::std::print!)).into() +} + +#[proc_macro] +pub fn f16n_println(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_print_stmt(input, quote!(::std::println!)).into() +} + +#[proc_macro] +pub fn f16n_eprint(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_print_stmt(input, quote!(::std::eprint!)).into() +} + +#[proc_macro] +pub fn f16n_eprintln(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_print_stmt(input, quote!(::std::eprintln!)).into() +} + +#[proc_macro] +pub fn f16n_write(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as WriteF16nInput); + expand_f16n_write_stmt(input, quote!(::std::write!)).into() +} + +#[proc_macro] +pub fn f16n_writeln(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as WriteF16nInput); + expand_f16n_write_stmt(input, quote!(::std::writeln!)).into() +} + +#[proc_macro] +pub fn f16n_panic(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nInput); + expand_f16n_panic_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_assert(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertInput); + expand_f16n_assert_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_assert_eq(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertEqInput); + expand_f16n_assert_eq_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_assert_ne(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertNeInput); + expand_f16n_assert_ne_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_debug_assert(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertInput); + expand_f16n_debug_assert_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_debug_assert_eq(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertEqInput); + expand_f16n_debug_assert_eq_stmt(input).into() +} + +#[proc_macro] +pub fn f16n_debug_assert_ne(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as F16nAssertNeInput); + expand_f16n_debug_assert_ne_stmt(input).into() +} + +fn expand_l10n_expr(input: LitStr) -> proc_macro2::TokenStream { + let key = input.value(); + let span = input.span(); + let translations = build_l10n_arms(&key, span, |lang, translation, span| { + quote_spanned! {span=> + #lang => #translation + } + }); + expand_match_expr(span, translations) +} + +fn expand_l10n_args_expr(input: LitStr) -> proc_macro2::TokenStream { + let key = input.value(); + let span = input.span(); + let translations = build_l10n_arms(&key, span, |lang, translation, span| { + quote_spanned! {span=> + #lang => ::std::format_args!(#translation) + } + }); + expand_match_expr(span, translations) +} + +fn expand_l10n_print_stmt( + input: LitStr, + printer: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let key = input.value(); + let span = input.span(); + let translations = build_l10n_arms(&key, span, |lang, translation, span| { + quote_spanned! {span=> + #lang => { #printer(#translation); } + } + }); + expand_match_expr(span, translations) +} + +fn expand_l10n_write_stmt( + input: WriteL10nInput, + writer: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let key = input.key; + let target = input.target; + let key_value = key.value(); + let span = key.span(); + let translations = build_l10n_arms(&key_value, span, |lang, translation, span| { + quote_spanned! {span=> + #lang => { let _ = #writer(#target, #translation); } + } + }); + expand_match_expr(span, translations) +} + +fn expand_l10n_panic_stmt(input: LitStr) -> proc_macro2::TokenStream { + let key = input.value(); + let span = input.span(); + let translations = build_l10n_arms(&key, span, |lang, translation, span| { + quote_spanned! {span=> + #lang => ::std::panic!(#translation) + } + }); + expand_match_expr(span, translations) +} + +fn expand_l10n_assert_stmt(input: L10nAssertInput) -> proc_macro2::TokenStream { + let cond = input.cond; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::assert!(#cond, "{}", #message); + } +} + +fn expand_l10n_assert_eq_stmt(input: L10nAssertEqInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::assert_eq!(#left, #right, "{}", #message); + } +} + +fn expand_l10n_assert_ne_stmt(input: L10nAssertNeInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::assert_ne!(#left, #right, "{}", #message); + } +} + +fn expand_l10n_debug_assert_stmt(input: L10nAssertInput) -> proc_macro2::TokenStream { + let cond = input.cond; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::debug_assert!(#cond, "{}", #message); + } +} + +fn expand_l10n_debug_assert_eq_stmt(input: L10nAssertEqInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::debug_assert_eq!(#left, #right, "{}", #message); + } +} + +fn expand_l10n_debug_assert_ne_stmt(input: L10nAssertNeInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let key = input.key; + let span = key.span(); + let message = expand_l10n_expr(key); + quote_spanned! {span=> + ::std::debug_assert_ne!(#left, #right, "{}", #message); + } +} + +fn expand_f16n_expr(input: F16nInput) -> proc_macro2::TokenStream { + let fmt_str_lit = input.fmt; + let fmt_str = fmt_str_lit.value(); + let span = fmt_str_lit.span(); + let translations = build_f16n_arms( + &fmt_str, + span, + &input.args, + |lang, translation, span, args| { + quote_spanned! {span=> + #lang => format!(#translation, #(#args),*) + } + }, + ); + expand_match_expr(span, translations) +} + +fn expand_f16n_args_expr(input: F16nInput) -> proc_macro2::TokenStream { + let fmt_str_lit = input.fmt; + let fmt_str = fmt_str_lit.value(); + let span = fmt_str_lit.span(); + let translations = build_f16n_arms( + &fmt_str, + span, + &input.args, + |lang, translation, span, args| { + quote_spanned! {span=> + #lang => ::std::format_args!(#translation, #(#args),*) + } + }, + ); + expand_match_expr(span, translations) +} + +fn expand_f16n_print_stmt( + input: F16nInput, + printer: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let fmt_str_lit = input.fmt; + let fmt_str = fmt_str_lit.value(); + let span = fmt_str_lit.span(); + let translations = build_f16n_arms( + &fmt_str, + span, + &input.args, + |lang, translation, span, args| { + quote_spanned! {span=> + #lang => { #printer(#translation, #(#args),*); } + } + }, + ); + expand_match_expr(span, translations) +} + +fn expand_f16n_write_stmt( + input: WriteF16nInput, + writer: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let fmt_str_lit = input.fmt; + let fmt_str = fmt_str_lit.value(); + let target = input.target; + let span = fmt_str_lit.span(); + let translations = build_f16n_arms( + &fmt_str, + span, + &input.args, + |lang, translation, span, args| { + quote_spanned! {span=> + #lang => { let _ = #writer(#target, #translation, #(#args),*); } + } + }, + ); + expand_match_expr(span, translations) +} + +fn expand_f16n_panic_stmt(input: F16nInput) -> proc_macro2::TokenStream { + let fmt_str_lit = input.fmt; + let fmt_str = fmt_str_lit.value(); + let span = fmt_str_lit.span(); + let translations = build_f16n_arms( + &fmt_str, + span, + &input.args, + |lang, translation, span, args| { + quote_spanned! {span=> + #lang => ::std::panic!(#translation, #(#args),*) + } + }, + ); + expand_match_expr(span, translations) +} + +fn expand_f16n_assert_stmt(input: F16nAssertInput) -> proc_macro2::TokenStream { + let cond = input.cond; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::assert!(#cond, "{}", #message); + } +} + +fn expand_f16n_assert_eq_stmt(input: F16nAssertEqInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::assert_eq!(#left, #right, "{}", #message); + } +} + +fn expand_f16n_assert_ne_stmt(input: F16nAssertNeInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::assert_ne!(#left, #right, "{}", #message); + } +} + +fn expand_f16n_debug_assert_stmt(input: F16nAssertInput) -> proc_macro2::TokenStream { + let cond = input.cond; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::debug_assert!(#cond, "{}", #message); + } +} + +fn expand_f16n_debug_assert_eq_stmt(input: F16nAssertEqInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::debug_assert_eq!(#left, #right, "{}", #message); + } +} + +fn expand_f16n_debug_assert_ne_stmt(input: F16nAssertNeInput) -> proc_macro2::TokenStream { + let left = input.left; + let right = input.right; + let fmt = input.fmt; + let span = fmt.span(); + let message = expand_f16n_args_expr(F16nInput { + fmt, + args: input.args, + }); + quote_spanned! {span=> + ::std::debug_assert_ne!(#left, #right, "{}", #message); + } +} + +fn expand_match_expr( + span: proc_macro2::Span, + translations: Vec, +) -> proc_macro2::TokenStream { + quote_spanned! {span=> + match crate::__STATIC_L10N_LANG__.lock().unwrap().as_ref() { + #(#translations,)* + other => panic!("Unsupported language: {}", other), + } + } +} + +fn build_l10n_arms( + key: &str, + span: proc_macro2::Span, + mut make_arm: impl FnMut(&str, &str, proc_macro2::Span) -> proc_macro2::TokenStream, +) -> Vec { + let translations = get_translations(key); + let mut arms = Vec::with_capacity(translations.len()); + for (lang, translation) in translations { + if let Some(translation) = translation { + arms.push(make_arm(&lang, &translation, span)); + } else { + let error_msg = format!("Missing translation for key '{}' in lang '{}'", key, lang); + arms.push(quote_spanned! {span=> + #lang => compile_error!(#error_msg) + }); + } + } + arms +} + +fn build_f16n_arms( + fmt_str: &str, + span: proc_macro2::Span, + args: &Punctuated, + mut make_arm: impl FnMut(&str, &str, proc_macro2::Span, &[&Expr]) -> proc_macro2::TokenStream, +) -> Vec { + let translations = get_translations(fmt_str); + let args_vec: Vec<_> = args.iter().collect(); + let mut arms = Vec::with_capacity(translations.len()); + for (lang, translation) in translations { + if let Some(translation) = translation { + arms.push(make_arm(&lang, &translation, span, &args_vec)); + } else { + let error_msg = format!( + "Missing translation for key '{}' in lang '{}'", + fmt_str, lang + ); + arms.push(quote_spanned! {span=> + #lang => compile_error!(#error_msg) + }); + } + } + arms +} -- cgit