recipes/backend/src/translation.rs

277 lines
6.8 KiB
Rust

use std::{fs::File, sync::LazyLock};
use ron::de::from_reader;
use serde::Deserialize;
use strum::EnumCount;
use strum_macros::EnumCount;
use tracing::{event, Level};
use crate::consts;
#[derive(Debug, Clone, EnumCount, Deserialize)]
pub enum Sentence {
MainTitle = 0,
CreateNewRecipe,
UnpublishedRecipes,
UntitledRecipe,
Name,
EmailAddress,
Password,
SignOut,
Save,
NotLoggedIn,
DatabaseError,
// Sign in page.
SignInMenu,
SignInTitle,
SignInButton,
WrongEmailOrPassword,
// Sign up page.
SignUpMenu,
SignUpTitle,
SignUpButton,
SignUpEmailSent,
SignUpFollowEmailLink,
SignUpEmailValidationSuccess,
SignUpValidationExpired,
SignUpValidationErrorTryAgain,
ChooseAPassword,
ReEnterPassword,
AccountMustBeValidatedFirst,
InvalidEmail,
PasswordDontMatch,
InvalidPassword,
EmailAlreadyTaken,
UnableToSendEmail,
// Validation.
ValidationSuccessful,
ValidationExpired,
ValidationErrorTryToSignUpAgain,
ValidationError,
ValidationUserAlreadyExists,
// Reset password page.
LostPassword,
AskResetButton,
AskResetAlreadyLoggedInError,
AskResetEmailAlreadyResetError,
AskResetFollowEmailLink,
AskResetEmailSent,
AskResetTokenMissing,
AskResetTokenExpired,
PasswordReset,
EmailUnknown,
UnableToSendResetEmail,
// Profile
ProfileTitle,
ProfileEmail,
ProfileNewPassword,
ProfileFollowEmailLink,
ProfileEmailSent,
ProfileSaved,
// Recipe.
RecipeNotAllowedToEdit,
RecipeNotAllowedToView,
RecipeNotFound,
RecipeTitle,
RecipeDescription,
RecipeServings,
RecipeEstimatedTime,
RecipeDifficulty,
RecipeDifficultyEasy,
RecipeDifficultyMedium,
RecipeDifficultyHard,
RecipeTags,
RecipeLanguage,
RecipeIsPublished,
RecipeDelete,
RecipeAddAGroup,
RecipeRemoveGroup,
RecipeGroupName,
RecipeGroupComment,
RecipeAddAStep,
RecipeRemoveStep,
RecipeStepAction,
RecipeAddAnIngredient,
RecipeRemoveIngredient,
RecipeIngredientName,
RecipeIngredientQuantity,
RecipeIngredientUnit,
RecipeIngredientComment,
}
const DEFAULT_LANGUAGE_CODE: &str = "en";
#[derive(Clone)]
pub struct Tr {
lang: &'static Language,
}
impl Tr {
pub fn new(code: &str) -> Self {
Self {
lang: get_language_translation(code),
}
}
pub fn t(&self, sentence: Sentence) -> String {
//&'static str {
self.lang.get(sentence).to_string()
// match self.lang.translation.get(&sentence) {
// Some(str) => str.clone(),
// None => format!(
// "Translation missing, lang: {}/{}, element: {:?}",
// self.lang.name, self.lang.code, sentence
// ),
// }
}
pub fn tp(&self, sentence: Sentence, params: &[Box<dyn ToString + Send>]) -> String {
// match self.lang.translation.get(&sentence) {
// Some(str) => {
// let mut result = str.clone();
// for p in params {
// result = result.replacen("{}", &p.to_string(), 1);
// }
// result
// }
// None => format!(
// "Translation missing, lang: {}/{}, element: {:?}",
// self.lang.name, self.lang.code, sentence
// ),
// }
let text = self.lang.get(sentence);
let mut result = text.to_string();
for p in params {
result = result.replacen("{}", &p.to_string(), 1);
}
result
}
pub fn current_lang_code(&self) -> &str {
&self.lang.code
}
}
// #[macro_export]
// macro_rules! t {
// ($self:expr, $str:expr) => {
// $self.t($str)
// };
// ($self:expr, $str:expr, $( $x:expr ),+ ) => {
// {
// let mut result = $self.t($str);
// $( result = result.replacen("{}", &$x.to_string(), 1); )+
// result
// }
// };
// }
#[derive(Debug, Deserialize)]
struct StoredLanguage {
code: String,
name: String,
translation: Vec<(Sentence, String)>,
}
#[derive(Debug)]
struct Language {
code: String,
name: String,
translation: Vec<String>,
}
impl Language {
pub fn from_stored_language(stored_language: StoredLanguage) -> Self {
println!("!!!!!!!!!!!! {:?}", &stored_language.code);
Self {
code: stored_language.code,
name: stored_language.name,
translation: {
let mut translation = vec![String::new(); Sentence::COUNT];
for (sentence, text) in stored_language.translation {
translation[sentence as usize] = text;
}
translation
},
}
}
pub fn get(&'static self, sentence: Sentence) -> &'static str {
let text: &str = self
.translation
.get(sentence.clone() as usize)
.unwrap()
.as_ref();
if text.is_empty() && self.code != DEFAULT_LANGUAGE_CODE {
return get_language_translation(DEFAULT_LANGUAGE_CODE).get(sentence);
}
text
}
}
pub fn available_languages() -> Vec<(&'static str, &'static str)> {
TRANSLATIONS
.iter()
.map(|tr| (tr.code.as_ref(), tr.name.as_ref()))
.collect()
}
pub fn available_codes() -> Vec<&'static str> {
TRANSLATIONS.iter().map(|tr| tr.code.as_ref()).collect()
}
fn get_language_translation(code: &str) -> &'static Language {
for lang in TRANSLATIONS.iter() {
if lang.code == code {
return lang;
}
}
event!(
Level::WARN,
"Unable to find translation for language {}",
code
);
if code != DEFAULT_LANGUAGE_CODE {
get_language_translation(DEFAULT_LANGUAGE_CODE)
} else {
// 'DEFAULT_LANGUAGE_CODE' must exist.
panic!("Unable to find language {}", code);
}
}
static TRANSLATIONS: LazyLock<Vec<Language>> =
LazyLock::new(|| match File::open(consts::TRANSLATION_FILE) {
Ok(file) => {
let stored_languages: Vec<StoredLanguage> = from_reader(file).unwrap_or_else(|error| {
{
panic!(
"Failed to read translation file {}: {}",
consts::TRANSLATION_FILE,
error
)
}
});
stored_languages
.into_iter()
.map(Language::from_stored_language)
.collect()
}
Err(error) => {
panic!(
"Failed to open translation file {}: {}",
consts::TRANSLATION_FILE,
error
)
}
});