From 29ea71f01fa5ad562393128b61ba31fbd49dfb4f Mon Sep 17 00:00:00 2001 From: VineetBala-AOT <90332175+VineetBala-AOT@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:57:52 -0700 Subject: [PATCH] [TO MAIN] Enable string translations for public view static text (#2423) * [To Feature] DESENG-467 Remaining public pages to fetch static text from the translation file. (#2420) * [To Feature] DESENG-467 Enable string translations for public view static text (#2417) --- CHANGELOG.MD | 9 +- met-web/sample.env | 3 + met-web/src/App.tsx | 67 ++++- met-web/src/components/FormCAC/FirstTab.tsx | 57 ++-- met-web/src/components/FormCAC/Form.tsx | 7 +- .../src/components/FormCAC/FormContext.tsx | 34 ++- met-web/src/components/FormCAC/SecondTab.tsx | 24 +- met-web/src/components/FormCAC/Tabs.tsx | 6 +- .../components/common/LanguageSelector.tsx | 104 +++++++ .../dashboard/comment/CommentTable.tsx | 8 +- .../dashboard/comment/CommentsBlock.tsx | 27 +- .../engagement/view/SurveyBlock.tsx | 13 +- .../widgets/Subscribe/FormSignUpSection.tsx | 3 +- .../ManageSubscription/Subscription.tsx | 16 +- .../feedback/FeedbackModal/index.tsx | 26 +- .../src/components/landing/EngagementTile.tsx | 13 +- .../components/landing/LandingComponent.tsx | 18 +- .../src/components/layout/Footer/index.tsx | 39 +-- .../components/layout/Header/PublicHeader.tsx | 21 +- .../components/publicDashboard/Dashboard.tsx | 34 ++- .../components/publicDashboard/ErrorBox.tsx | 7 +- .../publicDashboard/KPI/ProjectLocation.tsx | 6 +- .../publicDashboard/KPI/SurveyEmailsSent.tsx | 6 +- .../publicDashboard/KPI/SurveysCompleted.tsx | 6 +- .../src/components/publicDashboard/NoData.tsx | 5 +- .../SubmissionTrend/SubmissionTrend.tsx | 18 +- .../SurveyBar/QuestionBlock.tsx | 6 +- .../publicDashboard/SurveyBar/index.tsx | 22 +- .../SurveyBarPrintable/index.tsx | 6 +- .../components/survey/edit/ActionContext.tsx | 16 +- .../src/components/survey/edit/EditForm.tsx | 8 +- .../components/survey/edit/FormWrapped.tsx | 3 +- .../survey/edit/InvalidTokenModal.tsx | 15 +- .../survey/submit/ActionContext.tsx | 19 +- .../survey/submit/InvalidTokenModal.tsx | 17 +- .../components/survey/submit/SurveyForm.tsx | 8 +- .../survey/submit/SurveySubmitWrapped.tsx | 5 +- met-web/src/config.ts | 5 + met-web/src/constants/language.ts | 7 + met-web/src/locales/en/default.json | 265 +++++++++++++++- met-web/src/locales/en/eao.json | 29 -- met-web/src/locales/en/gdx.json | 284 ++++++++++++++++++ met-web/src/reduxSlices/languageSlice.ts | 33 ++ met-web/src/routes/NotAvailable.tsx | 12 +- met-web/src/routes/NotFound.tsx | 104 ++++--- met-web/src/services/userService/index.ts | 4 +- met-web/src/store.ts | 2 + .../unit/components/FormCAC/FormCAC.test.tsx | 21 ++ .../landingPage/LandingPage.test.tsx | 12 +- .../components/layout/PublicHeader.test.tsx | 53 ++++ .../publicDashboard/ProjectLocation.test.tsx | 15 + .../publicDashboard/PublicDashboard.test.tsx | 33 +- .../publicDashboard/SubmissionTrend.test.tsx | 16 + .../publicDashboard/SurveyEmailsSent.test.tsx | 12 + .../publicDashboard/SurveysCompleted.test.tsx | 12 + 55 files changed, 1300 insertions(+), 321 deletions(-) create mode 100644 met-web/src/components/common/LanguageSelector.tsx create mode 100644 met-web/src/constants/language.ts delete mode 100644 met-web/src/locales/en/eao.json create mode 100644 met-web/src/locales/en/gdx.json create mode 100644 met-web/src/reduxSlices/languageSlice.ts create mode 100644 met-web/tests/unit/components/layout/PublicHeader.test.tsx diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 741973b95..f02e9400a 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,4 +1,10 @@ -<<<<<<< HEAD +## March 19, 2024 + +- **Task**: Change static english text to be able to support string translations [DESENG-467](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-467) + - Implemented a language selector in the public header. + - Incorporated logic to dynamically adjust the unauthenticated route based on the selected language and load the appropriate translation file. + - Enhanced all public pages to fetch static text from the translation file. + ## March 15, 2024 - **Task**: Multi-language - Create event, subcribe_item, poll, timeline widget translation tables & API routes [DESENG-515](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-515) @@ -9,7 +15,6 @@ - Added Unit tests. - **Task** Add "Results" page to engagements [DESENG-512](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-512) - ## March 08, 2024 - **Task**: Multi-language - Create engagement translation table & API routes [DESENG-510](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-510) diff --git a/met-web/sample.env b/met-web/sample.env index 30b6433ba..21222f3aa 100644 --- a/met-web/sample.env +++ b/met-web/sample.env @@ -28,6 +28,9 @@ REACT_APP_ANALYTICS_API_URL=http://localhost:5001/api # Users visiting the root URL will be redirected to this tenant REACT_APP_DEFAULT_TENANT=gdx +# Users visiting the root URL will be redirected to this language of default tenant +REACT_APP_DEFAULT_LANGUAGE_ID=en + # Whether to skip certain auth checks. Should be false in production. # Must match the value set for IS_SINGLE_TENANT_ENVIRONMENT in the API. REACT_APP_IS_SINGLE_TENANT_ENVIRONMENT=false \ No newline at end of file diff --git a/met-web/src/App.tsx b/met-web/src/App.tsx index f74512a21..5aa53f544 100644 --- a/met-web/src/App.tsx +++ b/met-web/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import './App.scss'; import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; import UserService from './services/userService'; @@ -20,9 +20,15 @@ import NotFound from 'routes/NotFound'; import Footer from 'components/layout/Footer'; import { ZIndex } from 'styles/Theme'; import { TenantState, loadingTenant, saveTenant } from 'reduxSlices/tenantSlice'; +import { LanguageState } from 'reduxSlices/languageSlice'; import { openNotification } from 'services/notificationService/notificationSlice'; import i18n from './i18n'; import DocumentTitle from 'DocumentTitle'; +import { Language } from 'constants/language'; + +interface Translations { + [languageId: string]: { [key: string]: string }; +} const App = () => { const drawerWidth = 280; @@ -32,9 +38,10 @@ const App = () => { const isLoggedIn = useAppSelector((state) => state.user.authentication.authenticated); const authenticationLoading = useAppSelector((state) => state.user.authentication.loading); const pathSegments = window.location.pathname.split('/'); - const language = 'en'; // Default language is English, change as needed + const language: LanguageState = useAppSelector((state) => state.language); const basename = pathSegments[1].toLowerCase(); const tenant: TenantState = useAppSelector((state) => state.tenant); + const [translations, setTranslations] = useState({}); useEffect(() => { UserService.initKeycloak(dispatch); @@ -82,36 +89,70 @@ const App = () => { if (basename) { fetchTenant(basename); + if (pathSegments.length === 2) { + const defaultLanguage = AppConfig.language.defaultLanguageId; // Set the default language here + const defaultUrl = `/${basename}/${defaultLanguage}`; + window.location.replace(defaultUrl); + } return; } if (!basename && AppConfig.tenant.defaultTenant) { - window.location.replace(`/${AppConfig.tenant.defaultTenant}`); + const defaultLanguage = AppConfig.language.defaultLanguageId; // Set the default language here + const defaultUrl = `/${AppConfig.tenant.defaultTenant}/${defaultLanguage}`; + window.location.replace(defaultUrl); } dispatch(loadingTenant(false)); }; - const getTranslationFile = async () => { + const preloadTranslations = async () => { + if (!tenant.id) { + return; + } + + try { + const supportedLanguages = Object.values(Language); + const translationPromises = supportedLanguages.map((languageId) => getTranslationFile(languageId)); + const translationFiles = await Promise.all(translationPromises); + + const translationsObj: Translations = {}; + + translationFiles.forEach((file, index) => { + if (file) { + translationsObj[supportedLanguages[index]] = file.default; + } + }); + + setTranslations(translationsObj); + } catch (error) { + console.error('Error preloading translations:', error); + } + }; + + const getTranslationFile = async (languageId: string) => { try { - const translationFile = await import(`./locales/${language}/${tenant.id}.json`); + const translationFile = await import(`./locales/${languageId}/${tenant.id}.json`); return translationFile; } catch (error) { - const defaultTranslationFile = await import(`./locales/${language}/default.json`); + const defaultTranslationFile = await import(`./locales/${languageId}/default.json`); return defaultTranslationFile; } }; + useEffect(() => { + preloadTranslations(); + }, [tenant.id]); // Preload translations when tenant id changes + const loadTranslation = async () => { - if (!tenant.id) { + if (!tenant.id || !translations[language.id]) { return; } - i18n.changeLanguage(language); // Set the language for react-i18next + i18n.changeLanguage(language.id); // Set the language for react-i18next try { - const translationFile = await getTranslationFile(); - i18n.addResourceBundle(language, tenant.id, translationFile); + i18n.addResourceBundle(language.id, tenant.id, translations[language.id]); dispatch(loadingTenant(false)); } catch (error) { dispatch(loadingTenant(false)); @@ -126,7 +167,7 @@ const App = () => { useEffect(() => { loadTranslation(); - }, [tenant.id]); + }, [language.id, translations]); if (authenticationLoading || tenant.loading) { return ; @@ -151,7 +192,9 @@ const App = () => { - + + } /> +