feat: agregar soporte para el idioma francés en la aplicación
This commit is contained in:
parent
7e7044ac1c
commit
9faec986d7
@ -1,5 +1,4 @@
|
|||||||
import { ScrollArea } from "@/ui";
|
import { ScrollArea } from "@/ui";
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export type BlockDataType = {
|
export type BlockDataType = {
|
||||||
@ -99,10 +98,8 @@ export const BlockList = ({
|
|||||||
onSelect: (data: BlockDataType, quantity: number) => void;
|
onSelect: (data: BlockDataType, quantity: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const [language] = useState(i18n.resolvedLanguage || "es");
|
const language = i18n.resolvedLanguage || "es";
|
||||||
const [listState] = useState(blockData[language.toLowerCase()]);
|
const listState = blockData[language.toLowerCase()] ?? blockData.es;
|
||||||
|
|
||||||
console.log(language);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className='h-96'>
|
<ScrollArea className='h-96'>
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import { Trans } from "react-i18next";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
||||||
|
import { documentedLanguages } from "@/lib/languages";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useToast } from "@/ui/use-toast";
|
import { useToast } from "@/ui/use-toast";
|
||||||
import { IUpdateProfile_Request_DTO } from "@shared/contexts";
|
import { IUpdateProfile_Request_DTO } from "@shared/contexts";
|
||||||
@ -219,8 +220,12 @@ export const SettingsEditor = () => {
|
|||||||
<SelectValue placeholder={t("settings.form_fields.language.placeholder")} />
|
<SelectValue placeholder={t("settings.form_fields.language.placeholder")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='es'>{t("settings.form_fields.language.options.es")}</SelectItem>
|
{documentedLanguages.map((language) => (
|
||||||
<SelectItem value='en'>{t("settings.form_fields.language.options.en")}</SelectItem>
|
<SelectItem key={language.code} value={language.code}>
|
||||||
|
<span aria-hidden='true' className={`mr-2 ${language.flagClassName}`} />
|
||||||
|
{language.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -4,4 +4,5 @@ import "/node_modules/flag-icons/css/flag-icons.min.css";
|
|||||||
export const documentedLanguages = [
|
export const documentedLanguages = [
|
||||||
{ code: "es", name: "Español", flagClassName: "fi fi-es" },
|
{ code: "es", name: "Español", flagClassName: "fi fi-es" },
|
||||||
{ code: "en", name: "English", flagClassName: "fi fi-gb" },
|
{ code: "en", name: "English", flagClassName: "fi fi-gb" },
|
||||||
|
{ code: "fr", name: "Français", flagClassName: "fi fi-fr" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@ -342,7 +342,7 @@
|
|||||||
"label": "Document language",
|
"label": "Document language",
|
||||||
"desc": "Language used to generate the quote PDF",
|
"desc": "Language used to generate the quote PDF",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"options": { "es": "Spanish", "en": "English" }
|
"options": { "es": "Spanish", "en": "English", "fr": "French" }
|
||||||
},
|
},
|
||||||
"currency_code": {
|
"currency_code": {
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
@ -469,7 +469,8 @@
|
|||||||
"desc": "Preferred language for the interface",
|
"desc": "Preferred language for the interface",
|
||||||
"options": {
|
"options": {
|
||||||
"es": "Spanish",
|
"es": "Spanish",
|
||||||
"en": "English"
|
"en": "English",
|
||||||
|
"fr": "French"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
|
|||||||
@ -342,7 +342,7 @@
|
|||||||
"label": "Idioma del documento",
|
"label": "Idioma del documento",
|
||||||
"desc": "Idioma utilizado para generar el presupuesto en PDF",
|
"desc": "Idioma utilizado para generar el presupuesto en PDF",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"options": { "es": "Español", "en": "Inglés" }
|
"options": { "es": "Español", "en": "Inglés", "fr": "Francés" }
|
||||||
},
|
},
|
||||||
"currency_code": {
|
"currency_code": {
|
||||||
"label": "Moneda",
|
"label": "Moneda",
|
||||||
@ -469,7 +469,8 @@
|
|||||||
"desc": "Idioma preferido para la interfaz",
|
"desc": "Idioma preferido para la interfaz",
|
||||||
"options": {
|
"options": {
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"en": "Inglés"
|
"en": "Inglés",
|
||||||
|
"fr": "Francés"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
|
|||||||
450
client/src/locales/fr.json
Normal file
450
client/src/locales/fr.json
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
{
|
||||||
|
"translation": {
|
||||||
|
"common": {
|
||||||
|
"required": "obligatoire",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"no": "Non",
|
||||||
|
"yes": "Oui",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"save_close": "Enregistrer et fermer",
|
||||||
|
"discard": "Abandonner",
|
||||||
|
"hide": "Masquer",
|
||||||
|
"back": "Retour",
|
||||||
|
"upload": "Téléverser",
|
||||||
|
"continue": "Continuer",
|
||||||
|
"close": "Fermer",
|
||||||
|
"more": "Plus",
|
||||||
|
"pagination": "Pagination",
|
||||||
|
"previous": "Précédent",
|
||||||
|
"next": "Suivant",
|
||||||
|
"add": "Ajouter",
|
||||||
|
"submit": "Envoyer",
|
||||||
|
"download": "Télécharger",
|
||||||
|
"help": "Aide",
|
||||||
|
"error": "Erreur",
|
||||||
|
"actions": "Actions",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"remove": "Supprimer",
|
||||||
|
"archive": "Archiver",
|
||||||
|
"duplicate": "Dupliquer",
|
||||||
|
"pick_date": "Choisir une date",
|
||||||
|
"required_field": "Ce champ est obligatoire"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"loading_indicator": {
|
||||||
|
"title": "Chargement…"
|
||||||
|
},
|
||||||
|
"loading_overlay": {
|
||||||
|
"title": "Chargement…",
|
||||||
|
"subtitle": "Cela peut prendre quelques secondes. Veuillez ne pas fermer cette page."
|
||||||
|
},
|
||||||
|
"error_overlay": {
|
||||||
|
"title": "Une erreur s’est produite",
|
||||||
|
"go_home": "Aller à l’accueil"
|
||||||
|
},
|
||||||
|
"empty_state": {
|
||||||
|
"title": "C’est très vide ici",
|
||||||
|
"subtitle": "Commencez par demander le chargement du catalogue",
|
||||||
|
"button": "Demander le chargement du catalogue"
|
||||||
|
},
|
||||||
|
"data_table": {
|
||||||
|
"columns": "Colonnes",
|
||||||
|
"no_results": "Aucun résultat trouvé.",
|
||||||
|
"clear_filters": "Effacer les filtres",
|
||||||
|
"selected_count": "{{count}} sélectionné(s)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"use_unsaved_changes_notifier": {
|
||||||
|
"title": "Des modifications ont été détectées",
|
||||||
|
"subtitle": "Attention, des modifications ne sont pas enregistrées sur cette page.\nSi vous continuez, vous les perdrez.",
|
||||||
|
"confirm_text": "Continuer",
|
||||||
|
"cancel_text": "Ne pas continuer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main_menu": {
|
||||||
|
"toggle_navigation": "Basculer le menu de navigation",
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"catalog": "Catalogue",
|
||||||
|
"quotes": "Devis",
|
||||||
|
"search_placeholder": "Rechercher des produits, devis, etc.",
|
||||||
|
"user": {
|
||||||
|
"user_menu": "Menu utilisateur",
|
||||||
|
"my_account": "Mon compte",
|
||||||
|
"profile": "Profil",
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"support": "Assistance",
|
||||||
|
"logout": "Se déconnecter"
|
||||||
|
},
|
||||||
|
"logout_dialog": {
|
||||||
|
"title": "Se déconnecter",
|
||||||
|
"description": "Voulez-vous vous déconnecter ?",
|
||||||
|
"confirm_label": "Se déconnecter",
|
||||||
|
"cancel_label": "Annuler"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login_page": {
|
||||||
|
"title": "Outil de devis pour distributeurs",
|
||||||
|
"description": "Saisissez votre adresse e-mail et votre mot de passe pour accéder",
|
||||||
|
"email_label": "E-mail",
|
||||||
|
"email_placeholder": "monemail@exemple.com",
|
||||||
|
"password_label": "Mot de passe",
|
||||||
|
"forgotten_password": "Mot de passe oublié ?",
|
||||||
|
"become_dealer": "Vous souhaitez devenir distributeur Uecko ?",
|
||||||
|
"contact_us": "Contactez-nous",
|
||||||
|
"login": "Se connecter"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"welcome": "Bienvenue"
|
||||||
|
},
|
||||||
|
"catalog": {
|
||||||
|
"list": {
|
||||||
|
"title": "Catalogue d’articles",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Commencez par charger les articles du catalogue"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"description": "Description",
|
||||||
|
"points": "Points",
|
||||||
|
"retail_price": "Prix public",
|
||||||
|
"quantity": "Quantité"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quotes": {
|
||||||
|
"list": {
|
||||||
|
"title": "Devis",
|
||||||
|
"subtitle": "",
|
||||||
|
"tabs_title": "États",
|
||||||
|
"select_status": "Sélectionner un état",
|
||||||
|
"preview_aria": "Afficher l’aperçu du devis",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Commencez par créer un devis"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"all": "Tous",
|
||||||
|
"draft": "Brouillons",
|
||||||
|
"ready": "Prêts",
|
||||||
|
"delivered": "Livrés",
|
||||||
|
"accepted": "Acceptés",
|
||||||
|
"rejected": "Refusés",
|
||||||
|
"archived": "Archivés"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Date",
|
||||||
|
"date_sent": "Envoyé à Uecko",
|
||||||
|
"reference": "Référence",
|
||||||
|
"status": "État",
|
||||||
|
"customer_reference": "Réf. client",
|
||||||
|
"customer_information": "Client",
|
||||||
|
"total_price": "Montant total"
|
||||||
|
},
|
||||||
|
"resume": {
|
||||||
|
"title": "Devis",
|
||||||
|
"tabs": {
|
||||||
|
"resume": "Résumé",
|
||||||
|
"preview": "Aperçu"
|
||||||
|
},
|
||||||
|
"quote_information": "Informations",
|
||||||
|
"customer_information": "Client",
|
||||||
|
"price_information": "Montants",
|
||||||
|
"select_quote": "Sélectionnez un devis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"created": "Devis créé",
|
||||||
|
"saved": "Devis enregistré",
|
||||||
|
"saving": "Enregistrement du devis"
|
||||||
|
},
|
||||||
|
"pdf_generation": {
|
||||||
|
"title": "Génération du PDF",
|
||||||
|
"error": "Erreur lors de la génération du PDF",
|
||||||
|
"progress": "Progression : {{percentage}}%",
|
||||||
|
"elapsed": "Temps écoulé : {{elapsed}} secondes",
|
||||||
|
"remaining": "Temps restant estimé : {{estimatedTime}} secondes",
|
||||||
|
"cancel": "Annuler la génération"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Nouveau devis",
|
||||||
|
"form_groups": {
|
||||||
|
"general": {
|
||||||
|
"title": "Données générales",
|
||||||
|
"desc": "Données générales et client auquel le devis est destiné"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"title": "État",
|
||||||
|
"desc": "État du devis"
|
||||||
|
},
|
||||||
|
"documents": {
|
||||||
|
"title": "Documents",
|
||||||
|
"desc": "Ajoutez à votre devis des documents tels que photos, plans, croquis, etc.",
|
||||||
|
"images": "Images"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Devis",
|
||||||
|
"buttons": {
|
||||||
|
"save_quote": "Enregistrer le devis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"duplicate_action": {
|
||||||
|
"toast_success": "Devis dupliqué"
|
||||||
|
},
|
||||||
|
"downloading_dialog": {
|
||||||
|
"title": "Téléchargement du devis",
|
||||||
|
"description": "Veuillez patienter pendant la génération et le téléchargement du devis au format PDF…",
|
||||||
|
"progress": {
|
||||||
|
"label": "Le téléchargement est {{status}}",
|
||||||
|
"status_in_progress": "en cours",
|
||||||
|
"status_stopped": "arrêté"
|
||||||
|
},
|
||||||
|
"cancel_button": "Annuler le téléchargement",
|
||||||
|
"toast_success": "Devis téléchargé"
|
||||||
|
},
|
||||||
|
"catalog_picker_dialog": {
|
||||||
|
"title": "Sélectionner des articles du catalogue",
|
||||||
|
"description": "Pour compléter votre devis, vous pouvez ajouter des articles du catalogue.",
|
||||||
|
"toast_article_added": "Article du catalogue ajouté :"
|
||||||
|
},
|
||||||
|
"blocks_picker_dialog": {
|
||||||
|
"title": "Sélectionner des blocs de texte",
|
||||||
|
"description": "Pour vous aider à compléter le devis, vous pouvez ajouter des blocs de texte prédéfinis de cette liste.",
|
||||||
|
"toast_article_added": "Bloc de texte ajouté :"
|
||||||
|
},
|
||||||
|
"quote_sent_to_editor": {
|
||||||
|
"trigger_button": "Envoyer à Uecko",
|
||||||
|
"title": "Envoyer le devis à Uecko",
|
||||||
|
"description": "Voulez-vous envoyer ce devis à Uecko ? Cette action est irréversible.",
|
||||||
|
"toast_status_changed": "Devis envoyé à Uecko"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"draft": "Brouillon",
|
||||||
|
"ready": "Prêt",
|
||||||
|
"delivered": "Livré",
|
||||||
|
"accepted": "Accepté",
|
||||||
|
"rejected": "Refusé",
|
||||||
|
"archived": "Archivé"
|
||||||
|
},
|
||||||
|
"form_fields": {
|
||||||
|
"date": {
|
||||||
|
"label": "Date",
|
||||||
|
"desc": "Date de ce devis",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"label": "Référence",
|
||||||
|
"desc": "Référence de ce devis",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"lang_code": {
|
||||||
|
"label": "Langue du document",
|
||||||
|
"desc": "Langue utilisée pour générer le devis au format PDF",
|
||||||
|
"placeholder": "",
|
||||||
|
"options": {
|
||||||
|
"es": "Espagnol",
|
||||||
|
"en": "Anglais",
|
||||||
|
"fr": "Français"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currency_code": {
|
||||||
|
"label": "Devise",
|
||||||
|
"desc": "Devise du devis",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"customer_reference": {
|
||||||
|
"label": "Référence client",
|
||||||
|
"desc": "Référence client de ce devis",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"customer_information": {
|
||||||
|
"label": "Informations client",
|
||||||
|
"desc": "Indiquez le nom, l’adresse, le code postal et la ville du client.",
|
||||||
|
"placeholder": "Nom et prénom\nRue et numéro\nCode postal et ville…"
|
||||||
|
},
|
||||||
|
"payment_method": {
|
||||||
|
"label": "Mode de paiement",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Mode de paiement utilisé pour ce devis"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"label": "Notes",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Notes à inclure dans le devis"
|
||||||
|
},
|
||||||
|
"validity": {
|
||||||
|
"label": "Validité du devis",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Validité de ce devis"
|
||||||
|
},
|
||||||
|
"subtotal_price": {
|
||||||
|
"label": "Montant net",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"discount": {
|
||||||
|
"label": "Remise (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Pourcentage de remise"
|
||||||
|
},
|
||||||
|
"discount_price": {
|
||||||
|
"label": "Montant de la remise",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Montant de la remise"
|
||||||
|
},
|
||||||
|
"before_tax_price": {
|
||||||
|
"label": "Base imposable",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"tax": {
|
||||||
|
"label": "Taxe (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Pourcentage de taxe"
|
||||||
|
},
|
||||||
|
"tax_price": {
|
||||||
|
"label": "Montant de la taxe",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Montant de la taxe"
|
||||||
|
},
|
||||||
|
"total_price": {
|
||||||
|
"label": "Montant total",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Montant total"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"quantity": {
|
||||||
|
"label": "Quantité",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"unit_price": {
|
||||||
|
"label": "Prix unitaire",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Prix unitaire de l’article"
|
||||||
|
},
|
||||||
|
"subtotal_price": {
|
||||||
|
"label": "Sous-total",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"discount": {
|
||||||
|
"label": "Remise (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Pourcentage de remise"
|
||||||
|
},
|
||||||
|
"total_price": {
|
||||||
|
"label": "Montant total",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Montant total après application de la remise"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"edit": {
|
||||||
|
"title": "Paramètres",
|
||||||
|
"tabs": {
|
||||||
|
"profile": "Paramètres du profil",
|
||||||
|
"quotes": "Paramètres des devis",
|
||||||
|
"legal": "Paramètres juridiques"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form_fields": {
|
||||||
|
"language": {
|
||||||
|
"label": "Langue",
|
||||||
|
"placeholder": "Sélectionnez une langue",
|
||||||
|
"desc": "Langue préférée de l’interface",
|
||||||
|
"options": {
|
||||||
|
"es": "Espagnol",
|
||||||
|
"en": "Anglais",
|
||||||
|
"fr": "Français"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"label": "Logo",
|
||||||
|
"desc": "Ce logo apparaîtra dans les propositions exportées au format PDF",
|
||||||
|
"requirements": "Les fichiers JPG ou PNG sont acceptés.",
|
||||||
|
"upload": "Téléverser le logo",
|
||||||
|
"invalid_format": "Seuls les fichiers JPG ou PNG sont acceptés",
|
||||||
|
"invalid_dimensions": "Les dimensions du logo sont invalides."
|
||||||
|
},
|
||||||
|
"contact_information": {
|
||||||
|
"label": "Informations de contact",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Ces coordonnées apparaîtront dans les devis remis à vos clients."
|
||||||
|
},
|
||||||
|
"default_tax": {
|
||||||
|
"label": "Taxe par défaut (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Pourcentage de taxe par défaut dans les devis"
|
||||||
|
},
|
||||||
|
"default_tax_label": {
|
||||||
|
"placeholder": "% TVA",
|
||||||
|
"desc": "Libellé affiché à côté de la taxe"
|
||||||
|
},
|
||||||
|
"default_show_line_discount_column": {
|
||||||
|
"label": "Afficher la colonne de remise par défaut",
|
||||||
|
"desc": "Les nouveaux devis afficheront initialement la colonne de remise."
|
||||||
|
},
|
||||||
|
"default_legal_terms": {
|
||||||
|
"label": "Clauses juridiques",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Texte juridique à inclure dans les nouveaux devis"
|
||||||
|
},
|
||||||
|
"default_payment_method": {
|
||||||
|
"label": "Mode de paiement",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Texte utilisé dans les nouveaux devis"
|
||||||
|
},
|
||||||
|
"default_notes": {
|
||||||
|
"label": "Notes",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Notes à inclure dans les nouveaux devis"
|
||||||
|
},
|
||||||
|
"default_quote_validity": {
|
||||||
|
"label": "Validité par défaut",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Durée de validité du devis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"saved": "Paramètres enregistrés",
|
||||||
|
"saving": "Enregistrement des paramètres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"modal": {
|
||||||
|
"title": "Envoyer une incidence",
|
||||||
|
"subtitle": "Utilisez ce formulaire pour signaler un problème rencontré pendant l’utilisation de l’application.",
|
||||||
|
"open": "Ouvrir la fenêtre d’assistance",
|
||||||
|
"placeholder": "Décrivez l’incidence ici…",
|
||||||
|
"submit": "Envoyer l’incidence",
|
||||||
|
"validation": {
|
||||||
|
"empty": "Vous devez écrire quelque chose avant d’envoyer",
|
||||||
|
"min": "Le texte est trop court.",
|
||||||
|
"max": "Le texte est trop long.",
|
||||||
|
"required": "La description est obligatoire"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sent_title": "Incident envoyé",
|
||||||
|
"sent_description": "L’incident a été envoyé correctement",
|
||||||
|
"error_title": "Erreur d’envoi",
|
||||||
|
"error_description": "L’incident n’a pas pu être envoyé. Veuillez réessayer."
|
||||||
|
},
|
||||||
|
"cancel_confirmation": {
|
||||||
|
"title": "Voulez-vous annuler ?",
|
||||||
|
"description": "Vous perdrez les modifications non enregistrées.",
|
||||||
|
"back": "Retour au formulaire",
|
||||||
|
"confirm": "Oui, fermer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form_fields": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { initReactI18next } from "react-i18next";
|
|||||||
import catResources from "./ca.json";
|
import catResources from "./ca.json";
|
||||||
import enResources from "./en.json";
|
import enResources from "./en.json";
|
||||||
import esResources from "./es.json";
|
import esResources from "./es.json";
|
||||||
|
import frResources from "./fr.json";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
// detect user language
|
// detect user language
|
||||||
@ -26,6 +27,7 @@ i18n
|
|||||||
ca: catResources,
|
ca: catResources,
|
||||||
en: enResources,
|
en: enResources,
|
||||||
es: esResources,
|
es: esResources,
|
||||||
|
fr: frResources,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,34 @@ export const reportLabels = {
|
|||||||
percentage_discount: "% Discount",
|
percentage_discount: "% Discount",
|
||||||
taxable_amount: "Taxable amount",
|
taxable_amount: "Taxable amount",
|
||||||
},
|
},
|
||||||
|
fr: {
|
||||||
|
official_distributor: "DISTRIBUTEUR OFFICIEL",
|
||||||
|
dealer_logo: "Logo du distributeur",
|
||||||
|
document_title: "DEVIS",
|
||||||
|
browser_title: "Devis",
|
||||||
|
quote_number: "Devis n° :",
|
||||||
|
date: "Date :",
|
||||||
|
validity: "Validité :",
|
||||||
|
salesperson: "Commercial :",
|
||||||
|
customer_reference: "Référence client :",
|
||||||
|
page: "Page",
|
||||||
|
of: "sur",
|
||||||
|
quantity: "Qté",
|
||||||
|
description: "Description",
|
||||||
|
unit_price: "Prix unitaire",
|
||||||
|
subtotal: "Sous-total",
|
||||||
|
discount: "Remise (%)",
|
||||||
|
total_amount: "Montant total",
|
||||||
|
payment_method: "Mode de paiement :",
|
||||||
|
notes: "Notes :",
|
||||||
|
net_amount: "Montant net",
|
||||||
|
discounts: "Remises",
|
||||||
|
percentage_discount: "% de remise",
|
||||||
|
taxable_amount: "Base imposable",
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type ReportLanguage = keyof typeof reportLabels;
|
export type ReportLanguage = keyof typeof reportLabels;
|
||||||
|
|
||||||
export const resolveReportLanguage = (language: string): ReportLanguage =>
|
export const resolveReportLanguage = (language: string): ReportLanguage =>
|
||||||
language === "en" ? "en" : "es";
|
language === "en" || language === "fr" ? language : "es";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||||
import { createQuoteMapper } from "@/contexts/sales/infrastructure/mappers/quote.mapper";
|
import { createQuoteMapper } from "@/contexts/sales/infrastructure/mappers/quote.mapper";
|
||||||
|
import { resolveReportLanguage } from "./ReportQuote.labels";
|
||||||
import { ReportQuotePresenter } from "./ReportQuote.reporter";
|
import { ReportQuotePresenter } from "./ReportQuote.reporter";
|
||||||
|
|
||||||
const source = (
|
const source = (
|
||||||
@ -69,12 +70,16 @@ describe("ReportQuotePresenter", () => {
|
|||||||
expect(html).not.toContain(">PRESUPUESTO<");
|
expect(html).not.toContain(">PRESUPUESTO<");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to Spanish labels and language for unsupported quote languages", async () => {
|
it("renders French report labels", async () => {
|
||||||
const html = await ReportQuotePresenter.toHTML(mapper.mapToDomain(source(false, 0, "% IGIC", "fr")), context);
|
const html = await ReportQuotePresenter.toHTML(mapper.mapToDomain(source(false, 0, "% TVA", "fr")), context);
|
||||||
|
|
||||||
expect(html).toContain('<html lang="es">');
|
expect(html).toContain('<html lang="fr">');
|
||||||
expect(html).toContain("PRESUPUESTO");
|
expect(html).toContain("DEVIS");
|
||||||
expect(html).toContain("Descripción");
|
expect(html).toContain("Description");
|
||||||
expect(html).toContain("% IGIC");
|
expect(html).toContain("% TVA");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to Spanish labels and language for unsupported quote languages", async () => {
|
||||||
|
expect(resolveReportLanguage("zz")).toBe("es");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,13 +26,13 @@ describe("ensureUpdateProfile_Request_DTOIsValid default_tax_label", () => {
|
|||||||
).toHaveProperty("isFailure", true);
|
).toHaveProperty("isFailure", true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([undefined, "es", "en"])('accepts interface language %p', (lang_code) => {
|
it.each([undefined, "es", "en", "fr"])('accepts interface language %p', (lang_code) => {
|
||||||
expect(
|
expect(
|
||||||
ensureUpdateProfile_Request_DTOIsValid({ ...validProfile, lang_code })
|
ensureUpdateProfile_Request_DTOIsValid({ ...validProfile, lang_code })
|
||||||
).toHaveProperty("isSuccess", true);
|
).toHaveProperty("isSuccess", true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(["ca", "fr", "zz"])('rejects unsupported interface language %p', (lang_code) => {
|
it.each(["ca", "zz"])('rejects unsupported interface language %p', (lang_code) => {
|
||||||
expect(
|
expect(
|
||||||
ensureUpdateProfile_Request_DTOIsValid({ ...validProfile, lang_code })
|
ensureUpdateProfile_Request_DTOIsValid({ ...validProfile, lang_code })
|
||||||
).toHaveProperty("isFailure", true);
|
).toHaveProperty("isFailure", true);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import { IPercentage_DTO, Result, RuleValidator } from "../../../../common";
|
import { IPercentage_DTO, Result, RuleValidator } from "../../../../common";
|
||||||
|
|
||||||
export const SUPPORTED_INTERFACE_LANGUAGE_CODES = ["es", "en"] as const;
|
export const SUPPORTED_INTERFACE_LANGUAGE_CODES = ["es", "en", "fr"] as const;
|
||||||
|
|
||||||
export interface IUpdateProfile_Request_DTO {
|
export interface IUpdateProfile_Request_DTO {
|
||||||
lang_code?: string;
|
lang_code?: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user