Skip to content

Commit

Permalink
Merge pull request #367 from City-of-Helsinki/HAUKI-610-multi-lang-su…
Browse files Browse the repository at this point in the history
…pport

HAUKI-610 Add multi language support for most of resource page and batch update…
  • Loading branch information
timwessman authored Feb 6, 2024
2 parents 589e16b + 2e23094 commit b28af57
Show file tree
Hide file tree
Showing 20 changed files with 764 additions and 173 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
!.eslintrc.js
!tsconfig.eslint.json
!.prettierrc.json
!.eslintignore
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.eslintrc.js
src/i18n.js
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,25 @@
"@types/react-dom": "^17.0.1",
"@types/react-router-dom": "^5.3.2",
"@types/testing-library__dom": "7.5.0",
"@typescript-eslint/parser": "^6.20.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"axios": "^0.21.2",
"cypress-axe": "^0.12.1",
"date-fns": "2.29.3",
"date-holidays": "^3.16.1",
"enzyme": "^3.11.0",
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
"hds-core": "^3.1.0",
"hds-design-tokens": "^3.1.0",
"hds-react": "^3.1.0",
"hds-core": "^3.4.0",
"hds-design-tokens": "^3.4.0",
"hds-react": "^3.4.0",
"i18next": "23.7.11",
"lodash": "^4.17.21",
"prettier": "^2.1.1",
"react": "^17.0.1",
"react-axe": "^3.5.3",
"react-dom": "^17.0.1",
"react-hook-form": "7.34.2",
"react-i18next": "^13.5.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.0",
"react-test-renderer": "^16.13.1",
Expand Down Expand Up @@ -98,12 +101,12 @@
]
},
"devDependencies": {
"@playwright/test": "^1.40.1",
"@testing-library/jest-dom": "^5.11.4",
"cypress": "^6.5.0",
"eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-jsx-a11y": "^6.3.1",
"jest-environment-jsdom-sixteen": "^1.0.3",
"@playwright/test": "^1.40.1",
"start-server-and-test": "^1.11.3",
"stylelint": "^13.7.2",
"stylelint-config-standard": "^20.0.0",
Expand Down
18 changes: 18 additions & 0 deletions src/components/header/HaukiHeader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ import { AppContext } from '../../App-context';
import { AuthContext } from '../../auth/auth-context';
import HaukiHeader from './HaukiHeader';

jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
return {
t: (str: string) => str,
i18n: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
changeLanguage: () => new Promise(() => {}),
},
};
},
initReactI18next: {
type: '3rdParty',
// eslint-disable-next-line @typescript-eslint/no-empty-function
init: () => {},
},
}));

describe('<HaukiHeader>', () => {
afterEach(() => {
jest.clearAllMocks();
Expand Down
3 changes: 3 additions & 0 deletions src/components/header/HaukiHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Logo,
Link,
} from 'hds-react';
import { useTranslation } from 'react-i18next';
import api from '../../common/utils/api/api';
import { useAppContext } from '../../App-context';
import { AuthContextProps, TokenKeys, useAuth } from '../../auth/auth-context';
Expand All @@ -20,6 +21,7 @@ import { languageOptions } from '../../constants';
const HaukiHeader = (): JSX.Element => {
const { hasOpenerWindow, closeAppWindow, setLanguage } = useAppContext();
const authProps: Partial<AuthContextProps> = useAuth();
const { i18n } = useTranslation();
const { authTokens, clearAuth } = authProps;
const history = useHistory();
const isAuthenticated = !!authTokens;
Expand Down Expand Up @@ -71,6 +73,7 @@ const HaukiHeader = (): JSX.Element => {
className="header"
languages={languageOptions}
onDidChangeLanguage={(language) => {
i18n.changeLanguage(language);
if (setLanguage) {
const newLanguage =
Language[language.toUpperCase() as keyof typeof Language];
Expand Down
132 changes: 82 additions & 50 deletions src/components/opening-period-accordion/OpeningPeriodAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useAccordion,
} from 'hds-react';
import React, { ReactNode, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ConfirmationModal, useModal } from '../modal/ConfirmationModal';
import toast from '../notification/Toast';
Expand All @@ -19,27 +20,6 @@ import {
useSelectedDatePeriodsContext,
} from '../../common/selectedDatePeriodsContext/SelectedDatePeriodsContext';

type DeleteModalTextProps = {
dateRange?: ReactNode | null;
periodName?: string | null;
};

const DeleteModalText = ({
dateRange,
periodName,
}: DeleteModalTextProps): JSX.Element => (
<>
<p>Olet poistamassa aukiolojakson</p>
<p>
<b>
{periodName}
<br />
{dateRange}
</b>
</p>
</>
);

type AccordionIconProps = {
isOpen: boolean;
};
Expand All @@ -62,6 +42,7 @@ const OpeningPeriodActionsMenu = React.forwardRef<
});
const actionsMenuRef = useRef<HTMLDivElement>(null);
useOnClickOutside(actionsMenuRef, closeAccordion);
const { t } = useTranslation();

return (
<div ref={actionsMenuRef} className="opening-period-actions-menu">
Expand All @@ -71,18 +52,30 @@ const OpeningPeriodActionsMenu = React.forwardRef<
type="button"
{...buttonProps}>
<IconMenuDots aria-hidden="true" />
<span className="visually-hidden">{`Avaa ${
periodName || 'nimetön'
} aukiolojakson muokkaa ja poista valikko`}</span>
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.OpenPeriodEditMenu', {
periodName,
})
: t(
'ResourcePage.OpeningPeriodsSection.OpenUntitledPeriodEditMenu'
)}
</span>
</button>
{isOpen && (
<div className="opening-period-actions-menu-items">
{editUrl && (
<Link className="opening-period-actions-menu-item" to={editUrl}>
Muokkaa
<span className="visually-hidden">{`Muokkaa ${
periodName || 'nimettömän'
} aukiolojakson tietoja`}</span>
{t('ResourcePage.OpeningPeriodsSection.Modify')}
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.ModifyPeriod', {
periodName,
})
: t(
'ResourcePage.OpeningPeriodsSection.ModifyUntitledPeriod'
)}
</span>
</Link>
)}
{onDelete && (
Expand All @@ -93,10 +86,16 @@ const OpeningPeriodActionsMenu = React.forwardRef<
onDelete();
}}
type="button">
Poista
<span className="visually-hidden">{`Poista ${
periodName || 'nimetön'
} aukiolojakso`}</span>
{t('ResourcePage.OpeningPeriodsSection.Remove')}
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.RemovePeriod', {
periodName,
})
: t(
'ResourcePage.OpeningPeriodsSection.RemoveUntitledPeriod'
)}
</span>
</button>
)}
</div>
Expand Down Expand Up @@ -130,7 +129,10 @@ const OpeningPeriodAccordion = ({
toggleChecked,
checked,
}: Props): JSX.Element => {
const deleteModalTitle = 'Oletko varma että haluat poistaa aukiolojakson?';
const { t } = useTranslation();
const deleteModalTitle = t(
'ResourcePage.OpeningPeriodsSection.DeleteModalTitle'
);
const { isModalOpen, openModal, closeModal } = useModal();
const { buttonProps, isOpen } = useAccordion({
initiallyOpen,
Expand Down Expand Up @@ -169,9 +171,11 @@ const OpeningPeriodAccordion = ({
{isActive && (
<StatusLabel
className="opening-period-active"
aria-label="Voimassa nyt"
aria-label={t(
'ResourcePage.OpeningPeriodsSection.StatusLabelActive'
)}
type="info">
Voimassa nyt
{t('ResourcePage.OpeningPeriodsSection.StatusLabelActive')}
</StatusLabel>
)}
</div>
Expand All @@ -185,9 +189,15 @@ const OpeningPeriodAccordion = ({
data-test={`openingPeriodEditLink${dataTestPostFix}`}
to={editUrl}>
<IconPenLine aria-hidden="true" />
<span className="visually-hidden">{`Muokkaa ${
periodName || 'nimettömän'
} aukiolojakson tietoja`}</span>
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.ModifyPeriod', {
periodName,
})
: t(
'ResourcePage.OpeningPeriodsSection.ModifyUntitledPeriod'
)}
</span>
</Link>
)}
{onDelete && (
Expand All @@ -198,9 +208,15 @@ const OpeningPeriodAccordion = ({
type="button"
onClick={openModal}>
<IconTrash aria-hidden="true" />
<span className="visually-hidden">{`Poista ${
periodName || 'nimetön'
} aukiolojakso`}</span>
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.RemovePeriod', {
periodName,
})
: t(
'ResourcePage.OpeningPeriodsSection.RemoveUntitledPeriod'
)}
</span>
</button>
)}
</>
Expand All @@ -223,9 +239,13 @@ const OpeningPeriodAccordion = ({
type="button"
{...buttonProps}>
<AccordionIcon isOpen={isOpen} />
<span className="visually-hidden">{`Näytä ${
periodName || 'nimetön'
}`}</span>
<span className="visually-hidden">
{periodName
? t('ResourcePage.OpeningPeriodsSection.ShowPediod', {
periodName,
})
: t('ResourcePage.OpeningPeriodsSection.ShowUntitledPeriod')}
</span>
</button>
</div>
<ConfirmationModal
Expand All @@ -236,29 +256,41 @@ const OpeningPeriodAccordion = ({
await onDelete();
setDeleting(false);
toast.success({
label: `Aukiolo "${periodName}" poistettu onnistuneesti.`,
label: t('ResourcePage.Notifications.PeriodRemoveSuccess', {
periodName,
}),
dataTestId: 'date-period-delete-success',
});
} catch (_) {
toast.error({
label:
'Aukiolon poisto epäonnistui. Yritä myöhemmin uudelleen.',
label: t('ResourcePage.Notifications.PeriodRemoveFailed', {
periodName,
}),
});
}
}
}}
isLoading={isDeleting}
loadingText="Poistetaan aukiolojaksoa"
loadingText={t('ResourcePage.OpeningPeriodsSection.RemovingPeriod')}
title={deleteModalTitle}
text={
<DeleteModalText periodName={periodName} dateRange={dateRange} />
<>
<p>{t('ResourcePage.OpeningPeriodsSection.RemoveModalText')}</p>
<p>
<b>
{periodName}
<br />
{dateRange}
</b>
</p>
</>
}
isOpen={isModalOpen}
onClose={() => {
closeModal();
deleteRef.current?.focus();
}}
confirmText="Poista"
confirmText={t('ResourcePage.OpeningPeriodsSection.Remove')}
/>
</div>
{isOpen && children}
Expand Down
20 changes: 20 additions & 0 deletions src/components/opening-period/OpeningPeriod.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ import {
import OpeningPeriod from './OpeningPeriod';
import { SelectedDatePeriodsProvider } from '../../common/selectedDatePeriodsContext/SelectedDatePeriodsContext';

jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
return {
t: (str: string) => str,
i18n: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
changeLanguage: () => new Promise(() => {}),
},
};
},
initReactI18next: {
type: '3rdParty',
// eslint-disable-next-line @typescript-eslint/no-empty-function
init: () => {},
},
}));

const testDatePeriod: DatePeriod = { ...datePeriod, isActive: false };
const testDatePeriodOptions: UiDatePeriodConfig = datePeriodOptions;

Expand All @@ -35,4 +53,6 @@ describe(`<OpeningPeriod />`, () => {
await container.querySelector('div[data-test="openingPeriod-1"]')
).toBeInTheDocument();
});

jest.clearAllMocks();
});
4 changes: 3 additions & 1 deletion src/components/opening-period/OpeningPeriod.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Language,
UiDatePeriodConfig,
Expand Down Expand Up @@ -36,6 +37,7 @@ const OpeningPeriod = ({
removeDatePeriods,
datePeriodSelectState,
} = useSelectedDatePeriodsContext();
const { t } = useTranslation();

const toggleChecked =
datePeriodSelectState === DatePeriodSelectState.ACTIVE
Expand Down Expand Up @@ -66,7 +68,7 @@ const OpeningPeriod = ({
isActive={datePeriod.isActive}>
<div className="date-period-details-container">
{datePeriod.resourceState === ResourceState.CLOSED ? (
'Suljettu'
t('ResourcePage.OpeningPeriodsSection.StateClosed')
) : (
<OpeningHoursPreview
openingHours={datePeriod.openingHours}
Expand Down
Loading

0 comments on commit b28af57

Please sign in to comment.