From ae290c10ead4ebe59e3185297bf8368eff6949aa Mon Sep 17 00:00:00 2001 From: timwessman Date: Wed, 11 Sep 2024 13:13:30 +0300 Subject: [PATCH] feat: add language support in all missing places (#402) * feat: add language support in all missing places --- e2e/tests/resource-page.spec.ts | 32 ++-- src/components/button/Button.tsx | 6 +- .../ExceptionOpeningHoursForm.tsx | 57 +++--- .../ExceptionOpeningHoursStateToggle.tsx | 14 +- .../ExceptionOpeningHoursValidity.tsx | 20 ++- .../HolidayOpeningHours.tsx | 6 +- .../holidays-table/HolidaysTable.test.tsx | 82 +++++++++ .../holidays-table/HolidaysTable.tsx | 17 +- src/components/link/Link.tsx | 2 +- src/components/modal/ConfirmationModal.tsx | 6 +- .../NormalOpeningHoursForm.tsx | 99 +++++----- .../NormalOpeningHoursValidity.tsx | 26 +-- src/components/notification/Toast.tsx | 3 +- .../OpeningHoursFormPreview.tsx | 39 ++-- .../OpeningHoursFormPreviewMobile.tsx | 4 +- .../opening-hours-form/OpeningHoursForm.tsx | 12 +- .../OpeningHoursFormActions.tsx | 8 +- .../OpeningHoursTitles.test.tsx | 60 +++++++ .../opening-hours-form/OpeningHoursTitles.tsx | 169 +++++++++--------- .../opening-hours-weekdays/DayCheckbox.tsx | 6 +- .../OpeningHoursWeekdays.tsx | 55 +++--- .../OpeningPeriodAccordion.tsx | 8 +- .../opening-period/OpeningPeriod.test.tsx | 2 +- .../OpeningPeriodsSection.tsx | 2 +- .../resource-title/ResourceTitle.tsx | 2 +- src/components/time-span/TimeSpan.tsx | 80 +++++---- src/components/time-span/TimeSpans.tsx | 8 +- src/{i18n.js => i18n.ts} | 0 src/index.tsx | 1 - src/language/en.json | 88 +++++++++ src/language/fi.json | 87 +++++++++ src/language/sv.json | 89 ++++++++- src/pages/AddExceptionOpeningHoursPage.tsx | 4 +- src/pages/AddNormalOpeningHoursPage.tsx | 4 +- src/pages/EditExceptionOpeningHoursPage.tsx | 4 +- src/pages/EditHolidaysPage.tsx | 69 ++++--- src/pages/EditNormalOpeningHoursPage.tsx | 4 +- src/pages/ResourcePage.test.tsx | 6 +- src/pages/ResourcePage.tsx | 4 +- 39 files changed, 838 insertions(+), 347 deletions(-) create mode 100644 src/components/holidays-table/HolidaysTable.test.tsx create mode 100644 src/components/opening-hours-form/OpeningHoursTitles.test.tsx rename src/{i18n.js => i18n.ts} (100%) diff --git a/e2e/tests/resource-page.spec.ts b/e2e/tests/resource-page.spec.ts index f863ea65d..1daa3463a 100644 --- a/e2e/tests/resource-page.spec.ts +++ b/e2e/tests/resource-page.spec.ts @@ -47,12 +47,12 @@ test.describe('Resource page', async () => { const titleFi = 'e2e test title'; await page.getByRole('button', { name: 'Lisää aukioloaika +' }).click(); - await page.locator('[data-test="opening-period-title-fi"]').fill(titleFi); + await page.locator('[data-testid="opening-period-title-fi"]').fill(titleFi); await page - .locator('[data-test="opening-period-title-sv"]') + .locator('[data-testid="opening-period-title-sv"]') .fill('sommartid'); await page - .locator('[data-test="opening-period-title-en"]') + .locator('[data-testid="opening-period-title-en"]') .fill('summertime'); await page .getByLabel('Maanantai-Perjantai') @@ -67,12 +67,12 @@ test.describe('Resource page', async () => { .getByPlaceholder('E.g. seniors') .fill('seniors'); await page.getByText('24 h').click(); - await page.locator('[data-test="submit-opening-hours-button"]').click(); + await page.locator('[data-testid="submit-opening-hours-button"]').click(); await expect(page.getByRole('heading', { name: titleFi })).toBeVisible(); const removeButton = page .locator( - '[data-test="resource-opening-periods-list"] .opening-period-action-delete' + '[data-testid="resource-opening-periods-list"] .opening-period-action-delete' ) .last(); await removeButton.click(); @@ -92,20 +92,20 @@ test.describe('Resource page', async () => { const titleFi = 'e2e test title'; await page.getByRole('button', { name: 'Lisää poikkeava päivä +' }).click(); - await page.locator('[data-test="opening-period-title-fi"]').fill(titleFi); + await page.locator('[data-testid="opening-period-title-fi"]').fill(titleFi); await page - .locator('[data-test="opening-period-title-sv"]') + .locator('[data-testid="opening-period-title-sv"]') .fill('utbildngingstad'); await page - .locator('[data-test="opening-period-title-en"]') + .locator('[data-testid="opening-period-title-en"]') .fill('training day'); - await page.locator('[data-test="submit-opening-hours-button"]').click(); + await page.locator('[data-testid="submit-opening-hours-button"]').click(); await expect(page.getByTestId('opening-period-form-success')).toBeVisible(); const removeButton = page .locator( - '[data-test="resource-exception-opening-periods-list"] .opening-period-action-delete' + '[data-testid="resource-exception-opening-periods-list"] .opening-period-action-delete' ) .last(); await removeButton.click(); @@ -129,12 +129,12 @@ test.describe('Resource page', async () => { .first(); const holidayTestId = await holidayInput.getAttribute('data-test'); - await page.locator(`[data-test="${holidayTestId}"]`).check(); - await page.locator('[data-test="submit-opening-hours-button"]').click(); + await page.locator(`[data-testid="${holidayTestId}"]`).check(); + await page.locator('[data-testid="submit-opening-hours-button"]').click(); await expect(page.getByTestId('holiday-form-success')).toBeVisible({ timeout: 30 * 1000, }); - await page.locator(`[data-test="${holidayTestId}"]`).click(); + await page.locator(`[data-testid="${holidayTestId}"]`).click(); await expect(page.locator('#confirmation-modal')).toBeVisible(); await page.getByRole('button', { name: 'Poista', exact: true }).click(); @@ -150,7 +150,7 @@ test.describe('Resource page', async () => { .first(); const holidayTestId = await holidayInput.getAttribute('data-test'); - await page.locator(`[data-test="${holidayTestId}"]`).check(); + await page.locator(`[data-testid="${holidayTestId}"]`).check(); await page.getByText('Poikkeava aukioloaika').click(); await page.getByLabel('Auki', { exact: true }).click(); await page.getByRole('option', { name: 'Itsepalvelu' }).click(); @@ -158,11 +158,11 @@ test.describe('Resource page', async () => { await page.getByPlaceholder('Esim. seniorit').fill('seniorit'); await page.getByPlaceholder('T.ex. seniorer').fill('seniorer'); await page.getByPlaceholder('E.g. seniors').fill('seniors'); - await page.locator('[data-test="submit-opening-hours-button"]').click(); + await page.locator('[data-testid="submit-opening-hours-button"]').click(); await expect(page.getByTestId('holiday-form-success')).toBeVisible({ timeout: 30 * 1000, }); - await page.locator(`[data-test="${holidayTestId}"]`).click(); + await page.locator(`[data-testid="${holidayTestId}"]`).click(); await expect( page.getByRole('heading', { name: 'Oletko varma että haluat' }) ).toBeVisible(); diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index 78882e992..09bf57715 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -61,7 +61,7 @@ export const SecondaryButton = React.forwardRef< } ${className}`} theme={light ? 'default' : 'coat'} size={size} - data-test={dataTest} + data-testid={dataTest} disabled={disabled} variant="secondary" onClick={onClick} @@ -100,7 +100,7 @@ export const PrimaryButton = React.forwardRef( +): JSX.Element => { + const { t } = useTranslation(); + + const config: FormConfig = { + exception: true, + defaultValues: { + startDate: formatDate(new Date().toISOString()), + endDate: formatDate(new Date().toISOString()), + fixed: true, + name: { fi: '', sv: '', en: '' }, + override: true, + resourceState: ResourceState.CLOSED, + openingHours: [], }, - title: { - placeholders: { - fi: 'Esim. koulutuspäivä', - sv: 'T.ex. utbildningsdag', - en: 'E.g. training day', + texts: { + submit: { + notifications: { + success: t('OpeningHours.ExceptionPeriodSubmitSuccess'), + error: t('OpeningHours.ExceptionPeriodSubmitError'), + }, + }, + title: { + placeholders: { + fi: 'Esim. koulutuspäivä', + sv: 'T.ex. utbildningsdag', + en: 'E.g. training day', + }, }, }, - }, -}; + }; -const ExceptionOpeningHoursForm = ( - props: Omit -): JSX.Element => { return ; }; diff --git a/src/components/exception-opening-hours-state-toggle/ExceptionOpeningHoursStateToggle.tsx b/src/components/exception-opening-hours-state-toggle/ExceptionOpeningHoursStateToggle.tsx index c3d839bd6..8dcaca4eb 100644 --- a/src/components/exception-opening-hours-state-toggle/ExceptionOpeningHoursStateToggle.tsx +++ b/src/components/exception-opening-hours-state-toggle/ExceptionOpeningHoursStateToggle.tsx @@ -1,4 +1,5 @@ import React, { ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { RadioButton, SelectionGroup } from 'hds-react'; import { useFormContext } from 'react-hook-form'; import { DatePeriod } from '../../common/lib/types'; @@ -18,6 +19,7 @@ const ExceptionOpeningHoursStateToggle = ({ onClose: () => void; onOpen: () => void; }): JSX.Element => { + const { t } = useTranslation(); const [isOpen, setOpen] = useState(initiallyOpen); const { watch } = useFormContext(); const [startDate, endDate] = watch(['startDate', 'endDate']); @@ -29,22 +31,26 @@ const ExceptionOpeningHoursStateToggle = ({ className="exception-opening-hours-resource-state-toggle" label=""> 0 ? 'Suljettu' : 'Suljettu koko päivän'} + label={ + differenceInDays > 0 + ? t('OpeningHours.ExceptionStateClosed') + : t('OpeningHours.ExceptionStateClosedWholeDay') + } onChange={(): void => { setOpen(false); onClose(); }} /> { setOpen(true); onOpen(); diff --git a/src/components/exception-opening-hours-validity/ExceptionOpeningHoursValidity.tsx b/src/components/exception-opening-hours-validity/ExceptionOpeningHoursValidity.tsx index 3e6c3f37c..056fee32e 100644 --- a/src/components/exception-opening-hours-validity/ExceptionOpeningHoursValidity.tsx +++ b/src/components/exception-opening-hours-validity/ExceptionOpeningHoursValidity.tsx @@ -1,4 +1,5 @@ import { DateInput, SelectionGroup } from 'hds-react'; +import { useTranslation } from 'react-i18next'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { useAppContext } from '../../App-context'; @@ -13,6 +14,7 @@ import ExceptionOpeningHoursStateToggle from '../exception-opening-hours-state-t import './ExceptionOpeningHoursValidity.scss'; const ExceptionOpeningHoursValidity = (): JSX.Element => { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const { getValues, setValue, watch } = useFormContext(); const [startDate, endDate, resourceState] = watch([ @@ -26,7 +28,7 @@ const ExceptionOpeningHoursValidity = (): JSX.Element => {
{ { @@ -58,14 +60,14 @@ const ExceptionOpeningHoursValidity = (): JSX.Element => { } }} onBlur={onBlur} - openButtonAriaLabel="Valitse alkupäivämäärä" + openButtonAriaLabel={t('OpeningHours.ExceptionStartDateAria')} required value={value} crossOrigin={undefined} /> )} rules={{ - required: 'Pakollinen', + required: t('Common.Mandatory'), validate: isValidDate, }} /> @@ -79,24 +81,24 @@ const ExceptionOpeningHoursValidity = (): JSX.Element => { )} rules={{ - required: 'Pakollinen', + required: t('Common.Mandatory'), validate: { validDate: isValidDate, endDateAfterStartDate: endDateAfterStartDate(getValues), diff --git a/src/components/holiday-opening-hours/HolidayOpeningHours.tsx b/src/components/holiday-opening-hours/HolidayOpeningHours.tsx index 6fb9703c4..927aebb6a 100644 --- a/src/components/holiday-opening-hours/HolidayOpeningHours.tsx +++ b/src/components/holiday-opening-hours/HolidayOpeningHours.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { DatePeriod, ResourceState, @@ -15,11 +16,12 @@ const HolidayOpeningHours = ({ datePeriod, datePeriodConfig, }: Props): JSX.Element => { + const { t } = useTranslation(); if (datePeriod) { return (
{datePeriod.resourceState === ResourceState.CLOSED - ? 'Suljettu' + ? t('ResourcePage.OpeningPeriodsSection.StateClosed') : datePeriod.openingHours.map((openingHours) => openingHours.timeSpanGroups.map((timeSpanGroup) => timeSpanGroup.timeSpans.map((timeSpan, timeSpanIdx) => ( @@ -38,7 +40,7 @@ const HolidayOpeningHours = ({ ); } - return <>Ei poikkeavia aukioloja; + return <>{t('OpeningHours.NoExceptions')}; }; export default HolidayOpeningHours; diff --git a/src/components/holidays-table/HolidaysTable.test.tsx b/src/components/holidays-table/HolidaysTable.test.tsx new file mode 100644 index 000000000..b842dc075 --- /dev/null +++ b/src/components/holidays-table/HolidaysTable.test.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import HolidaysTable from './HolidaysTable'; +import { Holiday } from '../../services/holidays'; +import { useAppContext } from '../../App-context'; +import { Language } from '../../common/lib/types'; + +import { SelectedDatePeriodsProvider } from '../../common/selectedDatePeriodsContext/SelectedDatePeriodsContext'; + +// Mock useTranslation hook +vi.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: () => {}, + }, +})); + +// Mock useAppContext hook +vi.mock('../../App-context', () => ({ + useAppContext: vi.fn(), +})); + +const mockHolidays: Holiday[] = [ + { + date: '2023-12-25', + name: { + fi: 'Joulupäivä', + sv: 'Juldagen', + en: 'Christmas Day', + }, + }, + { + date: '2023-01-06', + name: { + en: 'Epiphany', + fi: 'Loppiainen', + sv: 'Trettondedag jul', + }, + }, +]; + +const renderWithLanguage = (language: Language) => { + vi.mocked(useAppContext).mockReturnValue({ language }); + render( + + + + ); +}; + +describe('HolidaysTable', () => { + test('renders HolidaysTable component in Finnish', () => { + renderWithLanguage(Language.FI); + expect(screen.getAllByText('Joulupäivä')[0]).toBeInTheDocument(); + }); + + test('renders HolidaysTable component in Swedish', () => { + renderWithLanguage(Language.SV); + expect(screen.getAllByText('Juldagen')[0]).toBeInTheDocument(); + }); + + test('renders HolidaysTable component in English', () => { + renderWithLanguage(Language.EN); + expect(screen.getAllByText('Christmas Day')[0]).toBeInTheDocument(); + }); +}); diff --git a/src/components/holidays-table/HolidaysTable.tsx b/src/components/holidays-table/HolidaysTable.tsx index aa470c133..49f0672a5 100644 --- a/src/components/holidays-table/HolidaysTable.tsx +++ b/src/components/holidays-table/HolidaysTable.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Checkbox } from 'hds-react'; import { useAppContext } from '../../App-context'; import { isHolidayOrEve } from '../../common/helpers/opening-hours-helpers'; @@ -29,6 +30,7 @@ export const UpcomingHolidayNotification = ({ datePeriods: DatePeriod[]; holidays: Holiday[]; }): JSX.Element => { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const nextHoliday = holidays.find(isHoliday); @@ -41,7 +43,8 @@ export const UpcomingHolidayNotification = ({ return (
- Seuraava juhlapyhä: {nextHoliday.name[language]} + {t('OpeningHours.NextHoliday')} + {nextHoliday.name[language]} { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const { selectedDatePeriods, toggleDatePeriod, datePeriodSelectState } = useSelectedDatePeriodsContext(); @@ -85,11 +89,10 @@ const HolidaysTable = ({ }>

- Seuraavat juhlapyhät + {t('ResourcePage.HolidaysTableTitle')}

- Muista tarkistaa juhlapyhien aikataulut vuosittain – esimerkiksi - pääsiäisen juhlapyhien ajankohta vaihtelee. + {t('ResourcePage.HolidaysTableDescription')}

- Juhlapyhä + {t('ResourcePage.HolidaysTableHolidayColumn')}
- Päivämäärä + {t('ResourcePage.HolidaysTableDateColumn')}
- Aukiolo + {t('ResourcePage.HolidaysTableOpeningHoursColumn')}
diff --git a/src/components/link/Link.tsx b/src/components/link/Link.tsx index 988bc872c..3966b803a 100644 --- a/src/components/link/Link.tsx +++ b/src/components/link/Link.tsx @@ -11,7 +11,7 @@ export function Link({ dataTest?: string; }): JSX.Element { return ( - + {text} ); diff --git a/src/components/modal/ConfirmationModal.tsx b/src/components/modal/ConfirmationModal.tsx index 6a4d12507..a5dbefd84 100644 --- a/src/components/modal/ConfirmationModal.tsx +++ b/src/components/modal/ConfirmationModal.tsx @@ -1,4 +1,5 @@ import React, { ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Dialog, IconQuestionCircle } from 'hds-react'; import { PrimaryButton, SecondaryButton } from '../button/Button'; @@ -38,6 +39,7 @@ export function ConfirmationModal({ text: string | ReactNode; title: string; }): JSX.Element | null { + const { t } = useTranslation(); const titleId = 'confirmation-modal-title'; return ( @@ -55,7 +57,9 @@ export function ConfirmationModal({ onClick={onConfirm}> {confirmText} - Peruuta + + {t('Common.Cancel')} + ); diff --git a/src/components/normal-opening-hours-form/NormalOpeningHoursForm.tsx b/src/components/normal-opening-hours-form/NormalOpeningHoursForm.tsx index 177cf0661..0906e3f59 100644 --- a/src/components/normal-opening-hours-form/NormalOpeningHoursForm.tsx +++ b/src/components/normal-opening-hours-form/NormalOpeningHoursForm.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ResourceState } from '../../common/lib/types'; import { formatDate } from '../../common/utils/date-time/format'; import { defaultTimeSpan, defaultTimeSpanGroup } from '../../constants'; @@ -7,59 +8,61 @@ import OpeningHoursForm, { OpeningHoursFormProps, } from '../opening-hours-form/OpeningHoursForm'; -const config: FormConfig = { - exception: false, - defaultValues: { - fixed: false, - endDate: null, - startDate: formatDate(new Date().toISOString()), - name: { - fi: null, - sv: null, - en: null, - }, - openingHours: [ - { - weekdays: [1, 2, 3, 4, 5], - timeSpanGroups: [defaultTimeSpanGroup], - }, - { - weekdays: [6, 7], - timeSpanGroups: [ - { - ...defaultTimeSpanGroup, - timeSpans: [ - { - ...defaultTimeSpan, - resource_state: ResourceState.CLOSED, - }, - ], - }, - ], - }, - ], - override: false, - }, - texts: { - submit: { - notifications: { - success: 'Aukiolon tallennus onnistui', - error: 'Aukiolon tallennus epäonnistui', +const NormalOpeningHoursForm = ( + props: Omit +): JSX.Element => { + const { t } = useTranslation(); + + const config: FormConfig = { + exception: false, + defaultValues: { + fixed: false, + endDate: null, + startDate: formatDate(new Date().toISOString()), + name: { + fi: null, + sv: null, + en: null, }, + openingHours: [ + { + weekdays: [1, 2, 3, 4, 5], + timeSpanGroups: [defaultTimeSpanGroup], + }, + { + weekdays: [6, 7], + timeSpanGroups: [ + { + ...defaultTimeSpanGroup, + timeSpans: [ + { + ...defaultTimeSpan, + resource_state: ResourceState.CLOSED, + }, + ], + }, + ], + }, + ], + override: false, }, - title: { - placeholders: { - fi: 'Esim. kesäkausi', - sv: 'T.ex. sommartid', - en: 'E.g. summertime', + texts: { + submit: { + notifications: { + success: t('OpeningHours.SubmitSuccessNotification'), + error: t('OpeningHours.SubmitErrorNotification'), + }, + }, + title: { + placeholders: { + fi: t('TitlePlaceholderInFinnish'), + sv: t('TitlePlaceholderInSwedish'), + en: t('TitlePlaceholderInEnglish'), + }, }, }, - }, -}; + }; -const NormalOpeningHoursForm = ( - props: Omit -): JSX.Element => { return ; }; diff --git a/src/components/normal-opening-hours-validity/NormalOpeningHoursValidity.tsx b/src/components/normal-opening-hours-validity/NormalOpeningHoursValidity.tsx index 75580a0c3..0abbccf9c 100644 --- a/src/components/normal-opening-hours-validity/NormalOpeningHoursValidity.tsx +++ b/src/components/normal-opening-hours-validity/NormalOpeningHoursValidity.tsx @@ -1,5 +1,6 @@ import { DateInput, RadioButton, SelectionGroup } from 'hds-react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Controller, useFormContext } from 'react-hook-form'; import { useAppContext } from '../../App-context'; import { Language, DatePeriod } from '../../common/lib/types'; @@ -10,6 +11,7 @@ import { import './NormalOpeningHoursValidity.scss'; const NormalOpeningHoursValidity = (): JSX.Element => { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const { control, getValues, watch } = useFormContext(); const [startDate, endDate, fixed] = watch(['startDate', 'endDate', 'fixed']); @@ -23,22 +25,22 @@ const NormalOpeningHoursValidity = (): JSX.Element => { <> onChange(false)} /> onChange(true)} /> @@ -48,7 +50,7 @@ const NormalOpeningHoursValidity = (): JSX.Element => { defaultValue={startDate ?? ''} name="startDate" rules={{ - required: 'Pakollinen', + required: t('Common.Mandatory'), validate: isValidDate, }} render={({ @@ -58,18 +60,18 @@ const NormalOpeningHoursValidity = (): JSX.Element => { { defaultValue={endDate ?? ''} name="endDate" rules={{ - required: 'Pakollinen', + required: t('Common.Mandatory'), validate: { validDate: isValidDate, endDateAfterStartDate: endDateAfterStartDate(getValues), @@ -94,18 +96,18 @@ const NormalOpeningHoursValidity = (): JSX.Element => { {text} , diff --git a/src/components/opening-hours-form-preview/OpeningHoursFormPreview.tsx b/src/components/opening-hours-form-preview/OpeningHoursFormPreview.tsx index 22c46a87d..8bc3c910c 100644 --- a/src/components/opening-hours-form-preview/OpeningHoursFormPreview.tsx +++ b/src/components/opening-hours-form-preview/OpeningHoursFormPreview.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { DatePeriod, TranslatedApiChoice } from '../../common/lib/types'; import { formatDateRange } from '../../common/utils/date-time/format'; import OpeningHoursPreview from '../opening-hours-preview/OpeningHoursPreview'; @@ -15,22 +16,26 @@ const OpeningHoursFormPreview = ({ resourceStates: TranslatedApiChoice[]; tabIndex?: number; className?: string; -}): JSX.Element => ( -
-

- Esikatselu -

-

{formatDateRange(datePeriod)}

- -
-); +}): JSX.Element => { + const { t } = useTranslation(); + + return ( +
+

+ {t('OpeningHours.OpeningHoursFormPreview')} +

+

{formatDateRange(datePeriod)}

+ +
+ ); +}; export default OpeningHoursFormPreview; diff --git a/src/components/opening-hours-form-preview/OpeningHoursFormPreviewMobile.tsx b/src/components/opening-hours-form-preview/OpeningHoursFormPreviewMobile.tsx index d10d24237..daf20a821 100644 --- a/src/components/opening-hours-form-preview/OpeningHoursFormPreviewMobile.tsx +++ b/src/components/opening-hours-form-preview/OpeningHoursFormPreviewMobile.tsx @@ -1,5 +1,6 @@ import { IconAngleDown, IconAngleUp, useAccordion } from 'hds-react'; import React, { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { DatePeriod, Language, @@ -27,6 +28,7 @@ const OpeningHoursFormPreviewMobile = ({ const mobilePreview = useRef(null); useOnClickOutside(mobilePreview, closeAccordion); const isMobile = useMobile(); + const { t } = useTranslation(); return (
@@ -38,7 +40,7 @@ const OpeningHoursFormPreviewMobile = ({ } size={isMobile ? 'small' : 'default'} {...buttonProps}> - Esikatselu + {t('OpeningHours.OpeningHoursFormPreview')}
Promise; resource: Resource; }): JSX.Element => { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const defaultValues: DatePeriod = datePeriod ? apiDatePeriodToDatePeriod(datePeriod) @@ -116,7 +118,7 @@ const OpeningHoursForm = ({ const sortOpeningHours = () => { setValue('openingHours', [...formValues.openingHours].sort(byWeekdays)); toast.info({ - label: 'Päiväryhmät järjestetty viikonpäivien mukaan', + label: t('OpeningHours.GroupsSortedNotification'), position: 'bottom-right', }); }; @@ -140,7 +142,7 @@ const OpeningHoursForm = ({ (resource && datePeriodConfig && (

- Tähdellä (*) merkityt kohdat ovat pakollisia. + {t('OpeningHours.RequiredFieldsText')}

} onClick={sortOpeningHours}> - Järjestä päiväryhmät viikonpäivien mukaan + {t('OpeningHours.SortWeekdaysContainer')}
@@ -200,7 +202,7 @@ const OpeningHoursForm = ({ - )) ||

Ladataan...

+ )) ||

{t('Common.IsLoading')}

); }; diff --git a/src/components/opening-hours-form/OpeningHoursFormActions.tsx b/src/components/opening-hours-form/OpeningHoursFormActions.tsx index e5a561a43..78194e0dc 100644 --- a/src/components/opening-hours-form/OpeningHoursFormActions.tsx +++ b/src/components/opening-hours-form/OpeningHoursFormActions.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import useMobile from '../../hooks/useMobile'; import useReturnToResourcePage from '../../hooks/useReturnToResourcePage'; import { PrimaryButton, SecondaryButton } from '../button/Button'; @@ -9,6 +10,7 @@ type Props = { }; const OpeningHoursFormActions = ({ isSaving }: Props): JSX.Element => { + const { t } = useTranslation(); const isMobile = useMobile(); const returnToResourcePage = useReturnToResourcePage(); @@ -18,15 +20,15 @@ const OpeningHoursFormActions = ({ isSaving }: Props): JSX.Element => { - Tallenna + {t('Common.Submit')} - Peruuta + {t('Common.Cancel')}
diff --git a/src/components/opening-hours-form/OpeningHoursTitles.test.tsx b/src/components/opening-hours-form/OpeningHoursTitles.test.tsx new file mode 100644 index 000000000..46edb0c6a --- /dev/null +++ b/src/components/opening-hours-form/OpeningHoursTitles.test.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import OpeningHoursTitles from './OpeningHoursTitles'; + +// Mock useTranslation hook +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +// Mock Controller component +vi.mock('react-hook-form', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any + Controller: ({ render: renderMock }: any) => + renderMock({ + field: { + ref: vi.fn(), + name: '', + onChange: vi.fn(), + onBlur: vi.fn(), + value: '', + }, + fieldState: { error: null }, + }), +})); + +test('renders OpeningHoursTitles component', () => { + const placeholders = { + fi: 'Placeholder FI', + sv: 'Placeholder SV', + en: 'Placeholder EN', + }; + render(); + + // Assertions for labels + expect( + screen.getByLabelText('OpeningHours.TitleInFinnish') + ).toBeInTheDocument(); + expect( + screen.getByLabelText('OpeningHours.TitleInSwedish') + ).toBeInTheDocument(); + expect( + screen.getByLabelText('OpeningHours.TitleInEnglish') + ).toBeInTheDocument(); + + // Assertions for placeholders + expect(screen.getByTestId('opening-period-title-fi')).toHaveAttribute( + 'placeholder', + placeholders.fi + ); + expect(screen.getByTestId('opening-period-title-sv')).toHaveAttribute( + 'placeholder', + placeholders.sv + ); + expect(screen.getByTestId('opening-period-title-en')).toHaveAttribute( + 'placeholder', + placeholders.en + ); +}); diff --git a/src/components/opening-hours-form/OpeningHoursTitles.tsx b/src/components/opening-hours-form/OpeningHoursTitles.tsx index f99d31c62..2c60aadaa 100644 --- a/src/components/opening-hours-form/OpeningHoursTitles.tsx +++ b/src/components/opening-hours-form/OpeningHoursTitles.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { TextInput } from 'hds-react'; import { Controller } from 'react-hook-form'; import './OpeningHoursTitles.scss'; @@ -9,90 +10,94 @@ type Props = { placeholders: LanguageStrings; }; -const nameMaxLength = 100; +const OpeningHoursTitles = ({ placeholders }: Props): JSX.Element => { + const { t } = useTranslation(); -const titleRules = { maxLength: { value: nameMaxLength, message: 'Tarkista' } }; + const nameMaxLength = 100; -const OpeningHoursTitles = ({ placeholders }: Props): JSX.Element => ( -
-
- ( - - )} - /> - ( - - )} - /> - ( - - )} - /> + const titleRules = { + maxLength: { value: nameMaxLength, message: t('OpeningHours.Validate') }, + }; + + return ( +
+
+ ( + + )} + /> + ( + + )} + /> + ( + + )} + /> +
+

+ {t('OpeningHours.TitlesHelperText')} +

-

- Otsikko ei ole pakollinen. Tähän kohtaan voit kirjoittaa esim talviaika, - kevätkausi ym. Älä kirjoita aukiolokohdetta esim sauna, uima-allas, kerros - tms. -

-
-); + ); +}; export default OpeningHoursTitles; diff --git a/src/components/opening-hours-weekdays/DayCheckbox.tsx b/src/components/opening-hours-weekdays/DayCheckbox.tsx index c4cba00fb..5b7ae3c92 100644 --- a/src/components/opening-hours-weekdays/DayCheckbox.tsx +++ b/src/components/opening-hours-weekdays/DayCheckbox.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useAppContext } from '../../App-context'; import { Language } from '../../common/lib/types'; import { getWeekdayShortNameByIndexAndLang } from '../../common/utils/date-time/format'; import { getUiId } from '../../common/utils/form/form'; @@ -17,12 +18,13 @@ const DayCheckbox = ({ onChange: (checked: boolean) => void; checked?: boolean; }): JSX.Element => { + const { language } = useAppContext(); const id = getUiId([namePrefix, 'weekdays', currentDay]); return ( diff --git a/src/components/opening-hours-weekdays/OpeningHoursWeekdays.tsx b/src/components/opening-hours-weekdays/OpeningHoursWeekdays.tsx index 1b059e58e..9034322ae 100644 --- a/src/components/opening-hours-weekdays/OpeningHoursWeekdays.tsx +++ b/src/components/opening-hours-weekdays/OpeningHoursWeekdays.tsx @@ -2,6 +2,7 @@ import { Notification, Select } from 'hds-react'; import { upperFirst } from 'lodash'; import React, { Fragment, useEffect, useRef } from 'react'; import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { Language, TranslatedApiChoice, @@ -63,6 +64,7 @@ const OpeningHoursWeekdays = ({ rules: Rule[]; onDayChange: (day: number, checked: boolean, offsetTop: number) => void; }): JSX.Element => { + const { t } = useTranslation(); const namePrefix = `openingHours.${openingHoursIdx}` as const; const { language = Language.FI } = useAppContext(); const { control, getValues, setValue, watch } = useFormContext(); @@ -116,7 +118,7 @@ const OpeningHoursWeekdays = ({ const removedDayLabel = removedDay ? getWeekdayLongNameByIndexAndLang({ weekdayIndex: removedDay, - language: Language.FI, + language, }) : ''; @@ -148,18 +150,21 @@ const OpeningHoursWeekdays = ({ }; const weekdayGroup = upperFirst( - item.weekdays.length === 1 - ? `${resolveDayTranslation(item.weekdays[0], true)} aukioloajat` - : `${groupWeekdays(item.weekdays) - .map((group) => - group.length === 1 - ? resolveDayTranslation(group[0], false) - : `${resolveDayTranslation( - group[0], - false - )}-${resolveDayTranslation(group[group.length - 1], false)}` - ) - .join(', ')} aukioloajat` + t('OpeningHours.WeekdayGroupOpeningHoursAria', { + weekDayLabels: + item.weekdays.length === 1 + ? resolveDayTranslation(item.weekdays[0], true) + : groupWeekdays(item.weekdays) + .map((group) => + group.length === 1 + ? resolveDayTranslation(group[0], false) + : `${resolveDayTranslation( + group[0], + false + )}-${resolveDayTranslation(group[group.length - 1], false)}` + ) + .join(', '), + }) ); const handleRuleChange = @@ -189,14 +194,16 @@ const OpeningHoursWeekdays = ({
+ aria-label={t('OpeningHours.WeekdayGroupAria', { + openingHoursIdx: openingHoursIdx + 1, + })}>
- Päivä tai päiväryhmä + {t('OpeningHours.WeekdaysLabel')}
{formatDateRange({ startDate, endDate })}
@@ -207,17 +214,19 @@ const OpeningHoursWeekdays = ({ {removedDay && ( setRemovedDay(null)} style={{ zIndex: 100 }}> - {`Juuri poistettu ${removedDayLabel} siirrettiin omaksi rivikseen.`} + {t('OpeningHours.RemovedDayNotification', { removedDayLabel })} )} ( <> @@ -271,10 +280,10 @@ const OpeningHoursWeekdays = ({ > className="rule-select" defaultValue={rules[0]} - label="Toistuvuus" + label={t('OpeningHours.RuleSelectLabel')} onChange={handleRuleChange(i)} options={rules} - placeholder="Valitse" + placeholder={t('OpeningHours.RuleSelectPlaceholder')} required value={rules.find((rule) => rule.value === value.type)} /> diff --git a/src/components/opening-period-accordion/OpeningPeriodAccordion.tsx b/src/components/opening-period-accordion/OpeningPeriodAccordion.tsx index 780bd87e8..38d0a487d 100644 --- a/src/components/opening-period-accordion/OpeningPeriodAccordion.tsx +++ b/src/components/opening-period-accordion/OpeningPeriodAccordion.tsx @@ -146,7 +146,7 @@ const OpeningPeriodAccordion = ({ return (
+ data-testid={`openingPeriod${dataTestPostFix}`}>
{toggleChecked !== undefined && datePeriodSelectState === DatePeriodSelectState.ACTIVE && ( @@ -187,7 +187,7 @@ const OpeningPeriodAccordion = ({ {editUrl && (
) : ( -
    +
      {React.Children.map(children, (child) => (
    • {child}
    • ))} diff --git a/src/components/resource-title/ResourceTitle.tsx b/src/components/resource-title/ResourceTitle.tsx index 5815dbd1d..ede9131c7 100644 --- a/src/components/resource-title/ResourceTitle.tsx +++ b/src/components/resource-title/ResourceTitle.tsx @@ -74,7 +74,7 @@ const ResourceTitle = ({

      diff --git a/src/components/time-span/TimeSpan.tsx b/src/components/time-span/TimeSpan.tsx index f6b7c3970..15b3e400b 100644 --- a/src/components/time-span/TimeSpan.tsx +++ b/src/components/time-span/TimeSpan.tsx @@ -1,6 +1,7 @@ import { Checkbox, IconTrash, Select, TextInput, TimeInput } from 'hds-react'; import { Controller, useFormContext } from 'react-hook-form'; import React, { MutableRefObject, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { InputOption, Language, @@ -22,26 +23,6 @@ import { isDescriptionAllowed, } from '../../common/helpers/opening-hours-helpers'; -const validateTime = (value: string | null) => { - const re = /\d{2}:\d{2}/; - - return value && re.test(value) ? undefined : 'Tarkista'; -}; - -const descriptionMaxLength = 100; - -const descriptionRules = { - maxLength: { - value: descriptionMaxLength, - message: 'Tarkista', - }, -}; - -const timeInputRules = { - required: 'Pakollinen', - validate: validateTime, -}; - type Props = { disabled?: boolean; groupLabel: string; @@ -65,6 +46,7 @@ const TimeSpan = ({ resourceStates, timeSpanGroupIdx, }: Props): JSX.Element => { + const { t } = useTranslation(); const namePrefix = `openingHours.${openingHoursIdx}.timeSpanGroups.${timeSpanGroupIdx}.timeSpans.${i}` as const; const { language = Language.FI } = useAppContext(); @@ -84,6 +66,23 @@ const TimeSpan = ({ }) .map(choiceToOption(language)); + const descriptionMaxLength = 100; + + const descriptionRules = { + maxLength: { + value: descriptionMaxLength, + message: t('OpeningHours.Validate'), + }, + }; + + const validateTime = (value: string | null) => + value && /\d{2}:\d{2}/.test(value) ? undefined : t('OpeningHours.Validate'); + + const timeInputRules = { + required: t('Common.Mandatory'), + validate: validateTime, + }; + const displayStartAndEndTimes = resourceState && areStartAndEndTimesAllowed(i, resourceState); @@ -110,17 +109,17 @@ const TimeSpan = ({ name={resourceStateName} control={control} rules={{ - required: 'Pakollinen', + required: t('Common.Mandatory'), }} render={({ field: { onChange, value } }): JSX.Element => ( disabled={disabled} id={resourceStateId} - label="Aukiolon tyyppi" + label={t('OpeningHours.TimeSpanState')} options={sanitizedResourceStateOptions} className="time-span__resource-state-select" onChange={(option: InputOption): void => onChange(option.value)} - placeholder="Valitse" + placeholder={t('OpeningHours.TimeSpanStatePlaceholder')} required value={sanitizedResourceStateOptions.find( (option) => option.value === value @@ -139,7 +138,7 @@ const TimeSpan = ({ disabled={disabled} id={getUiId([namePrefix, 'full-day'])} name={`${namePrefix}.full_day`} - label="24 h" + label={t('OpeningHours.TimeSpan24h')} onChange={(e): void => { field.onChange(e.target.checked); }} @@ -161,11 +160,11 @@ const TimeSpan = ({ )} @@ -244,11 +245,13 @@ const TimeSpan = ({ helperText={toCharCount(descriptionMaxLength, value)} id={getUiId([name])} invalid={!!error} - label="Kuvaus ruotsiksi" + label={t('OpeningHours.DescriptionInSwedish')} name={name} onBlur={onBlur} onChange={onChange} - placeholder="T.ex. seniorer" + placeholder={t( + 'OpeningHours.DescriptionPlaceholderInSwedish' + )} value={value || ''} /> )} @@ -266,11 +269,13 @@ const TimeSpan = ({ helperText={toCharCount(descriptionMaxLength, value)} id={getUiId([name])} invalid={!!error} - label="Kuvaus englanniksi" + label={t('OpeningHours.DescriptionInEnglish')} name={name} onBlur={onBlur} onChange={onChange} - placeholder="E.g. seniors" + placeholder={t( + 'OpeningHours.DescriptionPlaceholderInEnglish' + )} value={value || ''} /> )} @@ -281,7 +286,8 @@ const TimeSpan = ({
      {onDelete && ( } onClick={onDelete}> - Poista rivi{groupLabel} + {t('OpeningHours.RemoveTimeSpanButton')} + {groupLabel} )}
      diff --git a/src/components/time-span/TimeSpans.tsx b/src/components/time-span/TimeSpans.tsx index d5f82b731..0c35cb670 100644 --- a/src/components/time-span/TimeSpans.tsx +++ b/src/components/time-span/TimeSpans.tsx @@ -1,6 +1,7 @@ import { IconPlusCircle } from 'hds-react'; import React, { useEffect, useRef, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { areStartAndEndTimesAllowed, isDescriptionAllowed, @@ -35,6 +36,7 @@ const TimeSpans = ({ resourceStates: TranslatedApiChoice[]; timeSpanGroupIdx: number; }): JSX.Element => { + const { t } = useTranslation(); const namePrefix = `openingHours.${openingHoursIdx}.timeSpanGroups.${timeSpanGroupIdx}.timeSpans` as const; const { control, getValues, setValue, watch } = useFormContext(); @@ -96,7 +98,9 @@ const TimeSpans = ({ openingHoursIdx={openingHoursIdx} timeSpanGroupIdx={timeSpanGroupIdx} i={i} - groupLabel={`Aukioloaika ${i + 1}`} + groupLabel={t('OpeningHours.TimeSpanAriaLabel', { + timeSpanIndex: i + 1, + })} item={field} resourceStates={resourceStates} onDelete={ @@ -120,7 +124,7 @@ const TimeSpans = ({ setTimeSpansChanged(true); }} type="button"> - Lisää aukiolomääritys + {t('OpeningHours.AddTimeSpanButton')}

)} diff --git a/src/i18n.js b/src/i18n.ts similarity index 100% rename from src/i18n.js rename to src/i18n.ts diff --git a/src/index.tsx b/src/index.tsx index 22bfce5d3..f52e9af9c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,7 +7,6 @@ import './index.scss'; import axe from '@axe-core/react'; import App from './App'; import * as serviceWorker from './serviceWorker'; -import './i18n'; declare global { interface Window { diff --git a/src/language/en.json b/src/language/en.json index e7db72e6d..51044f193 100644 --- a/src/language/en.json +++ b/src/language/en.json @@ -30,6 +30,7 @@ "SelectAll": "Choose all", "CountSingular": "Opening hours", "CountPlural": "Searching opening hours", + "LoadingText": "Searching for opening hours", "StatusLabelActive": "Valid now", "DeleteModalTitle": "Are you sure you want to delete the opening period?", "ModifyPeriod": "Edit the opening period information for {{periodName}}", @@ -93,6 +94,93 @@ "PeriodRemoveFailed": "Failed to remove opening hours. Try again later." } }, + "OpeningHours": { + "RequiredFieldsText": "Items marked with an asterisk (*) are mandatory.", + "SortWeekdaysContainer": "Organise day groups by day of the week", + "GroupsSortedNotification": "Day groups organised by day of the week", + "IsSubmitting": "Saving opening hours", + "IsSubmittingExceptional": "Saving exceptional opening hours", + "TitleInFinnish": "Opening hours title in Finnish", + "TitleInEnglish": "Opening hours title in English", + "TitleInSwedish": "Opening hours title in Swedish", + "TitlePlaceholderInFinnish": "Esim. kesäkausi", + "TitlePlaceholderInEnglish": "T.ex. sommartid", + "TitlePlaceholderInSwedish": "E.g. summertime", + "TitlesHelperText": "The title is not compulsory. In this section you can write e.g. winter season, spring season, etc. Do not write the opening hours, e.g. sauna, swimming pool, floor, etc.", + "ValidityTitle": "Period of validity of the opening hours", + "ValidityRecurring": "Valid for the time being", + "ValidityFixed": "Valid for a limited period", + "PeriodBeginDate": "Valid from", + "PeriodBeginDateAria": "Choose a start date", + "PeriodEndDate": "Expires", + "PeriodEndDateAria": "Select end date", + "WeekdaysLabel": "Day or day group", + "WeekdayGroupAria": " Opening hours definition {{openingHoursIdx}}", + "WeekdayGroupOpeningHoursAria": "{{weekDayLabels}} opening hours", + "RemovedDayNotificationLabel": "{{removedDayLabel}}-day moved to a separate line", + "RemovedDayNotification": "Just removed {{removedDayLabel}} moved to its own line.", + "RuleSelectLabel": "Recurrence", + "RuleSelectPlaceholder": "Select", + "TimeSpanState": "Type of opening", + "TimeSpanStatePlaceholder": "Select", + "TimeSpan24h": "24 h", + "TimeSpanHoursLabel": "hours", + "TimeSpanBegins": "Starts at", + "TimeSpanMinutesLabel": "minutes", + "TimeSpanEnds": "Ends at", + "TimeSpanAriaLabel": "Opening hours {{timeSpanIndex}}", + "DescriptionInFinnish": "Description in Finnish", + "DescriptionInEnglish": "Description in English", + "DescriptionInSwedish": "Description in Swedish", + "DescriptionPlaceholderInFinnish": "Esim. seniorit", + "DescriptionPlaceholderInEnglish": "E.g. seniors", + "DescriptionPlaceholderInSwedish": "T.ex. seniorer", + "Validate": "Check", + "AddTimeSpanButton": "Add new opening hours information", + "SubmitSuccessNotification": "Successful recording of the opening hours", + "SubmitErrorNotification": "Failed to save the opening hours", + "ExceptionOpeningHoursValidity": "Exceptional opening hours are valid", + "ExceptionStartDate": "Starts", + "ExceptionStartDateAria": "Choose a start date", + "ExceptionEndDate": "Ends", + "ExceptionEndDateAria": "Select end date", + "ExceptionStateClosed": "Closed", + "ExceptionStateClosedWholeDay": "Closed all day", + "ExceptionStateOpen": "Exceptional opening hours", + "ExceptionPeriodSubmitSuccess": "Exception period was successfully saved", + "ExceptionPeriodSubmitError": "Exception period failed to save", + "NoExceptions": "No exceptional opening hours", + "HolidaysTitle": "Opening hours on public holidays", + "HolidaysHelperText": "If you add a different opening time for a public holiday in the list, it is valid until further notice. Remember to check every year that the information is still correct.", + "HolidaysListHeader": "Holidays", + "RemoveHolidaySpanConfirmTitle": "Are you sure you want to delete the opening period?", + "RemoveHolidaySpanConfirmText": "You are about to delete an opening period", + "RemoveHolidaySpanLoading": "Deleting the opening period", + "RemoveHolidaySpanSpinner": "Deleting the opening period...", + "RemoveHolidaySpanConfirm": "Remove ", + "EditHolidaySpan": "Edit {{holiday}} opening period information", + "HolidayCreateSuccess": "{{holidayName}} was added successfully", + "HolidayCreateError": "{{holidayName}} failed to add opening hours", + "HolidayUpdateSuccess": "{{holidayName}} opening hours recording was successful", + "HolidayUpdateError": "{{holidayName}} opening failed to save", + "HolidayDeleteSuccess": "{{holidayName}} was successfully saved", + "HolidayDeleteError": "{{holidayName}} opening hour failed to save", + "HolidaysTableTitle": "The following holidays", + "HolidaysTableDescription": "Remember to check the timetable of public holidays each year - for example, the timing of the Easter holidays varies.", + "HolidaysTableHolidayColumn": "Holidays", + "HolidaysTableDateColumn": "Date", + "HolidaysTableOpeningHoursColumn": "Opening hours", + "RemoveTimeSpanButton": "Delete row", + "NextHoliday": "Next holiday: ", + "OpeningHoursFormPreview": "Preview" + }, + "Common": { + "IsLoading": "Loading...", + "Submit": "Save", + "Cancel": "Cancel", + "Mandatory": "Mandatory", + "CloseNotification": "Close notification" + }, "Header": { "Help": "Guide", "LogoAlt": "City of Helsinki", diff --git a/src/language/fi.json b/src/language/fi.json index e07a119ec..03dfa8448 100644 --- a/src/language/fi.json +++ b/src/language/fi.json @@ -94,6 +94,93 @@ "PeriodRemoveFailed": "Aukiolon poisto epäonnistui. Yritä myöhemmin uudelleen." } }, + "OpeningHours": { + "RequiredFieldsText": "Tähdellä (*) merkityt kohdat ovat pakollisia.", + "SortWeekdaysContainer": "Järjestä päiväryhmät viikonpäivien mukaan", + "GroupsSortedNotification": "Päiväryhmät järjestetty viikonpäivien mukaan", + "IsSubmitting": "Tallentaa aukioloaikoja", + "IsSubmittingExceptional": "Tallentaa poikkeusaukioloa", + "TitleInFinnish": "Aukioloajan otsikko suomeksi", + "TitleInEnglish": "Aukioloajan otsikko englanniksi", + "TitleInSwedish": "Aukioloajan otsikko ruotsiksi", + "TitlePlaceholderInFinnish": "Esim. kesäkausi", + "TitlePlaceholderInEnglish": "T.ex. sommartid", + "TitlePlaceholderInSwedish": "E.g. summertime", + "TitlesHelperText": "Otsikko ei ole pakollinen. Tähän kohtaan voit kirjoittaa esim talviaika, kevätkausi ym. Älä kirjoita aukiolokohdetta esim sauna, uima-allas, kerros tms.", + "ValidityTitle": "Aukiolon voimassaoloaika", + "ValidityRecurring": "Toistaiseksi voimassa", + "ValidityFixed": "Voimassa tietyn ajan", + "PeriodBeginDate": "Astuu voimaan", + "PeriodBeginDateAria": "Valitse alkupäivämäärä", + "PeriodEndDate": "Päättyy", + "PeriodEndDateAria": "Valitse loppupäivämäärä", + "WeekdaysLabel": "Päivä tai päiväryhmä", + "WeekdayGroupAria": "Aukiolomääritys {{openingHoursIdx}}", + "WeekdayGroupOpeningHoursAria": "{{weekDayLabels}} aukioloajat", + "RemovedDayNotificationLabel": "{{removedDayLabel}}-päivä siirretty omaksi riviksi", + "RemovedDayNotification": "Juuri poistettu {{removedDayLabel}} siirrettiin omaksi rivikseen.", + "RuleSelectLabel": "Toistuvuus", + "RuleSelectPlaceholder": "Valitse", + "TimeSpanState": "Aukiolon tyyppi", + "TimeSpanStatePlaceholder": "Valitse", + "TimeSpan24h": "24 h", + "TimeSpanHoursLabel": "tunnit", + "TimeSpanBegins": "Alkaa klo", + "TimeSpanMinutesLabel": "minuutit", + "TimeSpanEnds": "Päättyy klo", + "TimeSpanAriaLabel": "Aukioloaika {{timeSpanIndex}}", + "DescriptionInFinnish": "Kuvaus suomeksi", + "DescriptionInEnglish": "Kuvaus englanniksi", + "DescriptionInSwedish": "Kuvaus ruotsiksi", + "DescriptionPlaceholderInFinnish": "Esim. seniorit", + "DescriptionPlaceholderInEnglish": "E.g. seniors", + "DescriptionPlaceholderInSwedish": "T.ex. seniorer", + "Validate": "Tarkista", + "AddTimeSpanButton": "Lisää aukiolomääritys", + "SubmitSuccessNotification": "Aukiolon tallennus onnistui", + "SubmitErrorNotification": "Aukiolon tallennus epäonnistui", + "ExceptionOpeningHoursValidity": "Poikkeavan aukiolon voimassaoloaika", + "ExceptionStartDate": "Alkaa", + "ExceptionStartDateAria": "Valitse alkupäivämäärä", + "ExceptionEndDate": "Päättyy", + "ExceptionEndDateAria": "Valitse loppupäivämäärä", + "ExceptionStateClosed": "Suljettu", + "ExceptionStateClosedWholeDay": "Suljettu koko päivän", + "ExceptionStateOpen": "Poikkeava aukioloaika", + "ExceptionPeriodSubmitSuccess": "Poikkeavan päivän aukiolon lisääminen onnistui", + "ExceptionPeriodSubmitError": "Poikkeavan päivän aukiolon lisääminen epäonnistui", + "NoExceptions": "Ei poikkeavia aukioloja", + "HolidaysTitle": "Juhlapyhien aukioloajat", + "HolidaysHelperText": "Jos lisäät listassa olevalle juhlapyhälle poikkeavan aukioloajan, se on voimassa toistaiseksi. Muista tarkistaa vuosittain, että tieto pitää yhä paikkansa.", + "HolidaysListHeader": "Juhlapyhä", + "RemoveHolidaySpanConfirmTitle": "Oletko varma että haluat poistaa aukiolojakson?", + "RemoveHolidaySpanConfirmText": "Olet poistamassa aukiolojakson", + "RemoveHolidaySpanLoading": "Poistetaan aukiolojaksoa", + "RemoveHolidaySpanSpinner": "Poistetaan aukiolojaksoa...", + "RemoveHolidaySpanConfirm": "Poista", + "EditHolidaySpan": "Muokkaa {{holiday}} aukiolojakson tietoja", + "HolidayCreateSuccess": "{{holidayName}} aukiolon lisääminen onnistui", + "HolidayCreateError": "{{holidayName}} aukiolon lisääminen epäonnistui", + "HolidayUpdateSuccess": "{{holidayName}} aukiolon tallennus onnistui", + "HolidayUpdateError": "{{holidayName}} aukiolon tallennus epäonnistui", + "HolidayDeleteSuccess": "{{holidayName}} aukiolon tallennus onnistui", + "HolidayDeleteError": "{{holidayName}} aukiolon tallennus epäonnistui", + "HolidaysTableTitle": "Seuraavat juhlapyhät", + "HolidaysTableDescription": "Muista tarkistaa juhlapyhien aikataulut vuosittain – esimerkiksi pääsiäisen juhlapyhien ajankohta vaihtelee.", + "HolidaysTableHolidayColumn": "Juhlapyhä", + "HolidaysTableDateColumn": "Päivämäärä", + "HolidaysTableOpeningHoursColumn": "Aukiolo", + "RemoveTimeSpanButton": "Poista rivi", + "NextHoliday": "Seuraava juhlapyhä: ", + "OpeningHoursFormPreview": "Esikatselu" + }, + "Common": { + "IsLoading": "Ladataan...", + "Submit": "Tallenna", + "Cancel": "Peruuta", + "Mandatory": "Pakollinen", + "CloseNotification": "Sulje ilmoitus" + }, "Header": { "Help": "Ohje", "LogoAlt": "Helsingin kaupunki", diff --git a/src/language/sv.json b/src/language/sv.json index f1dcebcd3..7b0cbba65 100644 --- a/src/language/sv.json +++ b/src/language/sv.json @@ -94,12 +94,99 @@ "PeriodRemoveFailed": "Misslyckades ta bort information om öppettider. Försök igen senare." } }, + "OpeningHours": { + "RequiredFieldsText": "Poster markerade med en asterisk (*) är obligatoriska.", + "SortWeekdaysContainer": "Organisera daggrupper efter veckodag", + "GroupsSortedNotification": "Daggrupper organiserade efter veckodag", + "IsSubmitting": "Sparar öppettiderna", + "IsSubmittingExceptional": "Sparar den exceptionella öppettiden", + "TitleInFinnish": "Titel för öppettider på finska", + "TitleInEnglish": "Titel på öppettider på engelska", + "TitleInSwedish": "Titel för öppettider på svenska", + "TitlePlaceholderInFinnish": "Esim. kesäkausi", + "TitlePlaceholderInEnglish": "T.ex. sommartid", + "TitlePlaceholderInSwedish": "E.g. summertime", + "TitlesHelperText": "Rubriken är inte obligatorisk. I detta avsnitt kan du skriva t.ex. vintersäsong, vårsäsong etc. Skriv inte öppettiderna, t.ex. bastu, simbassäng, våning, etc.", + "ValidityTitle": "Giltighetstid för öppettiderna", + "ValidityRecurring": "Gäller tillsvidare", + "ValidityFixed": "Gäller under en begränsad period", + "PeriodBeginDate": "Giltig från", + "PeriodBeginDateAria": "Välj ett startdatum", + "PeriodEndDate": "Upphör att gälla", + "PeriodEndDateAria": "Välj slutdatum", + "WeekdaysLabel": "Dag eller daggrupp", + "WeekdayGroupAria": "Öppettider {{openingHoursIdx}}", + "WeekdayGroupOpeningHoursAria": "{{weekDayLabels}} öppettider", + "RemovedDayNotificationLabel": "{{removedDayLabel}}-dag flyttat till en separat rad", + "RemovedDayNotification": "Nyligen borttagen {{removedDayLabel}} flyttades till sin egen rad.", + "RuleSelectLabel": "Återkommande", + "RuleSelectPlaceholder": "Välj", + "TimeSpanState": "Typ av öppning", + "TimeSpanStatePlaceholder": "Välj", + "TimeSpan24h": "24 h", + "TimeSpanHoursLabel": "timmar", + "TimeSpanBegins": "Börjar kl.", + "TimeSpanMinutesLabel": "minuter", + "TimeSpanEnds": "slutar kl.", + "TimeSpanAriaLabel": "Öppettider {{timeSpanIndex}}", + "DescriptionInFinnish": "Kuvaus suomeksi", + "DescriptionInEnglish": "Beskrivning på finska", + "DescriptionInSwedish": "Beskrivning på svenska", + "DescriptionPlaceholderInFinnish": "Esim. seniorit", + "DescriptionPlaceholderInEnglish": "E.g. seniors", + "DescriptionPlaceholderInSwedish": "T.ex. seniorer", + "Validate": "Kontrollera", + "AddTimeSpanButton": "Lägg till en ny öppning", + "SubmitSuccessNotification": "Registreringen av öppettider lyckades", + "SubmitErrorNotification": "Registreringen av öppettider misslyckades", + "ExceptionOpeningHoursValidity": "Giltighetstiden för avvikande öppettider", + "ExceptionStartDate": "Börjar på", + "ExceptionStartDateAria": "Välj ett startdatum", + "ExceptionEndDate": "Slut på", + "ExceptionEndDateAria": "Välj slutdatum", + "ExceptionStateClosed": "Stängt", + "ExceptionStateClosedWholeDay": "Stängt hela dagen", + "ExceptionStateOpen": "Avvikande öppettider", + "ExceptionPeriodSubmitSuccess": "inspelningen av avvikande öppettider var lyckad", + "ExceptionPeriodSubmitError": "misslyckades med att spara avvikande öppettider", + "NoExceptions": "Inga avvikande öppettider", + "HolidaysTitle": "Öppettider på allmänna helgdagar", + "HolidaysHelperText": "Om du lägger till en annan öppettid för en helgdag i listan gäller den tills vidare. Kom ihåg att varje år kontrollera att informationen fortfarande är korrekt.", + "HolidaysListHeader": "Helgdag", + "RemoveHolidaySpanConfirmTitle": "Är du säker på att du vill ta bort öppethållningsperioden?", + "RemoveHolidaySpanConfirmText": "Du håller på att ta bort öppethållningsperioden", + "RemoveHolidaySpanLoading": "Ta bort öppethållningsperiod", + "RemoveHolidaySpanSpinner": "Ta bort öppethållningsperiod...", + "RemoveHolidaySpanConfirm": "Ta bort", + "EditHolidaySpan": "Redigera {{helgdag}} information om öppningsperiod", + "HolidayCreateSuccess": "{{holidayName}} öppningstiden lades till framgångsrikt", + "HolidayCreateError": "{{holidayName}} misslyckades med att lägga till öppettider", + "HolidayUpdateSuccess": "{{holidayName}} inspelningen av öppettider var lyckad", + "HolidayUpdateError": "{{holidayName}} misslyckades med att rädda öppningen", + "HolidayDeleteSuccess": "{{holidayName}} inspelningen av öppettider var lyckad", + "HolidayDeleteError": "{{holidayName}} misslyckades med att spara öppettider", + "HolidaysTableTitle": "Följande helgdagar", + "HolidaysTableDescription": "Kom ihåg att kontrollera schemat för allmänna helgdagar varje år - till exempel varierar tidpunkten för påskhelgen.", + "HolidaysTableHolidayColumn": "Helgdag", + "HolidaysTableDateColumn": "Datum", + "HolidaysTableOpeningHoursColumn": "Öppettider", + "RemoveTimeSpanButton": "Radera linje", + "NextHoliday": "Nästa helgdag: ", + "OpeningHoursFormPreview": "Förhandsvisning" + }, + "Common": { + "IsLoading": "Laddar...", + "Submit": "Spara på", + "Cancel": "Avbryt", + "Mandatory": "Obligatoriskt", + "CloseNotification": "Stäng meddelandet" + }, "Header": { "Help": "Anvisning", "LogoAlt": "Helsingfors stad", "FrontPage": "Framsida", "Menu": "Meny", - "Title": "Aukiolot", + "Title": "Öppettider", "SkipLink": "Hoppa till innehåll", "SignOutFailed": "Det gick inte att logga ut. Vänligen försök igen senare. Fel: ", "SignOutRejected": "Logga ut avvisades.", diff --git a/src/pages/AddExceptionOpeningHoursPage.tsx b/src/pages/AddExceptionOpeningHoursPage.tsx index 6a77e932b..102f0dd82 100644 --- a/src/pages/AddExceptionOpeningHoursPage.tsx +++ b/src/pages/AddExceptionOpeningHoursPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import api from '../common/utils/api/api'; import ExceptionOpeningHoursForm from '../components/exception-opening-hours-form/ExceptionOpeningHoursForm'; import useDatePeriodConfig from '../services/useDatePeriodConfig'; @@ -9,11 +10,12 @@ const AddExceptionOpeningHoursPage = ({ }: { resourceId: string; }): JSX.Element => { + const { t } = useTranslation(); const resource = useResource(resourceId); const datePeriodConfig = useDatePeriodConfig(); if (!resource || !datePeriodConfig) { - return

Ladataan...

; + return

{t('Common.IsLoading')}

; } return ( diff --git a/src/pages/AddNormalOpeningHoursPage.tsx b/src/pages/AddNormalOpeningHoursPage.tsx index 346fd0609..e4a538c04 100644 --- a/src/pages/AddNormalOpeningHoursPage.tsx +++ b/src/pages/AddNormalOpeningHoursPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ApiDatePeriod } from '../common/lib/types'; import api from '../common/utils/api/api'; import NormalOpeningHoursForm from '../components/normal-opening-hours-form/NormalOpeningHoursForm'; @@ -10,11 +11,12 @@ const AddNormalOpeningHoursPage = ({ }: { resourceId: string; }): JSX.Element => { + const { t } = useTranslation(); const resource = useResource(resourceId); const datePeriodConfig = useDatePeriodConfig(); if (!resource || !datePeriodConfig) { - return

Ladataan...

; + return

{t('Common.IsLoading')}

; } const submitFn = (data: ApiDatePeriod): Promise => diff --git a/src/pages/EditExceptionOpeningHoursPage.tsx b/src/pages/EditExceptionOpeningHoursPage.tsx index 75e23244c..930e3b318 100644 --- a/src/pages/EditExceptionOpeningHoursPage.tsx +++ b/src/pages/EditExceptionOpeningHoursPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import api from '../common/utils/api/api'; import { ApiDatePeriod } from '../common/lib/types'; import ExceptionOpeningHoursForm from '../components/exception-opening-hours-form/ExceptionOpeningHoursForm'; @@ -13,6 +14,7 @@ const EditExceptionOpeningHoursPage = ({ resourceId: string; datePeriodId: string; }): JSX.Element => { + const { t } = useTranslation(); const resource = useResource(resourceId); const datePeriodConfig = useDatePeriodConfig(); const datePeriod = useDatePeriod(datePeriodId); @@ -21,7 +23,7 @@ const EditExceptionOpeningHoursPage = ({ api.patchDatePeriod(updatedDatePeriod); if (!resource || !datePeriodConfig || !datePeriod) { - return

Ladataan...

; + return

{t('Common.IsLoading')}

; } return ( diff --git a/src/pages/EditHolidaysPage.tsx b/src/pages/EditHolidaysPage.tsx index 93c15096a..329b7e24f 100644 --- a/src/pages/EditHolidaysPage.tsx +++ b/src/pages/EditHolidaysPage.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Checkbox, IconPenLine, LoadingSpinner } from 'hds-react'; import { FormProvider, useForm } from 'react-hook-form'; import { @@ -77,6 +78,7 @@ const HolidayForm = ({ actions: FormActions; onClose: () => void; }): JSX.Element => { + const { t } = useTranslation(); const { name, date: holidayDate } = holiday; const [isSaving, setIsSaving] = useState(false); const valueToUse = value || getDefaultFormValues({ name, holidayDate }); @@ -148,15 +150,15 @@ const HolidayForm = ({ - Tallenna + {t('Common.Submit')} - Peruuta + {t('Common.Cancel')}
@@ -177,6 +179,7 @@ const HolidayListItem = ({ datePeriodConfig: UiDatePeriodConfig; actions: FormActions; }): JSX.Element => { + const { t } = useTranslation(); const { language = Language.FI } = useAppContext(); const [checked, setChecked] = useState(!!value); const [willBeRemoved, setWillBeRemoved] = useState(false); @@ -215,13 +218,13 @@ const HolidayListItem = ({ setWillBeRemoved(true); await actions.delete(value); }} - title="Oletko varma että haluat poistaa aukiolojakson?" + title={t('OpeningHours.RemoveHolidaySpanConfirmTitle')} text={ <> -

Olet poistamassa aukiolojakson

+

{t('OpeningHours.RemoveHolidaySpanConfirmText')}

- {value.name.fi} + {value.name[language]}
{value.startDate}
@@ -229,10 +232,10 @@ const HolidayListItem = ({ } isLoading={willBeRemoved} - loadingText="Poistetaan aukiolojaksoa" + loadingText={t('OpeningHours.RemoveHolidaySpanLoading')} isOpen={isModalOpen} onClose={closeModal} - confirmText="Poista" + confirmText={t('OpeningHours.RemoveHolidaySpanConfirm')} /> ) : ( @@ -248,7 +251,7 @@ const HolidayListItem = ({ {willBeRemoved ? (

- Poistetaan aukiolojaksoa.. + {t('OpeningHours.RemoveHolidaySpanSpinner')}
) : ( @@ -283,7 +286,9 @@ const HolidayListItem = ({ onClick={() => setIsEditing(true)} type="button">