Files
harmony/harmony_i18n/src/lib.rs

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());
}
}