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, utils}; #[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, } pub const DEFAULT_LANGUAGE_CODE: &str = "en"; pub const PLACEHOLDER_SUBSTITUTE: &str = "{}"; #[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) -> &'static str { self.lang.get(sentence) } pub fn tp(&self, sentence: Sentence, params: &[Box]) -> String { let text = self.lang.get(sentence); let params_as_string: Vec = params.iter().map(|p| p.to_string()).collect(); utils::substitute( text, PLACEHOLDER_SUBSTITUTE, ¶ms_as_string .iter() .map(AsRef::as_ref) .collect::>(), ) } 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, } impl Language { pub fn from_stored_language(stored_language: StoredLanguage) -> Self { 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> = LazyLock::new(|| match File::open(consts::TRANSLATION_FILE) { Ok(file) => { let stored_languages: Vec = 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 ) } });