dioxus_translate_macro/
lib.rs1extern crate proc_macro;
2
3use std::cell::RefCell;
4use std::rc::Rc;
5
6use proc_macro::TokenStream;
7use quote::{ToTokens, quote};
8use syn::parse::{Parse, ParseStream};
9use syn::{DeriveInput, Fields, Ident, Lit, LitStr, Meta, Token, braced, parse_macro_input};
10
11#[proc_macro]
12pub fn translate(input: TokenStream) -> TokenStream {
13 let input = parse_macro_input!(input as TranslateInput);
14
15 let struct_name = input.struct_name;
16 let mut fields = Vec::new();
17 let mut ko_impl = Vec::new();
18 let mut en_impl = Vec::new();
19
20 for field in input.fields {
21 let field_name = field.field_name;
22
23 fields.push(quote! {
24 pub #field_name: &'static str,
25 });
26
27 let mut ko_value = None;
28 let mut en_value = None;
29
30 for translation in field.translations {
31 if translation.lang == "ko" {
32 ko_value = Some(translation.value);
33 } else if translation.lang == "en" {
34 en_value = Some(translation.value);
35 }
36 }
37
38 ko_impl.push(quote! {
39 #field_name: #ko_value,
40 });
41
42 en_impl.push(quote! {
43 #field_name: #en_value,
44 });
45 }
46
47 let en = quote! {
48 fn en() -> Self {
49 Self {
50 #(#en_impl)*
51 }
52 }
53 };
54
55 #[allow(unused_variables)]
56 let ko = quote! {};
57
58 #[cfg(feature = "ko")]
59 let ko = quote! {
60 fn ko() -> Self {
61 Self {
62 #(#ko_impl)*
63 }
64 }
65
66 };
67
68 #[allow(unused_variables)]
69 let new_ko = quote! {};
70
71 #[cfg(feature = "ko")]
72 let new_ko = quote! {
73 dioxus_translate::Language::Ko => Self::ko(),
74 };
75
76 let expanded = quote! {
77 #[derive(Debug, Clone, PartialEq)]
78 pub struct #struct_name {
79 #(#fields)*
80 }
81
82 impl #struct_name {
83 pub fn new(lang: &dioxus_translate::Language) -> Self {
84 match lang {
85 dioxus_translate::Language::En => Self::en(),
86 #new_ko
87 }
88 }
89 }
90
91 impl dioxus_translate::Translator for #struct_name {
92 #en
93
94 #ko
95 }
96 };
97
98 TokenStream::from(expanded)
99}
100
101struct TranslateInput {
103 struct_name: Ident,
104 fields: Vec<FieldTranslations>,
105}
106
107struct FieldTranslations {
108 field_name: Ident,
109 translations: Vec<LanguageTranslation>,
110}
111
112struct LanguageTranslation {
113 lang: Ident,
114 value: String,
115}
116
117impl Parse for TranslateInput {
118 fn parse(input: ParseStream) -> syn::Result<Self> {
119 let struct_name: Ident = input.parse()?;
121 input.parse::<Token![;]>()?;
122
123 let mut fields = Vec::new();
124
125 while !input.is_empty() {
127 let field_name: Ident = input.parse()?;
128 input.parse::<Token![:]>()?;
129 let content;
130 braced!(content in input);
131
132 let mut translations = Vec::new();
133
134 while !content.is_empty() {
135 let lang: Ident = content.parse()?;
136 content.parse::<Token![:]>()?;
137 let value: Lit = content.parse()?;
138 content.parse::<Token![,]>().ok(); if let Lit::Str(lit_str) = value {
140 translations.push(LanguageTranslation {
141 lang,
142 value: lit_str.value(),
143 });
144 }
145 }
146
147 fields.push(FieldTranslations {
148 field_name,
149 translations,
150 });
151
152 input.parse::<Token![,]>().ok(); }
154
155 Ok(TranslateInput {
156 struct_name,
157 fields,
158 })
159 }
160}
161
162#[proc_macro_derive(Translate, attributes(translate))]
178pub fn translate_derive(input: TokenStream) -> TokenStream {
179 let ast = parse_macro_input!(input as DeriveInput);
180 let enum_name = ast.ident;
181
182 let variants = match ast.data {
184 syn::Data::Enum(ref data_enum) => &data_enum.variants,
185 _ => {
186 return syn::Error::new_spanned(enum_name, "Translate can only be derived for enums")
187 .to_compile_error()
188 .into();
189 }
190 };
191
192 let mut en_arms = Vec::new();
193 #[cfg(feature = "ko")]
194 let mut ko_arms = Vec::new();
195 let mut idents: Vec<Ident> = Vec::new();
199 let mut unit_variants = Vec::new();
200
201 for variant in variants {
202 let mut field_names = vec![];
203 let mut tuple_len = 0;
204
205 match variant.fields {
206 Fields::Unit => {
207 idents.push(variant.ident.clone());
208 unit_variants.push(variant.ident.clone());
209 }
210 Fields::Named(ref f) => {
211 idents.push(variant.ident.clone());
212 for field in f.named.iter() {
213 field_names.push(field.ident.clone().unwrap());
214 }
215 }
216 Fields::Unnamed(ref f) => {
217 idents.push(variant.ident.clone());
218 tuple_len = f.unnamed.len();
219 }
220 }
221 let variant_ident = &variant.ident;
222 let default_str = variant_ident.to_string();
223 let en_translation = Rc::new(RefCell::new(default_str.clone()));
224 #[cfg(feature = "ko")]
225 let ko_translation = Rc::new(RefCell::new(default_str.clone()));
226
227 let mut is_from = false;
230
231 for attr in &variant.attrs {
233 if let Meta::List(ref meta_list) = attr.meta {
234 if meta_list.path.is_ident("translate") {
235 let en = Rc::clone(&en_translation);
236 #[cfg(feature = "ko")]
237 let ko = Rc::clone(&ko_translation);
238
239 let is_from_ref = Rc::new(RefCell::new(false));
240 let is_from_clone = Rc::clone(&is_from_ref);
241
242 let _ = meta_list.parse_nested_meta(move |nv| {
243 if nv.path.is_ident("en") {
244 let s: LitStr = nv.value()?.parse()?;
245 *en.borrow_mut() = s.value();
246 }
247
248 #[cfg(feature = "ko")]
249 if nv.path.is_ident("ko") {
250 let s: LitStr = nv.value()?.parse()?;
251 *ko.borrow_mut() = s.value();
252 }
253
254 if nv.path.is_ident("from") {
255 *is_from_clone.borrow_mut() = true;
256 }
257
258 Ok(())
259 });
260
261 if *is_from_ref.borrow() {
262 is_from = true;
263 }
264 }
265 }
266 }
267
268 let en_str = syn::LitStr::new(&en_translation.borrow(), proc_macro2::Span::call_site());
269 #[cfg(feature = "ko")]
270 let ko_str = syn::LitStr::new(&ko_translation.borrow(), proc_macro2::Span::call_site());
271 let _lower_name = syn::LitStr::new(
272 &variant_ident.to_string().to_lowercase(),
273 proc_macro2::Span::call_site(),
274 );
275
276 if is_from && tuple_len == 1 {
279 let arm_name = quote! {
280 #enum_name::#variant_ident(inner)
281 };
282 en_arms.push(quote! {
283 #arm_name => inner.translate(lang),
284 });
285
286 #[cfg(feature = "ko")]
287 {
288 ko_arms.push(quote! {
289 #arm_name => inner.translate(lang),
290 });
291 }
292 continue;
293 }
294
295 let arm_name = if field_names.len() > 0 {
296 quote! {
297 #enum_name::#variant_ident { .. }
298 }
299 } else if tuple_len > 0 {
300 quote! {
301 #enum_name::#variant_ident(..)
302 }
303 } else {
304 quote! {
305 #enum_name::#variant_ident
306 }
307 };
308
309 en_arms.push(quote! {
310 #arm_name => #en_str,
311 });
312
313 if let Some((_, expr)) = &variant.discriminant {
314 let _value = LitStr::new(
315 expr.to_token_stream().to_string().as_str(),
316 proc_macro2::Span::call_site(),
317 );
318 }
319
320 #[cfg(feature = "ko")]
321 {
322 ko_arms.push(quote! {
323 #arm_name => #ko_str,
324 });
325 }
326 }
327
328 #[cfg(feature = "ko")]
329 let ko_arm = quote! {
330 dioxus_translate::Language::Ko => match self {
331 #(#ko_arms)*
332 },
333 };
334 #[cfg(not(feature = "ko"))]
335 let ko_arm = quote! {};
336
337 let r#gen = quote! {
339 impl dioxus_translate::Translate for #enum_name {
340 fn translate(&self, lang: &dioxus_translate::Language) -> &'static str {
341 match lang {
342 dioxus_translate::Language::En => match self {
343 #(#en_arms)*
344 },
345 #ko_arm
346 }
347 }
348 }
349
350 impl #enum_name {
351 pub const VARIANTS: &'static [Self] = &[ #(#enum_name::#unit_variants,)* ];
352 pub fn variants(lang: &dioxus_translate::Language) -> Vec<String> {
353 Self::VARIANTS.iter().map(|v| v.translate(&lang).to_string()).collect::<Vec<_>>()
354 }
355 }
356
357 };
377
378 r#gen.into()
379}