187 lines
4.4 KiB
Rust
187 lines
4.4 KiB
Rust
use std::marker::PhantomData;
|
|
|
|
pub trait Language: Clone + Copy + PartialEq + Eq + Send + Sync + 'static {
|
|
fn code(&self) -> &'static str;
|
|
|
|
fn all() -> &'static [Self]
|
|
where
|
|
Self: Sized;
|
|
}
|
|
|
|
pub trait Translations<L: Language>:
|
|
Sized + Clone + Copy + PartialEq + Send + Sync + 'static
|
|
{
|
|
fn for_lang(lang: L) -> Self;
|
|
|
|
fn for_code(code: &str) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
for lang in L::all() {
|
|
if lang.code() == code {
|
|
return Some(Self::for_lang(*lang));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
pub struct TranslationsRef<T, L: Language> {
|
|
translations: T,
|
|
_lang: PhantomData<L>,
|
|
}
|
|
|
|
impl<T, L: Language> TranslationsRef<T, L> {
|
|
pub fn new(translations: T) -> Self {
|
|
Self {
|
|
translations,
|
|
_lang: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn get(&self) -> &T {
|
|
&self.translations
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! translations {
|
|
(
|
|
$(#[$struct_attr:meta])*
|
|
$vis:vis struct $name:ident<$lang_type:ty> {
|
|
$($field:ident: $ty:ty,)*
|
|
}
|
|
$($lang_variant:ident: { $($tfield:ident: $translation:expr,)* },)+
|
|
) => {
|
|
$(#[$struct_attr])*
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
$vis struct $name {
|
|
$(pub $field: $ty,)*
|
|
}
|
|
|
|
impl $crate::Translations<$lang_type> for $name {
|
|
fn for_lang(lang: $lang_type) -> Self {
|
|
match lang {
|
|
$(
|
|
<$lang_type>::$lang_variant => Self {
|
|
$($tfield: $translation,)*
|
|
},
|
|
)+
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! define_language {
|
|
(
|
|
$(#[$enum_attr:meta])*
|
|
$vis:vis enum $name:ident {
|
|
$(
|
|
$(#[$variant_attr:meta])*
|
|
$variant:ident = $code:expr,
|
|
)+
|
|
}
|
|
) => {
|
|
$(#[$enum_attr])*
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
|
|
$vis enum $name {
|
|
$(
|
|
$(#[$variant_attr])*
|
|
$variant,
|
|
)+
|
|
}
|
|
|
|
impl $crate::Language for $name {
|
|
fn code(&self) -> &'static str {
|
|
match self {
|
|
$(
|
|
Self::$variant => $code,
|
|
)+
|
|
}
|
|
}
|
|
|
|
fn all() -> &'static [Self] {
|
|
&[
|
|
$(
|
|
Self::$variant,
|
|
)+
|
|
]
|
|
}
|
|
}
|
|
|
|
impl $name {
|
|
pub fn from_code(code: &str) -> Option<Self> {
|
|
Self::all().iter().find(|l| l.code() == code).copied()
|
|
}
|
|
|
|
pub fn toggle(&self) -> Self {
|
|
let all = Self::all();
|
|
let idx = all.iter().position(|l| l == self).unwrap_or(0);
|
|
all[(idx + 1) % all.len()]
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
define_language! {
|
|
#[derive(Default)]
|
|
pub enum Lang {
|
|
#[default]
|
|
En = "en",
|
|
Fr = "fr",
|
|
}
|
|
}
|
|
|
|
translations! {
|
|
pub struct TestTexts<Lang> {
|
|
greeting: &'static str,
|
|
farewell: &'static str,
|
|
}
|
|
En: {
|
|
greeting: "Hello",
|
|
farewell: "Goodbye",
|
|
},
|
|
Fr: {
|
|
greeting: "Bonjour",
|
|
farewell: "Au revoir",
|
|
},
|
|
}
|
|
|
|
#[test]
|
|
fn test_language_enum() {
|
|
assert_eq!(Lang::En.code(), "en");
|
|
assert_eq!(Lang::Fr.code(), "fr");
|
|
assert!(Lang::from_code("en").is_some());
|
|
assert!(Lang::from_code("de").is_none());
|
|
assert_eq!(Lang::En.toggle(), Lang::Fr);
|
|
assert_eq!(Lang::Fr.toggle(), Lang::En);
|
|
}
|
|
|
|
#[test]
|
|
fn test_translations_for_lang() {
|
|
let en = TestTexts::for_lang(Lang::En);
|
|
assert_eq!(en.greeting, "Hello");
|
|
assert_eq!(en.farewell, "Goodbye");
|
|
|
|
let fr = TestTexts::for_lang(Lang::Fr);
|
|
assert_eq!(fr.greeting, "Bonjour");
|
|
assert_eq!(fr.farewell, "Au revoir");
|
|
}
|
|
|
|
#[test]
|
|
fn test_for_code() {
|
|
let texts = TestTexts::for_code("fr");
|
|
assert!(texts.is_some());
|
|
assert_eq!(texts.unwrap().greeting, "Bonjour");
|
|
|
|
let none = TestTexts::for_code("de");
|
|
assert!(none.is_none());
|
|
}
|
|
}
|