From fd8a6612592927d26a0733ce5bbaab1c161986b0 Mon Sep 17 00:00:00 2001 From: godsakani <70459251+godsakani@users.noreply.github.com> Date: Sun, 25 Sep 2022 09:19:08 +0100 Subject: [PATCH] Language feature Implementation (#137) * feat: Added language menu item on side nav * file: added package-lock json file * packages: Added packages for language translation * package: added new yarn package * feat: remove commented links * file: Added translation file * feat: Added translation for loading screen. * feat: added radio button for switching languages * feat: added translation for links data item * file: Added json file for english translation * file: Added json file for french translation * feat: added translation feature * feat: Make used of translation file * feat: make used of translation file * feat: added language selector * feat: Added translation for pages --- frontend/package.json | 4 + frontend/public/index.html | 3 +- frontend/src/assets/data/Items.data.ts | 9 +- frontend/src/assets/data/Links.data.ts | 14 +- frontend/src/assets/translation/en/en.json | 34 + frontend/src/assets/translation/fr/fr.json | 33 + .../cityDetail/CityDetail.component.tsx | 7 +- .../notFound/NotFound.component.tsx | 11 +- .../components/search/Search.component.tsx | 1 - frontend/src/i18n.js | 38 + frontend/src/index.tsx | 9 +- frontend/src/languageSelect.js | 18 + frontend/src/pages/advices/Advices.tsx | 9 +- frontend/src/pages/detail/Detail.page.tsx | 9 +- frontend/src/pages/home/Home.pages.tsx | 18 +- .../src/pages/homeModal/HomeModal.pages.tsx | 9 +- frontend/src/pages/list/List.pages.tsx | 6 +- .../src/pages/streetMap/StreetMap.page.tsx | 6 +- package-lock.json | 1416 +++++++++++++++++ 19 files changed, 1607 insertions(+), 47 deletions(-) create mode 100644 frontend/src/assets/translation/en/en.json create mode 100644 frontend/src/assets/translation/fr/fr.json create mode 100644 frontend/src/i18n.js create mode 100644 frontend/src/languageSelect.js create mode 100644 package-lock.json diff --git a/frontend/package.json b/frontend/package.json index 358e2bf2..86ece00f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,11 +5,15 @@ "dependencies": { "axios": "^0.22.0", "eslint": "^7.11.0", + "i18next": "^21.9.0", + "i18next-browser-languagedetector": "^6.1.5", + "i18next-http-backend": "^1.4.1", "leaflet": "^1.7.1", "rc-drawer": "^4.4.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hooks-use-modal": "^2.1.0", + "react-i18next": "^11.18.3", "react-leaflet": "^3.2.2", "react-modern-drawer": "^0.1.1", "react-router-dom": "^5.3.0", diff --git a/frontend/public/index.html b/frontend/public/index.html index 918e471b..b2b7a2d0 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -26,7 +26,6 @@
- - + diff --git a/frontend/src/assets/data/Items.data.ts b/frontend/src/assets/data/Items.data.ts index fa3a378e..5e28a308 100644 --- a/frontend/src/assets/data/Items.data.ts +++ b/frontend/src/assets/data/Items.data.ts @@ -1,9 +1,8 @@ import { LANGUAGE } from "../../constants/language"; -import home from "../img/home.png"; -import conseils from "../img/conseils.png"; -import listes from "../img/liste.png"; -import language from "../img/language.png"; -import report from "../img/alert.png"; +import home from "../img/home.png" +import conseils from "../img/conseils.png" +import listes from "../img/liste.png" +import language from "../img/language.png" export const ITEMS = [ { diff --git a/frontend/src/assets/data/Links.data.ts b/frontend/src/assets/data/Links.data.ts index fce9bc1b..c6e514ea 100644 --- a/frontend/src/assets/data/Links.data.ts +++ b/frontend/src/assets/data/Links.data.ts @@ -1,20 +1,22 @@ +import i18n from "../../i18n"; + export const LINKS = [ { - title: "Que faire en cas de panne de courant ", + title: i18n.t("adviceTitle2"), content: [ - "S'agit-il d'une panne de courant qui n'affecte que les appareils individuels ? Vos voisins ont également eu une coupure de courant ? Il est possible qu'un seul fusible ait grillé et que vous puissiez résoudre le problème vous-même.", + i18n.t("adviceContent"), ] }, { - title: "Comportement après une panne de courant ", + title:i18n.t( "adviceTitle3"), content: [ - "Dès que le courant est rétabli, dans la plupart des cas, il y a d'abord un soulagement. Mais il y a certaines choses que vous devez garder à l'esprit même après une panne de courant.", + i18n.t("adviceContent1"), ] }, { - title: "Raisons d'une panne de courant ", + title: i18n.t("adviceTitle4"), content: [ - "En cas de panne de courant, le plus important est de rétablir l'alimentation. Pour la plupart des personnes touchées par une panne de courant, cela peut être réalisé au moyen de mesures de commutation dans le réseau électrique. L'opérateur du réseau ne connaît souvent la raison exacte de la panne de courant qu'un certain temps plus tard.", + i18n.t("adviceContent2"), ] } ] \ No newline at end of file diff --git a/frontend/src/assets/translation/en/en.json b/frontend/src/assets/translation/en/en.json new file mode 100644 index 00000000..e1dd5a7a --- /dev/null +++ b/frontend/src/assets/translation/en/en.json @@ -0,0 +1,34 @@ +{ + "select_language": "Select Language", + "hello_welcome_to_react": "Hello, Welcome to React", + "this_is_an_example": "This is an example", + "please_enter_name": "Please enter your name", + "loading": "loading...", + "panel":"Electrical failure", + "reportOutage" :"Report a power outage", + "accept" : "accept", + "notification" : "This site uses cookies to make your visit to our site as pleasant as possible. We use your location to show you reports for your area.", + "info" : "Official Information on Network Operator Interference", + "title" : "Where is the current power outage? Network operators map", + "search" : "Enter the region", + "home" : "Home page", + "info1" : "Advice and information", + "view" : "List view", + "city" : "Enter your neighborhood", + "empty" : "No alerts for this region", + "location" : "show on map", + "notAvailable" : "This content is not available at the moment", + "warning" : "This problem usually stems from the owner only sharing it with a small group of people, changing who can see it, or deleting it.", + "homePage" : "Go to the home page", + "title1" : "Power outage, now what?", + "contents" : "A power outage is a sudden and rare event. However, in the event of a power outage, it is advisable to remain calm at first. In addition, you must pay attention to a few elements of your household and notify us of the power outage.", + "link" : "Quick links:", + "adviceContent" : "Is it a power outage that only affects individual devices? Have your neighbors also had a power outage? It is possible that only one fuse has blown and you can fix the problem yourself.", + "adviceContent1" : "As soon as the power is restored, in most cases there is relief first. But there are some things you need to keep in mind even after a power outage.", + "adviceContent2" : "In the event of a power outage, the most important thing is to restore power. For most people affected by a power outage, this can be achieved through switching measures in the power grid. The network operator often does not know the exact reason for the power failure until some time later.", + "adviceTitle2" : "What to do in the event of a power outage", + "adviceTitle3" : "Behavior after a power failure", + "adviceTitle4" : "Reasons for a power outage" + + } + \ No newline at end of file diff --git a/frontend/src/assets/translation/fr/fr.json b/frontend/src/assets/translation/fr/fr.json new file mode 100644 index 00000000..6ff50eb8 --- /dev/null +++ b/frontend/src/assets/translation/fr/fr.json @@ -0,0 +1,33 @@ +{ + "select_language": "Choisir la langue", + "hello_welcome_to_react": "Bonjour, Bienvenue chez React", + "this_is_an_example": "Ceci est un exemple", + "please_enter_name": "Veuillez saisir le nom", + "loading" : "Chargement.....", + "panel":"Panne électrique", + "reportOutage" :"Signaler une panne de courant", + "accept" : "J'accepte", + "notification" : "Ce site utilise des cookies pour rendre votre visite sur notre site aussi agréable que possible. Nous utilisons votre emplacement pour vous montrer les rapports de votre région.", + "info" : "Informations officielles sur Interférences des opérateurs de réseau", + "title" : "Où est la panne de courant actuelle? Carte des opérateurs de réseau", + "search" : "Entrez la region", + "home" : "Page d'accueil", + "info1" : "Conseils et informations", + "view" : "Vue liste", + "city" : "Entrer votre quartier", + "empty" : "Aucune alerte pour cette region", + "location" : "afficher sur la carte", + "notAvailable" : "Ce contenu n’est pas disponible pour le moment", + "warning" : "Ce problème vient généralement du fait que le propriétaire ne l’a partagé qu’avec un petit groupe de personnes, a modifié qui pouvait le voir ou l’a supprimé.", + "homePage" : "Acceder a la page d'accueil", + "title1" : "Panne de courant, et maintenant ? ", + "contents" : "Une panne de courant est un événement soudain et rare. Cependant, en cas de panne de courant, il est conseillé de rester calme dans un premier temps. De plus, vous devez faire attention à quelques éléments de votre foyer et nous signaler la panne de courant. ", + "link" : "Liens rapides:", + "adviceContent" : "S'agit-il d'une panne de courant qui n'affecte que les appareils individuels ? Vos voisins ont également eu une coupure de courant ? Il est possible qu'un seul fusible ait grillé et que vous puissiez résoudre le problème vous-même.", + "adviceContent1" : "Dès que le courant est rétabli, dans la plupart des cas, il y a d'abord un soulagement. Mais il y a certaines choses que vous devez garder à l'esprit même après une panne de courant.", + "adviceContent2" : "En cas de panne de courant, le plus important est de rétablir l'alimentation. Pour la plupart des personnes touchées par une panne de courant, cela peut être réalisé au moyen de mesures de commutation dans le réseau électrique. L'opérateur du réseau ne connaît souvent la raison exacte de la panne de courant qu'un certain temps plus tard.", + "advicetitle2" : "Que faire en cas de panne de courant ", + "advicetitle3" : "Comportement après une panne de courant ", + "advicetitle4" : "Raisons d'une panne de courant " + } + \ No newline at end of file diff --git a/frontend/src/components/cityDetail/CityDetail.component.tsx b/frontend/src/components/cityDetail/CityDetail.component.tsx index f83d5458..cff93c8e 100644 --- a/frontend/src/components/cityDetail/CityDetail.component.tsx +++ b/frontend/src/components/cityDetail/CityDetail.component.tsx @@ -8,8 +8,11 @@ import city from "../../assets/img/city.png"; import home from "../../assets/img/home.png" import { LANGUAGE } from "../../constants/language"; import { Link } from "react-router-dom"; +import StreetMap from "../../pages/streetMap/StreetMap.page"; +import { useTranslation } from "react-i18next"; export const CityDetail = (props: any) => { + const { t } = useTranslation(); return (
@@ -63,8 +66,8 @@ export const CityDetail = (props: any) => { }&long=${props.long}`}> diff --git a/frontend/src/components/notFound/NotFound.component.tsx b/frontend/src/components/notFound/NotFound.component.tsx index 7ac904e8..e935175b 100644 --- a/frontend/src/components/notFound/NotFound.component.tsx +++ b/frontend/src/components/notFound/NotFound.component.tsx @@ -1,24 +1,25 @@ import React from "react"; import { Link } from "react-router-dom"; import error from "../../assets/img/error.png"; -import { LANGUAGE } from "../../constants/language"; -import { Footer } from "../footer/Footer.component"; import { MyText } from "../myText/MyText.component"; +import { useTranslation } from "react-i18next"; +import { Footer } from "../footer/Footer.component"; export const NotFound = () => { + const { t } = useTranslation(); return (
error
@@ -29,7 +30,7 @@ export const NotFound = () => { className="text-gray-300" style={{ fontFamily: " 'Varela Round', sans-serif" }} > - {LANGUAGE.notfound.homePage} + {t("homePage")}

diff --git a/frontend/src/components/search/Search.component.tsx b/frontend/src/components/search/Search.component.tsx index 6b482cf4..095fc52c 100644 --- a/frontend/src/components/search/Search.component.tsx +++ b/frontend/src/components/search/Search.component.tsx @@ -1,6 +1,5 @@ import React from "react"; import search from "../../assets/img/search.png"; -import { LANGUAGE } from "../../constants/language"; export const Search = (props: any) => { return ( diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js new file mode 100644 index 00000000..3086ff8c --- /dev/null +++ b/frontend/src/i18n.js @@ -0,0 +1,38 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; + +// Importing translation files + +import translationEN from "./assets/translation/en/en.json"; +import translationFR from "./assets/translation/fr/fr.json"; + +//Creating object with the variables of imported translation files +const resources = { + en: { + translation: translationEN, + }, + fr: { + translation: translationFR, + }, +}; + +//i18N Initialization + +i18n.use(initReactI18next).init({ + resources, + lng: "en", //default language + keySeparator: false, + interpolation: { + escapeValue: false, + }, +}); +i18n.init({ + // ... other config + + react: { + // Turn off the use of React Suspense + useSuspense: false + } + }); + +export default i18n; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 52fa417a..970e8514 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,21 +4,22 @@ import "./index.css"; import reportWebVitals from "./reportWebVitals"; import { AppNavigation } from "./constants/navigations/App.navigation"; import { RecoilRoot } from "recoil"; -import { LANGUAGE } from "./constants/language"; +import './i18n'; +import i18n from "./i18n"; ReactDOM.render( - - {LANGUAGE.loading} + {i18n.t('loading')}

} > -
+
, document.getElementById("root") ); diff --git a/frontend/src/languageSelect.js b/frontend/src/languageSelect.js new file mode 100644 index 00000000..b441443b --- /dev/null +++ b/frontend/src/languageSelect.js @@ -0,0 +1,18 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +const LanguageSelector = () => { + + const { i18n } = useTranslation(); + + const changeLanguage = (event) => { + i18n.changeLanguage(event.target.value); + }; + return ( +
+ English + French +
+ ); +}; +export default LanguageSelector; \ No newline at end of file diff --git a/frontend/src/pages/advices/Advices.tsx b/frontend/src/pages/advices/Advices.tsx index 1ec2de8f..9bdc2122 100644 --- a/frontend/src/pages/advices/Advices.tsx +++ b/frontend/src/pages/advices/Advices.tsx @@ -4,9 +4,10 @@ import { Accordion } from "../../components/accordion/Accordion.component"; import { MyDrawer } from "../../components/drawer/Drawer.component"; import {Footer} from "../../components/footer/Footer.component"; import { MyText } from "../../components/myText/MyText.component"; -import { LANGUAGE } from "../../constants/language"; +import { useTranslation } from "react-i18next"; const Advices = () => { + const {t} = useTranslation(); return (
@@ -15,19 +16,19 @@ const Advices = () => {
{LINKS.map((i: any) => ( diff --git a/frontend/src/pages/detail/Detail.page.tsx b/frontend/src/pages/detail/Detail.page.tsx index 12f728e9..61f3dcea 100644 --- a/frontend/src/pages/detail/Detail.page.tsx +++ b/frontend/src/pages/detail/Detail.page.tsx @@ -5,14 +5,15 @@ import { MyText } from "../../components/myText/MyText.component"; import home from "../../assets/img/hotel.png"; import lists from "../../assets/img/list.png"; import { Search } from "../../components/search/Search.component"; -import { LANGUAGE } from "../../constants/language"; import { Link } from "react-router-dom"; import boxImg from "../../assets/img/box.png"; import { getDetails } from "../../atoms/details"; import { useRecoilValue } from "recoil"; import { NotFound } from "../../components/notFound/NotFound.component"; +import { useTranslation } from "react-i18next"; const Detail = () => { + const { t } = useTranslation(); const { search } = window.location; const details: any = useRecoilValue(getDetails); const query = new URLSearchParams(search).get("s"); @@ -49,7 +50,7 @@ const Detail = () => {
@@ -63,7 +64,7 @@ const Detail = () => { }} className="text-white pt-4" > - {LANGUAGE.empty} + {t("empty")}

@@ -91,7 +92,7 @@ const Detail = () => {
diff --git a/frontend/src/pages/home/Home.pages.tsx b/frontend/src/pages/home/Home.pages.tsx index 3b9ca83c..8cc2efc0 100644 --- a/frontend/src/pages/home/Home.pages.tsx +++ b/frontend/src/pages/home/Home.pages.tsx @@ -1,19 +1,21 @@ import React, { useEffect, useState } from "react"; import "react-modern-drawer/dist/index.css"; import { MyDrawer } from "../../components/drawer/Drawer.component"; -import { LANGUAGE } from "../../constants/language"; import bolt from "../../assets/img/bolt.png"; import { Link } from "react-router-dom"; import HomeModal from "../homeModal/HomeModal.pages"; import StreetMap from "../streetMap/StreetMap.page"; import { useRecoilValue } from "recoil"; import { panneBtnState } from "../../atoms/panne_btn"; +import LanguageSelector from "../../languageSelect"; +import { useTranslation } from "react-i18next"; const Home = () => { + const { t } = useTranslation(); let open = localStorage.getItem("modalValue"); let panneBtnZIndex = useRecoilValue(panneBtnState); useEffect(() => { - document.title = LANGUAGE.home.title; + document.title = t("title") }); if (open === null) { return ( @@ -37,7 +39,7 @@ const Home = () => { alt="" className="w-6 h-6 mr-2" /> - {LANGUAGE.home.panne} + {t("panel")}

@@ -59,7 +61,7 @@ const Home = () => { className="w-6 h-6 mr-2" /> - {LANGUAGE.home.signalerPanne} + {t("reportOutage")}

@@ -77,6 +79,10 @@ const Home = () => {
+
+ +
+

{ alt="" className="w-6 h-6 mr-2" /> - {LANGUAGE.home.panne} + {t("panel")}

@@ -115,7 +121,7 @@ const Home = () => { className="w-6 h-6 mr-2" /> - {LANGUAGE.home.signalerPanne} + {t("reportOutage")}

diff --git a/frontend/src/pages/homeModal/HomeModal.pages.tsx b/frontend/src/pages/homeModal/HomeModal.pages.tsx index 0a98ac00..44886fbc 100644 --- a/frontend/src/pages/homeModal/HomeModal.pages.tsx +++ b/frontend/src/pages/homeModal/HomeModal.pages.tsx @@ -3,9 +3,10 @@ import { useModal } from "react-hooks-use-modal"; import { useRecoilState } from "recoil"; import { modalState } from "../../atoms/modal"; import { MyText } from "../../components/myText/MyText.component"; -import { LANGUAGE } from "../../constants/language"; +import { useTranslation } from "react-i18next"; const HomeModal = () => { + const { t } = useTranslation(); let [Modal, open, close, isOpen] = useModal("root", { preventScroll: true, closeOnOverlayClick: false, @@ -23,14 +24,14 @@ const HomeModal = () => {
@@ -44,7 +45,7 @@ const HomeModal = () => { style={{ fontFamily: " 'Varela Round', sans-serif" }} className="flex cursor-pointer px-4 py-1 text-sm text-center text-gray-200" > - {LANGUAGE.homeModal.accept} + {t("accept")}

diff --git a/frontend/src/pages/list/List.pages.tsx b/frontend/src/pages/list/List.pages.tsx index 3cf5342e..f1e71b39 100644 --- a/frontend/src/pages/list/List.pages.tsx +++ b/frontend/src/pages/list/List.pages.tsx @@ -10,12 +10,14 @@ import {getRegions} from "../../atoms/regions"; import {NotFound} from "../../components/notFound/NotFound.component"; import {Link} from "react-router-dom"; import accueil from "../../assets/img/accueil.png"; +import { useTranslation } from "react-i18next"; import {getCities} from "../../atoms/cities"; import {getAlerts} from "../../atoms/alerts"; import {Footer} from "../../components/footer/Footer.component"; const List = () => { + const { t } = useTranslation(); const {search} = window.location; const query = new URLSearchParams(search).get("s"); const [searchQuery, setSearchQuery] = useState(query || ""); @@ -46,7 +48,7 @@ const List = () => { }); }; useEffect(() => { - setRegion((region) => (region = uniqueRegion)); + setRegion((region) => (region = (uniqueRegion))); }, []); const filteredRegions = filteRegions(region, searchQuery); return ( @@ -68,7 +70,7 @@ const List = () => {
diff --git a/frontend/src/pages/streetMap/StreetMap.page.tsx b/frontend/src/pages/streetMap/StreetMap.page.tsx index b3a626aa..a794ac08 100644 --- a/frontend/src/pages/streetMap/StreetMap.page.tsx +++ b/frontend/src/pages/streetMap/StreetMap.page.tsx @@ -16,6 +16,7 @@ import { zoomLevelState } from "../../atoms/zom_leve"; import { Modal } from "../../modals/Modals"; import { panneBtnState } from "../../atoms/panne_btn"; import { getCities } from "../../atoms/cities"; +import LanguageSelect from "../../languageSelect"; import {getDetails} from "../../atoms/details"; function MyComponent() { @@ -93,6 +94,7 @@ const StreetMap = (props: any) => { zoomControl={false} scrollWheelZoom={true} > + { zoom={v} zoomControl={false} scrollWheelZoom={true} - > + >