From 0d58bc167ea1398d64fa5eb012c07406eb1b25c5 Mon Sep 17 00:00:00 2001 From: Gwendal Date: Mon, 23 May 2022 00:13:47 +0200 Subject: [PATCH] feat(translation): enable translation --- package-lock.json | 84 ++++++++++++++++++++++ package.json | 4 ++ public/locales/en/translation.json | 70 ++++++++++++++++++ public/locales/fr/translation.json | 70 ++++++++++++++++++ src/components/Auth/Login.tsx | 27 ++++--- src/components/Page/Sidebar.tsx | 9 ++- src/components/Products/CreateProduct.tsx | 26 +++---- src/components/Products/ModifyProduct.tsx | 40 ++++++----- src/components/Products/ProductList.tsx | 48 +++++++++---- src/components/Products/PublishModal.tsx | 16 ++--- src/components/Products/UnPublishModal.tsx | 16 ++--- src/i18n.js | 26 +++++++ src/index.tsx | 1 + 13 files changed, 365 insertions(+), 72 deletions(-) create mode 100644 public/locales/en/translation.json create mode 100644 public/locales/fr/translation.json create mode 100644 src/i18n.js diff --git a/package-lock.json b/package-lock.json index 8fd346b..c4bf61c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4830,6 +4830,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7760,6 +7768,14 @@ "terser": "^4.6.3" } }, + "html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "requires": { + "void-elements": "3.1.0" + } + }, "html-tags": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", @@ -8018,6 +8034,30 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, + "i18next": { + "version": "21.8.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.3.tgz", + "integrity": "sha512-I6QEXu096oaNH8h+hs2eHu6hxtWPdb/rsoRFHmFep01uuwB0h86ckXaT14ladhstWenEScsxiAQ2TW9fmDG57Q==", + "requires": { + "@babel/runtime": "^7.17.2" + } + }, + "i18next-browser-languagedetector": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz", + "integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==", + "requires": { + "@babel/runtime": "^7.14.6" + } + }, + "i18next-http-backend": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.0.tgz", + "integrity": "sha512-wsvx7E/CT1pHmBM99Vu57YLJpsrHbVjxGxf25EIJ/6oTjsvCkZZ6c3SA4TejcK5jIHfv9oLxQX8l+DFKZHZ0Gg==", + "requires": { + "cross-fetch": "3.1.5" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10766,6 +10806,35 @@ "lodash": "^4.17.21" } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -14010,6 +14079,16 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.31.1.tgz", "integrity": "sha512-QjtjZ8r8KtEBWWpcXLyQordCraTFxILtyQpaz5KLLxN2YzcC+FZ9LLtOnNGuOnzZo9gCoB+viK3ZHV9Mb2htmQ==" }, + "react-i18next": { + "version": "11.16.9", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.16.9.tgz", + "integrity": "sha512-euXxWvcEAvsY7ZVkwx9ztCq4butqtsGHEkpkuo0RMj8Ru09IF9o2KxCyN+zyv51Nr0aBh/elaTIiR6fMb8YfVg==", + "requires": { + "@babel/runtime": "^7.14.5", + "html-escaper": "^2.0.2", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -17028,6 +17107,11 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index e093f68..cc56fb8 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,15 @@ "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.3.3", "axios": "^0.21.1", + "i18next": "^21.8.3", + "i18next-browser-languagedetector": "^6.1.4", + "i18next-http-backend": "^1.4.0", "luxon": "^2.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-gravatar": "^2.6.3", "react-hook-form": "^7.31.1", + "react-i18next": "^11.16.9", "react-loader-spinner": "^4.0.0", "react-lottie-player": "^1.4.1", "react-markdown": "^6.0.2", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json new file mode 100644 index 0000000..444bca5 --- /dev/null +++ b/public/locales/en/translation.json @@ -0,0 +1,70 @@ +{ + "login": { + "title": "Admin Panel - FI-Sweets", + "subtitle": "Please login to your account", + "email": "Email", + "password": "Password", + "login_btn": "Login", + "forgot_password": "Forgot Password?", + "alert_welcome": "Welcome", + "alert_failed_empty": "Email or password empty", + "alert_failed_incorrect": "Email or password incorrect" + }, + "navigation": { + "dashboard": "Dashboard", + "products": "Products", + "recipe": "Recipes" + }, + "products": { + "add_btn": "Add Product", + "title_all": "All products", + "title_online": "Online products", + "add": { + "title": "Create sweet", + "name": "Name", + "price": "Price", + "flavor": "Flavor", + "description": "Description", + "cancel_btn": "Cancel", + "save_btn": "Create", + "alert_success": "Sweet created successfully", + "alert_failed_empty": "Thanks to fill all fields" + }, + "update": { + "title": "Product modification", + "name": "Name", + "price": "Price", + "flavor": "Flavor", + "highlight": "Highlight", + "description": "Description", + "img_upload_title": "Upload image", + "img_upload_description": "Select an image", + "cancel_btn": "Cancel", + "modify_btn": "Modify", + "alert_success": "Sweet modified successfully", + "alert_img_upload": "Image uploaded successfully", + "alert_failed": "Error while modifying sweet" + }, + "col_name": { + "product": "Name", + "price": "Price", + "highlight": "Highlight", + "status": "Status", + "actions": "Actions" + }, + "publish": { + "title": "Publish the sweet", + "description": "Are you really sure you want to publish the mignardise? This mignardise will be available for purchase in the store after this action.", + "cancel_btn": "Cancel", + "publish_btn": "Publish", + "alert_success": "Sweet published" + }, + "unpublish": { + "title": "Remove the sweet from the store", + "description": "Are you sure you want to remove the sweet from the store? This sweet will no longer be available for purchase in the store after this action.", + "cancel_btn": "Cancel", + "publish_btn": "Unpublish", + "alert_success": "Sweet unpublished" + } + } +} diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json new file mode 100644 index 0000000..9dd8aed --- /dev/null +++ b/public/locales/fr/translation.json @@ -0,0 +1,70 @@ +{ + "login": { + "title": "Pannel d'administration - FI-Sweets", + "subtitle": "Veuillez vous connecter", + "email": "Email", + "password": "Mot de passe", + "login_btn": "Se connecter", + "forgot_password": "Mot de passe oublié ?", + "alert_welcome": "Bienvenue", + "alert_failed_empty": "Email ou mot de passe vide", + "alert_failed_incorrect": "Email ou mot de passe incorrect" + }, + "navigation": { + "dashboard": "Tableau de bord", + "products": "Produits", + "recipe": "Recettes" + }, + "products": { + "add_btn": "Ajouter un produit", + "title_all": "Tous les produits", + "title_online": "Produits en ligne", + "add": { + "title": "Ajouter un produit", + "name": "Nom", + "price": "Prix", + "flavor": "Saveur", + "description": "Description", + "cancel_btn": "Annuler", + "save_btn": "Créer", + "alert_success": "Mignardise ajouté avec succès", + "alert_failed_empty": "Merci de remplir tous les champs" + }, + "update": { + "title": "Modifier un produit", + "name": "Nom", + "price": "Prix", + "flavor": "Saveur", + "highlight": "Mise en avant", + "description": "Description", + "img_upload_title": "Ajouté une image", + "img_upload_description": "Sélectionner une image", + "cancel_btn": "Annuler", + "modify_btn": "Modifier", + "alert_success": "Mignardise modifié succès", + "alert_img_upload": "Image ajouté avec succès", + "alert_failed": "Erreur lors de la modification de la mignardise" + }, + "col_name": { + "product": "Nom", + "price": "Prix", + "highlight": "Mise en avant", + "status": "Status", + "actions": "Actions" + }, + "publish": { + "title": "Publier la mignardise", + "description": "Êtes-vous vraiment certain de vouloir publier la mignardise ? Cette mignardise sera disponible à l'achat dans la boutique après cette action.", + "cancel_btn": "Annuler", + "publish_btn": "Publier", + "alert_success": "Mignardise publiée" + }, + "unpublish": { + "title": "Retirer la mignadise de la boutique", + "description": "Êtes-vous vraiment certain de vouloir retirer la mignardise de la boutique ? Cette mignardise ne sera plus disponible à l'achat dans la boutique après cette action.", + "cancel_btn": "Annuler", + "publish_btn": "Retirer", + "alert_success": "Mignardise retirée" + } + } +} diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index f42999b..275b434 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, { useEffect } from 'react'; import Lottie from 'react-lottie-player'; import loadingSplash from '../../assets/lotties/splash-loading.json'; import { useNavigate } from 'react-router-dom'; @@ -6,8 +6,10 @@ import { useToken } from '../../hooks/token'; import { login } from '../../hooks/auth/register'; import LoginRequest from '../../hooks/auth/requests/LoginRequest'; import { useToasts } from 'react-toast-notifications'; +import { useTranslation } from 'react-i18next'; const Login: React.FC = () => { + const { t } = useTranslation(); const { addToast } = useToasts(); const navigate = useNavigate(); const { token, setToken } = useToken(); @@ -27,7 +29,7 @@ const Login: React.FC = () => { ); if (request.username?.trim() === '' || request.password?.trim() === '') { - addToast(`Email or password are empty`, { + addToast(`${t('login.alert_failed_empty')}`, { appearance: 'error', autoDismiss: true, }); @@ -36,14 +38,17 @@ const Login: React.FC = () => { try { const response = await login(request); - addToast(`Welcome ${request.username}`, { + addToast(`${t('login.alert_welcome')} ${request.username}`, { appearance: 'info', autoDismiss: true, }); setToken(response); navigate('/admin/products'); } catch (e) { - addToast(`Login failed`, { appearance: 'error', autoDismiss: true }); + addToast(`${t('login.alert_failed_incorrect')}`, { + appearance: 'error', + autoDismiss: true, + }); } }; @@ -63,17 +68,17 @@ const Login: React.FC = () => { alt="logo" />

- Admin Panel - FI-Sweets + {t('login.title')}

-

Please login to your account

+

{t('login.subtitle')}

@@ -81,7 +86,7 @@ const Login: React.FC = () => { type="password" className="form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none" id="password" - placeholder="Password" + placeholder={t('login.password')} />
@@ -90,10 +95,12 @@ const Login: React.FC = () => { className="inline-block px-6 py-2.5 text-white font-medium text-xs leading-tight uppercase rounded shadow-md bg-blue-500 hover:bg-blue-700 hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full mb-3" // type="button" > - Log in + {t('login.login_btn')}
-
Forgot password?
+
+ {t('login.forgot_password')} +
diff --git a/src/components/Page/Sidebar.tsx b/src/components/Page/Sidebar.tsx index 5935e52..f4d3adc 100644 --- a/src/components/Page/Sidebar.tsx +++ b/src/components/Page/Sidebar.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; const Sidebar: React.FC = () => { + const { t } = useTranslation(); + return ( <>
@@ -25,7 +28,7 @@ const Sidebar: React.FC = () => { - Dashboard + {t('navigation.dashboard')}
  • @@ -44,7 +47,7 @@ const Sidebar: React.FC = () => { /> - Products + {t('navigation.products')} @@ -65,7 +68,7 @@ const Sidebar: React.FC = () => { /> - Recettes + {t('navigation.recipe')} diff --git a/src/components/Products/CreateProduct.tsx b/src/components/Products/CreateProduct.tsx index 450095b..6a19efd 100644 --- a/src/components/Products/CreateProduct.tsx +++ b/src/components/Products/CreateProduct.tsx @@ -5,17 +5,19 @@ import { createSweet } from '../../hooks/sweets/sweetsHooks'; import ProductModelRow from './ProductModelRow'; import { useQueryClient } from 'react-query'; import { useToasts } from 'react-toast-notifications'; +import { useTranslation } from 'react-i18next'; interface CreateProductProps { setOpenedModal: (openedModal: boolean) => void; } const CreateProduct: React.FC = ({ setOpenedModal }) => { + const { t } = useTranslation(); const queryClient = useQueryClient(); const { addToast } = useToasts(); const { mutate } = useMutation(createSweet, { onSuccess: async (data: ProductModelRow) => { - addToast(`Sweet ${data.name} created successfully`, { + addToast(`${t('products.add.alert_success')}: ${data.name}`, { appearance: 'success', autoDismiss: true, }); @@ -35,7 +37,7 @@ const CreateProduct: React.FC = ({ setOpenedModal }) => { const description = event.target.description.value; if (name === '' || price === '' || flavor === '' || description === '') { - addToast('Please fill out all fields', { + addToast(`${t('products.add.alert_failed_empty')}`, { appearance: 'error', autoDismiss: true, }); @@ -79,38 +81,38 @@ const CreateProduct: React.FC = ({ setOpenedModal }) => {

    - Product creation + {t('products.add.title')}

    @@ -172,11 +174,11 @@ const CreateProduct: React.FC = ({ setOpenedModal }) => { }} className="w-auto bg-gray-500 hover:bg-gray-700 rounded-lg shadow-xl font-medium text-white px-4 py-2" > - Cancel + {t('products.add.cancel_btn')}
    diff --git a/src/components/Products/ModifyProduct.tsx b/src/components/Products/ModifyProduct.tsx index fbb495c..764a858 100644 --- a/src/components/Products/ModifyProduct.tsx +++ b/src/components/Products/ModifyProduct.tsx @@ -8,6 +8,7 @@ import { import { useToasts } from 'react-toast-notifications'; import { useQueryClient } from 'react-query'; import UpdateSweetRequest from '../../hooks/sweets/requests/UpdateSweetRequest'; +import { useTranslation } from 'react-i18next'; interface ModifyProductProps { product: ProductModelRow; @@ -18,6 +19,7 @@ const ModifyProduct: React.FC = ({ product, setOpenedModal, }) => { + const { t } = useTranslation(); const queryClient = useQueryClient(); const { isLoading: isSweetLoading, @@ -46,7 +48,10 @@ const ModifyProduct: React.FC = ({ ); await queryClient.invalidateQueries(`sweet-${sweetData.id}`); - addToast(`Image uploaded`, { appearance: 'success', autoDismiss: true }); + addToast(`${t('products.update.alert_img_upload')}`, { + appearance: 'success', + autoDismiss: true, + }); } async function handleModifySweet(event: any) { @@ -73,10 +78,13 @@ const ModifyProduct: React.FC = ({ if (response) { await queryClient.invalidateQueries(`all-sweets`); await queryClient.invalidateQueries(`sweet-${sweetData.id}`); - addToast(`Sweet updated`, { appearance: 'success', autoDismiss: true }); + addToast(`${t('products.update.alert_success')}`, { + appearance: 'success', + autoDismiss: true, + }); setOpenedModal(false); } else { - addToast(`Error updating sweet`, { + addToast(`${t('products.update.alert_failed')}`, { appearance: 'error', autoDismiss: true, }); @@ -90,20 +98,20 @@ const ModifyProduct: React.FC = ({

    - Product modification + {t('products.update.title')}

    @@ -111,19 +119,19 @@ const ModifyProduct: React.FC = ({
    = ({
    = ({ }} className="w-auto bg-gray-500 hover:bg-gray-700 rounded-lg shadow-xl font-medium text-white px-4 py-2" > - Cancel + {t('products.update.cancel_btn')}
    diff --git a/src/components/Products/ProductList.tsx b/src/components/Products/ProductList.tsx index 036423e..3cdf767 100644 --- a/src/components/Products/ProductList.tsx +++ b/src/components/Products/ProductList.tsx @@ -7,8 +7,10 @@ import { useSweets } from '../../hooks/sweets/sweetsHooks'; import ModifyProduct from './ModifyProduct'; import PublishModal from './PublishModal'; import UnPublishModal from './UnPublishModal'; +import { useTranslation } from 'react-i18next'; const ProductList: React.FC = () => { + const { t } = useTranslation(); const [addModalState, setAddModalState] = useState(false); let { data: sweets } = useSweets(); @@ -23,26 +25,36 @@ const ProductList: React.FC = () => { onClick={() => setAddModalState(true)} className="py-2 px-4 shadow-md no-underline rounded-full bg-indigo-500 text-white font-sans font-semibold text-sm border-blue btn-primary hover:text-white hover:bg-blue-light focus:outline-none active:shadow-none mr-2" > - Add product + {t('products.add_btn')}
    -
    Tous les produits
    +
    {t('products.title_all')}
    -
    Produits en ligne
    +
    {t('products.title_online')}
    - - - - - + + + + + @@ -57,11 +69,21 @@ const ProductList: React.FC = () => {
    ProductPriceHighlightStatusActions + {t('products.col_name.product')} + + {t('products.col_name.price')} + + {t('products.col_name.highlight')} + + {t('products.col_name.status')} + + {t('products.col_name.actions')} +
    - - - - - + + + + + diff --git a/src/components/Products/PublishModal.tsx b/src/components/Products/PublishModal.tsx index 06fd4df..3a8db25 100644 --- a/src/components/Products/PublishModal.tsx +++ b/src/components/Products/PublishModal.tsx @@ -6,6 +6,7 @@ import { useQueryClient } from 'react-query'; import { useToasts } from 'react-toast-notifications'; import Lottie from 'react-lottie-player'; import animationJson from '../../assets/lotties/walking-celery.json'; +import { useTranslation } from 'react-i18next'; interface PublishModalProps { product: ProductModelRow; @@ -16,6 +17,7 @@ const PublishModal: React.FC = ({ product, setOpenedModal, }) => { + const { t } = useTranslation(); const queryClient = useQueryClient(); const { addToast } = useToasts(); @@ -24,7 +26,7 @@ const PublishModal: React.FC = ({ const result = await publishSweet(publishRequest); // refresh sweets await queryClient.invalidateQueries('all-sweets'); - addToast(`Sweet published ${result.name.value}`, { + addToast(`${t('products.publish.alert_success')}: ${result.name.value}`, { appearance: 'info', autoDismiss: true, }); @@ -47,14 +49,12 @@ const PublishModal: React.FC = ({ className="text-lg leading-6 font-medium text-gray-900" id="modal-title" > - Publier la mignadise + {t('products.publish.title')}
    {/*

    Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone.

    */}

    - Êtes-vous vraiment certain de vouloir publier la mignardise ? - Cette mignardise sera disponible à l'achat dans la boutique - après cette action. + {t('products.publish.description')}

    @@ -71,7 +71,7 @@ const PublishModal: React.FC = ({ type="button" className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-purple-600 text-base font-medium text-white hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:ml-3 sm:w-auto sm:text-sm" > - Publier + {t('products.publish.publish_btn')} - - {/**/} ); }; diff --git a/src/components/Products/UnPublishModal.tsx b/src/components/Products/UnPublishModal.tsx index 65837cf..0218fab 100644 --- a/src/components/Products/UnPublishModal.tsx +++ b/src/components/Products/UnPublishModal.tsx @@ -4,6 +4,7 @@ import UnPublishSweetRequest from '../../hooks/sweets/requests/UnPublishSweetReq import { unPublishSweet } from '../../hooks/sweets/sweetsHooks'; import { useQueryClient } from 'react-query'; import { useToasts } from 'react-toast-notifications'; +import { useTranslation } from 'react-i18next'; interface UnPublishModalProps { product: ProductModelRow; @@ -14,6 +15,7 @@ const UnPublishModal: React.FC = ({ product, setOpenedModal, }) => { + const { t } = useTranslation(); const queryClient = useQueryClient(); const { addToast } = useToasts(); @@ -22,7 +24,7 @@ const UnPublishModal: React.FC = ({ const result = await unPublishSweet(request); // refresh sweets await queryClient.invalidateQueries('all-sweets'); - addToast(`Sweet ${result.name.value} unpublished`, { + addToast(`${t('products.unpublish.alert_success')}: ${result.name.value}`, { appearance: 'info', autoDismiss: true, }); @@ -56,14 +58,12 @@ const UnPublishModal: React.FC = ({ className="text-lg leading-6 font-medium text-gray-900" id="modal-title" > - Retirer la mignadise de la boutique + {t('products.unpublish.title')}
    {/*

    Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone.

    */}

    - Êtes-vous vraiment certain de vouloir retirer la mignardise de - la boutique ? Cette mignardise ne sera plus disponible à - l'achat dans la boutique après cette action. + {t('products.unpublish.description')}

    @@ -75,7 +75,7 @@ const UnPublishModal: React.FC = ({ type="button" className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" > - Retirer + {t('products.unpublish.publish_btn')} - - {/**/} ); }; diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..31f57a0 --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,26 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import Backend from 'i18next-http-backend'; + +// this is what will detect the navigator language +// and apply the adequate translation id it exists or the default one +import LanguageDetector from 'i18next-browser-languagedetector'; + +i18n + // load translation using http into files in /public/locales + .use(Backend) + // detect user language + .use(LanguageDetector) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + .init({ + react: { + useSuspense: false, + wait: true, + }, + fallbackLng: 'fr', // we want french to be default + debug: true, + }); + +export default i18n; diff --git a/src/index.tsx b/src/index.tsx index b0a768f..4bfcc9c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import './i18n'; ReactDOM.render(
    ProductPriceHighlightStatusActions + {t('products.col_name.product')} + + {t('products.col_name.price')} + + {t('products.col_name.highlight')} + + {t('products.col_name.status')} + + {t('products.col_name.actions')} +