Skip to content

Commit

Permalink
[TO MAIN] Enable string translations for public view static text (#2423)
Browse files Browse the repository at this point in the history
* [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)
  • Loading branch information
VineetBala-AOT authored Mar 20, 2024
1 parent 3058891 commit 29ea71f
Show file tree
Hide file tree
Showing 55 changed files with 1,300 additions and 321 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions met-web/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
67 changes: 55 additions & 12 deletions met-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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<Translations>({});

useEffect(() => {
UserService.initKeycloak(dispatch);
Expand Down Expand Up @@ -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));
Expand All @@ -126,7 +167,7 @@ const App = () => {

useEffect(() => {
loadTranslation();
}, [tenant.id]);
}, [language.id, translations]);

if (authenticationLoading || tenant.loading) {
return <MidScreenLoader />;
Expand All @@ -151,7 +192,9 @@ const App = () => {
<Notification />
<NotificationModal />
<PublicHeader />
<UnauthenticatedRoutes />
<Routes>
<Route path="/:lang/*" element={<UnauthenticatedRoutes />} />
</Routes>
<FeedbackModal />
<Footer />
</Router>
Expand Down
57 changes: 20 additions & 37 deletions met-web/src/components/FormCAC/FirstTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import { TAB_TWO } from './constants';
import { When } from 'react-if';
import { Editor } from 'react-draft-wysiwyg';
import { getEditorStateFromRaw } from 'components/common/RichTextEditor/utils';

// Define the Yup schema for validation
const schema = yup.object({
understand: yup.boolean().oneOf([true], 'You must acknowledge this.'),
termsOfReference: yup.boolean().oneOf([true], 'You must acknowledge this.'),
});
import { useAppTranslation } from 'hooks';

interface FormData {
understand: boolean;
termsOfReference: boolean;
}

export const FirstTab: React.FC = () => {
const { t: translate } = useAppTranslation();
const { consentMessage, setTabValue, setFormSubmission } = useContext(FormContext);

// Define the Yup schema for validation
const schema = yup.object({
understand: yup.boolean().oneOf([true], translate('formCAC.schema.understand')),
termsOfReference: yup.boolean().oneOf([true], translate('formCAC.schema.termsOfReference')),
});

// Initialize form state and validation using react-hook-form
const {
handleSubmit,
Expand All @@ -49,39 +51,20 @@ export const FirstTab: React.FC = () => {
return (
<Grid container spacing={2}>
<Grid item xs={12}>
<MetLabel>What is a Community Advisory Committee (CAC)?</MetLabel>
<MetParagraph>
A Community Advisory Committee provides a venue for interested members of the public who have
information on the potential effects of a project on a community to stay up to date on the progress
of the environmental assessment and to be informed of opportunities to provide their input and
advice. Community Advisory Committee members could, for example, provide local knowledge of the
community, the environment or the use of the proposed project area.
</MetParagraph>
<MetLabel>{translate('formCAC.tab1.labels.0')}</MetLabel>
<MetParagraph>{translate('formCAC.tab1.paragraph.0')}</MetParagraph>
</Grid>
<Grid item xs={12}>
<MetParagraph>
The format and structure will depend on the potential effects of a project and community interest in
a project, amongst other considerations. The starting point for a community advisory committee in
every assessment (with sufficient community interest) will be an email subscription list.
</MetParagraph>
<MetParagraph>{translate('formCAC.tab1.paragraph.1')}</MetParagraph>
</Grid>

<Grid item xs={12}>
<MetLabel>What can I expect as a Community Advisory Committee Member?</MetLabel>
<MetParagraph>
The Environmental Assessment Office will provide subscribed Community Advisory Committee members
information on the environmental assessment process and the proposed project, including
notifications of process milestones, when and where key documents are posted, information on public
comment periods and any other engagement opportunities. Members will be invited to provide their
input through the public comment periods held throughout the environmental assessment and, depending
on the overall interest of Community Advisory Committee members, the Environmental Assessment Office
may directly seek the advice of Community Advisory Committee members and establish other engagement
opportunities. See the Community Advisory Committee Guideline for further information.
</MetParagraph>
<MetLabel>{translate('formCAC.tab1.labels.1')}</MetLabel>
<MetParagraph>{translate('formCAC.tab1.paragraph.2')}</MetParagraph>
</Grid>

<Grid item xs={12}>
<MetLabel>I understand that...</MetLabel>
<MetLabel>{translate('formCAC.tab1.labels.2')}</MetLabel>
</Grid>
<Grid item xs={12}>
<Editor editorState={getEditorStateFromRaw(consentMessage)} readOnly={true} toolbarHidden />
Expand All @@ -97,9 +80,7 @@ export const FirstTab: React.FC = () => {
render={({ field }) => <Checkbox {...field} />}
/>
}
label={
<MetLabel>By checking this box, I acknowledge that I understand the above text.</MetLabel>
}
label={<MetLabel>{translate('formCAC.tab1.labels.3')}</MetLabel>}
/>
<When condition={Boolean(errors.understand)}>
<FormHelperText
Expand All @@ -122,9 +103,9 @@ export const FirstTab: React.FC = () => {
}
label={
<MetLabel>
By checking this box, I acknowledge that I have read, understood, and will abide by the{' '}
{translate('formCAC.tab1.labels.4')}
<Link href="https://www2.gov.bc.ca/assets/gov/environment/natural-resource-stewardship/environmental-assessments/guidance-documents/2018-act/community_advisory_committee_guideline_v1.pdf">
Community Advisory Committee Terms of Reference.
{translate('formCAC.tab1.link.text')}
</Link>
</MetLabel>
}
Expand All @@ -144,7 +125,9 @@ export const FirstTab: React.FC = () => {
</Grid>

<Grid item xs={12}>
<PrimaryButton onClick={handleSubmit(handleNextClick)}>Next</PrimaryButton>
<PrimaryButton onClick={handleSubmit(handleNextClick)}>
{translate('formCAC.tab1.button.next')}
</PrimaryButton>
</Grid>
</Grid>
);
Expand Down
7 changes: 5 additions & 2 deletions met-web/src/components/FormCAC/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { Grid } from '@mui/material';
import { Banner } from 'components/banner/Banner';
import LandingPageBanner from 'assets/images/LandingPageBanner.png';
import { Tabs } from './Tabs';
import { useAppTranslation } from 'hooks';

export const Form = () => {
const { t: translate } = useAppTranslation();

return (
<Grid container direction="row" justifyContent={'center'} alignItems="center">
<Grid item xs={12}>
Expand Down Expand Up @@ -40,10 +43,10 @@ export const Form = () => {
rowSpacing={2}
>
<Grid item xs={12}>
<MetHeader1>Community Advisory Committee</MetHeader1>
<MetHeader1>{translate('formCAC.form.header')}</MetHeader1>
</Grid>
<Grid item xs={12}>
<MetParagraph>Learn about and sign up for a Community Advisory Committee</MetParagraph>
<MetParagraph>{translate('formCAC.form.paragraph')}</MetParagraph>
</Grid>
</Grid>
</Grid>
Expand Down
34 changes: 25 additions & 9 deletions met-web/src/components/FormCAC/FormContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getSubscriptionsForms } from 'services/subscriptionService';
import { SUBSCRIBE_TYPE, SubscribeForm } from 'models/subscription';
import { openNotificationModal } from 'services/notificationModalService/notificationModalSlice';
import { getSlugByEngagementId } from 'services/engagementSlugService';
import { useAppTranslation } from 'hooks';

export interface CACFormSubmssion {
understand: boolean;
Expand Down Expand Up @@ -56,6 +57,7 @@ export const FormContext = createContext<FormContextProps>({
consentMessage: '',
});
export const FormContextProvider = ({ children }: { children: JSX.Element }) => {
const { t: translate } = useAppTranslation();
const { widgetId, engagementId } = useParams<{ widgetId: string; engagementId: string }>();
const [tabValue, setTabValue] = useState(TAB_ONE);
const [formSubmission, setFormSubmission] = useState<CACFormSubmssion>({
Expand All @@ -73,7 +75,6 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) =>
const [consentMessage, setConsentMessage] = useState<string>('');
const dispatch = useDispatch();
const navigate = useNavigate();

const loadEngagement = async () => {
if (isNaN(Number(engagementId))) {
return;
Expand All @@ -84,7 +85,10 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) =>
return engagement;
} catch (err) {
dispatch(
openNotification({ severity: 'error', text: 'An error occured while trying to load the engagement' }),
openNotification({
severity: 'error',
text: translate('formCAC.formContentNotification.engagementError'),
}),
);
navigate('/');
}
Expand All @@ -100,17 +104,26 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) =>

return subscriptionForms.find((form) => form.type === SUBSCRIBE_TYPE.SIGN_UP);
} catch (err) {
dispatch(openNotification({ severity: 'error', text: 'An error occured while trying to load the widget' }));
dispatch(
openNotification({ severity: 'error', text: translate('formCAC.formContentNotification.widgetError') }),
);
navigate('/');
}
};

const verifyData = (_engagement?: Engagement, subscribeWidget?: SubscribeForm) => {
if (!_engagement || !subscribeWidget) {
dispatch(openNotification({ severity: 'error', text: 'An error occured while trying to load the form' }));
dispatch(
openNotification({ severity: 'error', text: translate('formCAC.formContentNotification.formError') }),
);
navigate('/');
} else if (_engagement.engagement_status.id === EngagementStatus.Draft) {
dispatch(openNotification({ severity: 'error', text: 'Cannot submit this form at this time' }));
dispatch(
openNotification({
severity: 'error',
text: translate('formCAC.formContentNotification.unknownError'),
}),
);
navigate('/');
}
setLoading(false);
Expand Down Expand Up @@ -153,12 +166,13 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) =>
openNotificationModal({
open: true,
data: {
header: 'Thank you',
header: translate('formCAC.formContentNotification.success.header'),
subText: [
{
text:
`We have received your request to join the Community Advisory Committee for ` +
`${engagement?.name}. You will be notified of future updates to the Community Advisory Committee by email.`,
translate('formCAC.formContentNotification.success.text.0') +
`${engagement?.name}` +
translate('formCAC.formContentNotification.success.text.1'),
},
],
},
Expand All @@ -169,7 +183,9 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) =>
} catch (err) {
setSubmitting(false);
console.log(err);
dispatch(openNotification({ severity: 'error', text: 'An error occured while trying to submit the form' }));
dispatch(
openNotification({ severity: 'error', text: translate('formCAC.formContentNotification.submitError') }),
);
}
};

Expand Down
Loading

0 comments on commit 29ea71f

Please sign in to comment.