Avoid to use an hash map in translations
This commit is contained in:
parent
03ebbb74fa
commit
91ab379718
10 changed files with 334 additions and 237 deletions
|
|
@ -23,7 +23,6 @@ ron = "0.8"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
itertools = "0.14"
|
||||
rustc-hash = "2.1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] }
|
||||
|
|
@ -34,6 +33,8 @@ rinja_axum = "0.3"
|
|||
argon2 = { version = "0.5", features = ["default", "std"] }
|
||||
rand_core = { version = "0.6", features = ["std"] }
|
||||
rand = "0.8"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
|
||||
lettre = { version = "0.11", default-features = false, features = [
|
||||
"smtp-transport",
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ body {
|
|||
font-size: 0.5em;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use rinja_axum::Template;
|
|||
|
||||
use crate::{
|
||||
data::model,
|
||||
translation::{Sentence, Tr},
|
||||
translation::{self, Sentence, Tr},
|
||||
};
|
||||
|
||||
pub struct Recipes {
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ async fn translation(
|
|||
let language = if let Some(user) = user {
|
||||
user.lang
|
||||
} else {
|
||||
let available_codes = Tr::available_codes();
|
||||
let available_codes = translation::available_codes();
|
||||
let jar = CookieJar::from_headers(req.headers());
|
||||
match jar.get(consts::COOKIE_LANG_NAME) {
|
||||
Some(lang) if available_codes.contains(&lang.value()) => lang.value().to_string(),
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ pub async fn set_language(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeLanguage>,
|
||||
) -> Result<StatusCode> {
|
||||
if !crate::translation::Tr::available_codes()
|
||||
if !crate::translation::available_codes()
|
||||
.iter()
|
||||
.any(|&l| l == ron.lang)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
use std::{fs::File, sync::LazyLock};
|
||||
|
||||
use ron::de::from_reader;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumCount;
|
||||
use strum_macros::EnumCount;
|
||||
use tracing::{event, Level};
|
||||
|
||||
use crate::consts;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Clone)]
|
||||
#[derive(Debug, Clone, EnumCount, Deserialize)]
|
||||
pub enum Sentence {
|
||||
MainTitle,
|
||||
MainTitle = 0,
|
||||
CreateNewRecipe,
|
||||
UnpublishedRecipes,
|
||||
UntitledRecipe,
|
||||
|
|
@ -107,6 +108,8 @@ pub enum Sentence {
|
|||
RecipeIngredientComment,
|
||||
}
|
||||
|
||||
const DEFAULT_LANGUAGE_CODE: &str = "en";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tr {
|
||||
lang: &'static Language,
|
||||
|
|
@ -114,61 +117,48 @@ pub struct Tr {
|
|||
|
||||
impl Tr {
|
||||
pub fn new(code: &str) -> Self {
|
||||
for lang in TRANSLATIONS.iter() {
|
||||
if lang.code == code {
|
||||
return Self { lang };
|
||||
}
|
||||
Self {
|
||||
lang: get_language_translation(code),
|
||||
}
|
||||
|
||||
event!(
|
||||
Level::WARN,
|
||||
"Unable to find translation for language {}",
|
||||
code
|
||||
);
|
||||
|
||||
Tr::new("en")
|
||||
}
|
||||
|
||||
pub fn t(&self, sentence: Sentence) -> String {
|
||||
match self.lang.translation.get(&sentence) {
|
||||
Some(str) => str.clone(),
|
||||
None => format!(
|
||||
"Translation missing, lang: {}/{}, element: {:?}",
|
||||
self.lang.name, self.lang.code, sentence
|
||||
),
|
||||
}
|
||||
//&'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
|
||||
),
|
||||
// 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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// #[macro_export]
|
||||
|
|
@ -185,22 +175,98 @@ impl Tr {
|
|||
// };
|
||||
// }
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StoredLanguage {
|
||||
code: String,
|
||||
name: String,
|
||||
translation: Vec<(Sentence, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Language {
|
||||
code: String,
|
||||
name: String,
|
||||
translation: FxHashMap<Sentence, 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) => from_reader(file).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Failed to read translation file {}: {}",
|
||||
consts::TRANSLATION_FILE,
|
||||
error
|
||||
)
|
||||
}),
|
||||
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 {}: {}",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{% endmatch %}
|
||||
|
||||
<select id="select-website-language">
|
||||
{% for lang in Tr::available_languages() %}
|
||||
{% for lang in translation::available_languages() %}
|
||||
<option value="{{ lang.0 }}"
|
||||
{%+ if tr.current_lang_code() == lang.0 %}
|
||||
selected
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
|
||||
<label for="select-language">{{ tr.t(Sentence::RecipeLanguage) }}</label>
|
||||
<select id="select-language">
|
||||
{% for lang in Tr::available_languages() %}
|
||||
{% for lang in translation::available_languages() %}
|
||||
<option value="{{ lang.0 }}"
|
||||
{%+ if recipe.lang == lang.0 %}
|
||||
selected
|
||||
|
|
@ -86,6 +86,8 @@
|
|||
|
||||
<div id="hidden-templates">
|
||||
<div class="group">
|
||||
<div class="drag-handle"></div>
|
||||
|
||||
<label for="input-group-name">{{ tr.t(Sentence::RecipeGroupName) }}</label>
|
||||
<input class="input-group-name" type="text" />
|
||||
|
||||
|
|
@ -100,6 +102,8 @@
|
|||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="drag-handle"></div>
|
||||
|
||||
<label for="text-area-step-action">{{ tr.t(Sentence::RecipeStepAction) }}</label>
|
||||
<textarea class="text-area-step-action"></textarea>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,193 +2,193 @@
|
|||
(
|
||||
code: "en",
|
||||
name: "English",
|
||||
translation: {
|
||||
MainTitle: "Cooking Recipes",
|
||||
CreateNewRecipe: "Create a new recipe",
|
||||
UnpublishedRecipes: "Unpublished recipes",
|
||||
UntitledRecipe: "Untitled recipe",
|
||||
translation: [
|
||||
(MainTitle, "Cooking Recipes"),
|
||||
(CreateNewRecipe, "Create a new recipe"),
|
||||
(UnpublishedRecipes, "Unpublished recipes"),
|
||||
(UntitledRecipe, "Untitled recipe"),
|
||||
|
||||
Name: "Name",
|
||||
EmailAddress: "Email address",
|
||||
Password: "Password",
|
||||
(Name, "Name"),
|
||||
(EmailAddress, "Email address"),
|
||||
(Password, "Password"),
|
||||
|
||||
SignOut: "Sign out",
|
||||
Save: "Save",
|
||||
NotLoggedIn: "No logged in",
|
||||
(SignOut, "Sign out"),
|
||||
(Save, "Save"),
|
||||
(NotLoggedIn, "No logged in"),
|
||||
|
||||
DatabaseError: "Database error",
|
||||
(DatabaseError, "Database error"),
|
||||
|
||||
SignInMenu: "Sign in",
|
||||
SignInTitle: "Sign in",
|
||||
SignInButton: "Sign in",
|
||||
WrongEmailOrPassword: "Wrong email or password",
|
||||
AccountMustBeValidatedFirst: "This account must be validated first",
|
||||
InvalidEmail: "Invalid email",
|
||||
PasswordDontMatch: "Passwords don't match",
|
||||
InvalidPassword: "Password must have at least {} characters",
|
||||
EmailAlreadyTaken: "This email is not available",
|
||||
UnableToSendEmail: "Unable to send the validation email",
|
||||
(SignInMenu, "Sign in"),
|
||||
(SignInTitle, "Sign in"),
|
||||
(SignInButton, "Sign in"),
|
||||
(WrongEmailOrPassword, "Wrong email or password"),
|
||||
(AccountMustBeValidatedFirst, "This account must be validated first"),
|
||||
(InvalidEmail, "Invalid email"),
|
||||
(PasswordDontMatch, "Passwords don't match"),
|
||||
(InvalidPassword, "Password must have at least {} characters"),
|
||||
(EmailAlreadyTaken, "This email is not available"),
|
||||
(UnableToSendEmail, "Unable to send the validation email"),
|
||||
|
||||
ValidationSuccessful: "Email validation successful",
|
||||
ValidationExpired: "The validation has expired. Try to sign up again with the same email",
|
||||
ValidationErrorTryToSignUpAgain: "Validation error. Try to sign up again with the same email",
|
||||
ValidationError: "Validation error",
|
||||
ValidationUserAlreadyExists: "User already exists",
|
||||
(ValidationSuccessful, "Email validation successful"),
|
||||
(ValidationExpired, "The validation has expired. Try to sign up again with the same email"),
|
||||
(ValidationErrorTryToSignUpAgain, "Validation error. Try to sign up again with the same email"),
|
||||
(ValidationError, "Validation error"),
|
||||
(ValidationUserAlreadyExists, "User already exists"),
|
||||
|
||||
SignUpMenu: "Sign up",
|
||||
SignUpTitle: "Sign up",
|
||||
SignUpButton: "Sign up",
|
||||
SignUpEmailSent: "An email has been sent, follow the link to validate your account",
|
||||
SignUpFollowEmailLink: "Follow this link to confirm your inscription: {}",
|
||||
SignUpEmailValidationSuccess: "Email validation successful, your account has been created",
|
||||
SignUpValidationExpired: "The validation has expired. Try to sign up again",
|
||||
SignUpValidationErrorTryAgain: "Validation error. Try to sign up again",
|
||||
ChooseAPassword: "Choose a password (minimum {} characters)",
|
||||
ReEnterPassword: "Re-enter password",
|
||||
(SignUpMenu, "Sign up"),
|
||||
(SignUpTitle, "Sign up"),
|
||||
(SignUpButton, "Sign up"),
|
||||
(SignUpEmailSent, "An email has been sent), follow the link to validate your account"),
|
||||
(SignUpFollowEmailLink, "Follow this link to confirm your inscription, {}"),
|
||||
(SignUpEmailValidationSuccess, "Email validation successful), your account has been created"),
|
||||
(SignUpValidationExpired, "The validation has expired. Try to sign up again"),
|
||||
(SignUpValidationErrorTryAgain, "Validation error. Try to sign up again"),
|
||||
(ChooseAPassword, "Choose a password (minimum {} characters)"),
|
||||
(ReEnterPassword, "Re-enter password"),
|
||||
|
||||
LostPassword: "Lost password",
|
||||
AskResetButton: "Ask reset",
|
||||
AskResetAlreadyLoggedInError: "Can't ask to reset password when already logged in",
|
||||
AskResetEmailAlreadyResetError: "The password has already been reset for this email",
|
||||
AskResetFollowEmailLink: "Follow this link to reset your password: {}",
|
||||
AskResetEmailSent: "An email has been sent, follow the link to reset your password",
|
||||
AskResetTokenMissing: "Reset token missing",
|
||||
AskResetTokenExpired: "Token expired, try to reset password again",
|
||||
PasswordReset: "Your password has been reset",
|
||||
EmailUnknown: "Email unknown",
|
||||
UnableToSendResetEmail: "Unable to send the reset password email",
|
||||
(LostPassword, "Lost password"),
|
||||
(AskResetButton, "Ask reset"),
|
||||
(AskResetAlreadyLoggedInError, "Can't ask to reset password when already logged in"),
|
||||
(AskResetEmailAlreadyResetError, "The password has already been reset for this email"),
|
||||
(AskResetFollowEmailLink, "Follow this link to reset your password, {}"),
|
||||
(AskResetEmailSent, "An email has been sent), follow the link to reset your password"),
|
||||
(AskResetTokenMissing, "Reset token missing"),
|
||||
(AskResetTokenExpired, "Token expired), try to reset password again"),
|
||||
(PasswordReset, "Your password has been reset"),
|
||||
(EmailUnknown, "Email unknown"),
|
||||
(UnableToSendResetEmail, "Unable to send the reset password email"),
|
||||
|
||||
ProfileTitle: "Profile",
|
||||
ProfileEmail: "Email (need to be revalidated if changed)",
|
||||
ProfileNewPassword: "New password (minimum {} characters)",
|
||||
ProfileFollowEmailLink: "Follow this link to validate this email address: {}",
|
||||
ProfileEmailSent: "An email has been sent, follow the link to validate your new email",
|
||||
ProfileSaved: "Profile saved",
|
||||
(ProfileTitle, "Profile"),
|
||||
(ProfileEmail, "Email (need to be revalidated if changed)"),
|
||||
(ProfileNewPassword, "New password (minimum {} characters)"),
|
||||
(ProfileFollowEmailLink, "Follow this link to validate this email address, {}"),
|
||||
(ProfileEmailSent, "An email has been sent), follow the link to validate your new email"),
|
||||
(ProfileSaved, "Profile saved"),
|
||||
|
||||
RecipeNotAllowedToEdit: "Not allowed to edit this recipe",
|
||||
RecipeNotAllowedToView: "Not allowed the view the recipe {}",
|
||||
RecipeNotFound: "Recipe not found",
|
||||
RecipeTitle : "Title",
|
||||
RecipeDescription : "Description",
|
||||
RecipeServings : "Servings",
|
||||
RecipeEstimatedTime : "Estimated time [min]",
|
||||
RecipeDifficulty : "Difficulty",
|
||||
RecipeDifficultyEasy : "Easy",
|
||||
RecipeDifficultyMedium : "Medium",
|
||||
RecipeDifficultyHard : "Hard",
|
||||
RecipeTags : "Tags",
|
||||
RecipeLanguage : "Language",
|
||||
RecipeIsPublished : "Is published",
|
||||
RecipeDelete : "Delete recipe",
|
||||
RecipeAddAGroup : "Add a group",
|
||||
RecipeRemoveGroup : "Remove group",
|
||||
RecipeGroupName : "Name",
|
||||
RecipeGroupComment : "Comment",
|
||||
RecipeAddAStep : "Add a step",
|
||||
RecipeRemoveStep : "Remove step",
|
||||
RecipeStepAction : "Action",
|
||||
RecipeAddAnIngredient : "Add an ingredient",
|
||||
RecipeRemoveIngredient : "Remove ingredient",
|
||||
RecipeIngredientName : "Name",
|
||||
RecipeIngredientQuantity : "Quantity",
|
||||
RecipeIngredientUnit : "Unit",
|
||||
RecipeIngredientComment : "Comment",
|
||||
}
|
||||
(RecipeNotAllowedToEdit, "Not allowed to edit this recipe"),
|
||||
(RecipeNotAllowedToView, "Not allowed the view the recipe {}"),
|
||||
(RecipeNotFound, "Recipe not found"),
|
||||
(RecipeTitle, "Title"),
|
||||
(RecipeDescription, "Description"),
|
||||
(RecipeServings, "Servings"),
|
||||
(RecipeEstimatedTime, "Estimated time [min]"),
|
||||
(RecipeDifficulty, "Difficulty"),
|
||||
(RecipeDifficultyEasy, "Easy"),
|
||||
(RecipeDifficultyMedium, "Medium"),
|
||||
(RecipeDifficultyHard, "Hard"),
|
||||
(RecipeTags, "Tags"),
|
||||
(RecipeLanguage, "Language"),
|
||||
(RecipeIsPublished, "Is published"),
|
||||
(RecipeDelete, "Delete recipe"),
|
||||
(RecipeAddAGroup, "Add a group"),
|
||||
(RecipeRemoveGroup, "Remove group"),
|
||||
(RecipeGroupName, "Name"),
|
||||
(RecipeGroupComment, "Comment"),
|
||||
(RecipeAddAStep, "Add a step"),
|
||||
(RecipeRemoveStep, "Remove step"),
|
||||
(RecipeStepAction, "Action"),
|
||||
(RecipeAddAnIngredient, "Add an ingredient"),
|
||||
(RecipeRemoveIngredient, "Remove ingredient"),
|
||||
(RecipeIngredientName, "Name"),
|
||||
(RecipeIngredientQuantity, "Quantity"),
|
||||
(RecipeIngredientUnit, "Unit"),
|
||||
(RecipeIngredientComment, "Comment"),
|
||||
]
|
||||
),
|
||||
(
|
||||
code: "fr",
|
||||
name: "Français",
|
||||
translation: {
|
||||
MainTitle: "Recettes de Cuisine",
|
||||
CreateNewRecipe: "Créer une nouvelle recette",
|
||||
UnpublishedRecipes: "Recettes non-publiés",
|
||||
UntitledRecipe: "Recette sans nom",
|
||||
translation: [
|
||||
(MainTitle, "Recettes de Cuisine"),
|
||||
(CreateNewRecipe, "Créer une nouvelle recette"),
|
||||
(UnpublishedRecipes, "Recettes non-publiés"),
|
||||
(UntitledRecipe, "Recette sans nom"),
|
||||
|
||||
Name: "Nom",
|
||||
EmailAddress: "Adresse email",
|
||||
Password: "Mot de passe",
|
||||
(Name, "Nom"),
|
||||
(EmailAddress, "Adresse email"),
|
||||
(Password, "Mot de passe"),
|
||||
|
||||
SignOut: "Se déconnecter",
|
||||
Save: "Sauvegarder",
|
||||
NotLoggedIn: "Pas connecté",
|
||||
(SignOut, "Se déconnecter"),
|
||||
(Save, "Sauvegarder"),
|
||||
(NotLoggedIn, "Pas connecté"),
|
||||
|
||||
DatabaseError: "Erreur de la base de données",
|
||||
(DatabaseError, "Erreur de la base de données"),
|
||||
|
||||
SignInMenu: "Se connecter",
|
||||
SignInTitle: "Se connecter",
|
||||
SignInButton: "Se connecter",
|
||||
WrongEmailOrPassword: "Mot de passe ou email invalide",
|
||||
AccountMustBeValidatedFirst: "Ce compte doit d'abord être validé",
|
||||
InvalidEmail: "Adresse email invalide",
|
||||
PasswordDontMatch: "Les mots de passe ne correspondent pas",
|
||||
InvalidPassword: "Le mot de passe doit avoir au moins {} caractères",
|
||||
EmailAlreadyTaken: "Cette adresse email n'est pas disponible",
|
||||
UnableToSendEmail: "L'email de validation n'a pas pu être envoyé",
|
||||
(SignInMenu, "Se connecter"),
|
||||
(SignInTitle, "Se connecter"),
|
||||
(SignInButton, "Se connecter"),
|
||||
(WrongEmailOrPassword, "Mot de passe ou email invalide"),
|
||||
(AccountMustBeValidatedFirst, "Ce compte doit d'abord être validé"),
|
||||
(InvalidEmail, "Adresse email invalide"),
|
||||
(PasswordDontMatch, "Les mots de passe ne correspondent pas"),
|
||||
(InvalidPassword, "Le mot de passe doit avoir au moins {} caractères"),
|
||||
(EmailAlreadyTaken, "Cette adresse email n'est pas disponible"),
|
||||
(UnableToSendEmail, "L'email de validation n'a pas pu être envoyé"),
|
||||
|
||||
ValidationSuccessful: "Email validé avec succès",
|
||||
ValidationExpired: "La validation a expiré. Essayez de vous inscrire à nouveau avec la même adresse email",
|
||||
ValidationErrorTryToSignUpAgain: "Erreur de validation. Essayez de vous inscrire à nouveau avec la même adresse email",
|
||||
ValidationError: "Erreur de validation",
|
||||
ValidationUserAlreadyExists: "Utilisateur déjà existant",
|
||||
(ValidationSuccessful, "Email validé avec succès"),
|
||||
(ValidationExpired, "La validation a expiré. Essayez de vous inscrire à nouveau avec la même adresse email"),
|
||||
(ValidationErrorTryToSignUpAgain, "Erreur de validation. Essayez de vous inscrire à nouveau avec la même adresse email"),
|
||||
(ValidationError, "Erreur de validation"),
|
||||
(ValidationUserAlreadyExists, "Utilisateur déjà existant"),
|
||||
|
||||
SignUpMenu: "S'inscrire",
|
||||
SignUpTitle: "Inscription",
|
||||
SignUpButton: "Valider",
|
||||
SignUpEmailSent: "Un email a été envoyé, suivez le lien pour valider votre compte",
|
||||
SignUpFollowEmailLink: "Suivez ce lien pour valider votre inscription: {}",
|
||||
SignUpEmailValidationSuccess: "La validation de votre email s'est déroulée avec succès, votre compte a été créé",
|
||||
SignUpValidationExpired: "La validation a expiré. Essayez de vous inscrire à nouveau",
|
||||
SignUpValidationErrorTryAgain: "Erreur de validation. Essayez de vous inscrire à nouveau",
|
||||
ChooseAPassword: "Choisir un mot de passe (minimum {} caractères)",
|
||||
ReEnterPassword: "Entrez à nouveau le mot de passe",
|
||||
(SignUpMenu, "S'inscrire"),
|
||||
(SignUpTitle, "Inscription"),
|
||||
(SignUpButton, "Valider"),
|
||||
(SignUpEmailSent, "Un email a été envoyé), suivez le lien pour valider votre compte"),
|
||||
(SignUpFollowEmailLink, "Suivez ce lien pour valider votre inscription, {}"),
|
||||
(SignUpEmailValidationSuccess, "La validation de votre email s'est déroulée avec succès), votre compte a été créé"),
|
||||
(SignUpValidationExpired, "La validation a expiré. Essayez de vous inscrire à nouveau"),
|
||||
(SignUpValidationErrorTryAgain, "Erreur de validation. Essayez de vous inscrire à nouveau"),
|
||||
(ChooseAPassword, "Choisir un mot de passe (minimum {} caractères)"),
|
||||
(ReEnterPassword, "Entrez à nouveau le mot de passe"),
|
||||
|
||||
LostPassword: "Mot de passe perdu",
|
||||
AskResetButton: "Demander la réinitialisation",
|
||||
AskResetAlreadyLoggedInError: "Impossible de demander une réinitialisation du mot de passe lorsque déjà connecté",
|
||||
AskResetEmailAlreadyResetError: "Le mot de passe a déjà été réinitialisé pour cette adresse email",
|
||||
AskResetFollowEmailLink: "Suivez ce lien pour réinitialiser votre mot de passe: {}",
|
||||
AskResetEmailSent: "Un email a été envoyé, suivez le lien pour réinitialiser votre mot de passe",
|
||||
AskResetTokenMissing: "Jeton de réinitialisation manquant",
|
||||
AskResetTokenExpired: "Jeton expiré, essayez de réinitialiser votre mot de passe à nouveau",
|
||||
PasswordReset: "Votre mot de passe a été réinitialisé",
|
||||
EmailUnknown: "Email inconnu",
|
||||
UnableToSendResetEmail: "Impossible d'envoyer l'email pour la réinitialisation du mot de passe",
|
||||
(LostPassword, "Mot de passe perdu"),
|
||||
(AskResetButton, "Demander la réinitialisation"),
|
||||
(AskResetAlreadyLoggedInError, "Impossible de demander une réinitialisation du mot de passe lorsque déjà connecté"),
|
||||
(AskResetEmailAlreadyResetError, "Le mot de passe a déjà été réinitialisé pour cette adresse email"),
|
||||
(AskResetFollowEmailLink, "Suivez ce lien pour réinitialiser votre mot de passe, {}"),
|
||||
(AskResetEmailSent, "Un email a été envoyé), suivez le lien pour réinitialiser votre mot de passe"),
|
||||
(AskResetTokenMissing, "Jeton de réinitialisation manquant"),
|
||||
(AskResetTokenExpired, "Jeton expiré), essayez de réinitialiser votre mot de passe à nouveau"),
|
||||
(PasswordReset, "Votre mot de passe a été réinitialisé"),
|
||||
(EmailUnknown, "Email inconnu"),
|
||||
(UnableToSendResetEmail, "Impossible d'envoyer l'email pour la réinitialisation du mot de passe"),
|
||||
|
||||
ProfileTitle: "Profile",
|
||||
ProfileEmail: "Email (doit être revalidé si changé)",
|
||||
ProfileNewPassword: "Nouveau mot de passe (minimum {} caractères)",
|
||||
ProfileFollowEmailLink: "Suivez ce lien pour valider l'adresse email: {}",
|
||||
ProfileEmailSent: "Un email a été envoyé, suivez le lien pour valider la nouvelle adresse email",
|
||||
ProfileSaved: "Profile sauvegardé",
|
||||
(ProfileTitle, "Profile"),
|
||||
(ProfileEmail, "Email (doit être revalidé si changé)"),
|
||||
(ProfileNewPassword, "Nouveau mot de passe (minimum {} caractères)"),
|
||||
(ProfileFollowEmailLink, "Suivez ce lien pour valider l'adresse email, {}"),
|
||||
(ProfileEmailSent, "Un email a été envoyé), suivez le lien pour valider la nouvelle adresse email"),
|
||||
(ProfileSaved, "Profile sauvegardé"),
|
||||
|
||||
RecipeNotAllowedToEdit: "Vous n'êtes pas autorisé à éditer cette recette",
|
||||
RecipeNotAllowedToView: "Vous n'êtes pas autorisé à voir la recette {}",
|
||||
RecipeNotFound: "Recette non-trouvée",
|
||||
RecipeTitle : "Titre",
|
||||
RecipeDescription : "Description",
|
||||
RecipeServings : "Nombre de personnes",
|
||||
RecipeEstimatedTime : "Temps estimé",
|
||||
RecipeDifficulty : "Difficulté",
|
||||
RecipeDifficultyEasy : "Facile",
|
||||
RecipeDifficultyMedium : "Moyen",
|
||||
RecipeDifficultyHard : "Difficile",
|
||||
RecipeTags : "Tags",
|
||||
RecipeLanguage : "Langue",
|
||||
RecipeIsPublished : "Est publié",
|
||||
RecipeDelete : "Supprimer la recette",
|
||||
RecipeAddAGroup : "Ajouter un groupe",
|
||||
RecipeRemoveGroup : "Supprimer le groupe",
|
||||
RecipeGroupName : "Nom",
|
||||
RecipeGroupComment : "Commentaire",
|
||||
RecipeAddAStep : "Ajouter une étape",
|
||||
RecipeRemoveStep : "Supprimer l'étape",
|
||||
RecipeStepAction : "Action",
|
||||
RecipeAddAnIngredient : "Ajouter un ingrédient",
|
||||
RecipeRemoveIngredient : "Supprimer l'ingrédient",
|
||||
RecipeIngredientName : "Nom",
|
||||
RecipeIngredientQuantity : "Quantité",
|
||||
RecipeIngredientUnit : "Unité",
|
||||
RecipeIngredientComment : "Commentaire",
|
||||
}
|
||||
(RecipeNotAllowedToEdit, "Vous n'êtes pas autorisé à éditer cette recette"),
|
||||
(RecipeNotAllowedToView, "Vous n'êtes pas autorisé à voir la recette {}"),
|
||||
(RecipeNotFound, "Recette non-trouvée"),
|
||||
(RecipeTitle, "Titre"),
|
||||
(RecipeDescription, "Description"),
|
||||
(RecipeServings, "Nombre de personnes"),
|
||||
(RecipeEstimatedTime, "Temps estimé"),
|
||||
(RecipeDifficulty, "Difficulté"),
|
||||
(RecipeDifficultyEasy, "Facile"),
|
||||
(RecipeDifficultyMedium, "Moyen"),
|
||||
(RecipeDifficultyHard, "Difficile"),
|
||||
(RecipeTags, "Tags"),
|
||||
(RecipeLanguage, "Langue"),
|
||||
(RecipeIsPublished, "Est publié"),
|
||||
(RecipeDelete, "Supprimer la recette"),
|
||||
(RecipeAddAGroup, "Ajouter un groupe"),
|
||||
(RecipeRemoveGroup, "Supprimer le groupe"),
|
||||
(RecipeGroupName, "Nom"),
|
||||
(RecipeGroupComment, "Commentaire"),
|
||||
(RecipeAddAStep, "Ajouter une étape"),
|
||||
(RecipeRemoveStep, "Supprimer l'étape"),
|
||||
(RecipeStepAction, "Action"),
|
||||
(RecipeAddAnIngredient, "Ajouter un ingrédient"),
|
||||
(RecipeRemoveIngredient, "Supprimer l'ingrédient"),
|
||||
(RecipeIngredientName, "Nom"),
|
||||
(RecipeIngredientQuantity, "Quantité"),
|
||||
(RecipeIngredientUnit, "Unité"),
|
||||
(RecipeIngredientComment, "Commentaire"),
|
||||
]
|
||||
)
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue