feat: Added support for i18n (#2439)

* Added translation for various react components in OHIF-v3

* Added a test language for debugging- test-LNG 

* Added copy for test language to not get overwritten
This commit is contained in:
Alireza authored and GitHub committed 2021-06-15 10:56:33 -04:00
1 parent fdb6ca41d6
commit 1bf651e763
42 files changed
+365 -63

No files matched your search

+2 -2
View File
@@ -7,7 +7,7 @@ import { useHistory } from 'react-router-dom';
import { NavBar, Svg, Icon, IconButton, Dropdown } from '@ohif/ui';
function Header({ children, menuOptions, isReturnEnabled, isSticky, WhiteLabeling }) {
const { t } = useTranslation();
const { t } = useTranslation('Header');
const history = useHistory();
// TODO: this should be passed in as a prop instead and the react-router-dom
@@ -39,7 +39,7 @@ function Header({ children, menuOptions, isReturnEnabled, isSticky, WhiteLabelin
<div className="flex items-center">{children}</div>
<div className="flex items-center">
<span className="mr-3 text-lg text-common-light">
{t('Header:INVESTIGATIONAL USE ONLY')}
{t('INVESTIGATIONAL USE ONLY')}
</span>
<Dropdown id="options" showDropdownIcon={false} list={menuOptions}>
<IconButton
@@ -2,12 +2,16 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { HotkeyField, Typography } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
/* TODO: Move these configs and utils to core? */
import { MODIFIER_KEYS } from './hotkeysConfig';
import { validate, splitHotkeyDefinitionsAndCreateTuples } from './utils';
const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErrors, onChange }) => {
const { t } = useTranslation('UserPreferencesModal');
const visibleHotkeys = Object.keys(hotkeyDefinitions)
.filter(key => hotkeyDefinitions[key].isEditable)
.reduce((obj, key) => {
@@ -57,7 +61,7 @@ const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErr
variant='subtitle'
className={classNames('pr-6 w-full text-right text-primary-light', !isFirst && 'hidden')}
>
Function
{t('Function')}
</Typography>
<Typography
variant='subtitle'
@@ -70,7 +74,7 @@ const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErr
variant='subtitle'
className={classNames('pr-6 pl-0 text-left text-primary-light', !isFirst && 'hidden')}
>
Shortcut
{t('Shortcut')}
</Typography>
<div className={classNames('flex flex-col w-32', isFirst && 'mt-5')}>
<HotkeyField
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Icon } from '../';
@@ -21,6 +22,8 @@ const InputLabelWrapper = ({
className,
children,
}) => {
const { t } = useTranslation("StudyList")
const onClickHandler = e => {
if (!isSortable) {
return;
@@ -38,7 +41,7 @@ const InputLabelWrapper = ({
onKeyDown={onClickHandler}
tabIndex="0"
>
{label}
{t(label)}
{isSortable && (
<Icon
name={sortIconMap[sortDirection]}
@@ -1,14 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import MeasurementItem from './MeasurementItem';
const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
const { t } = useTranslation("MeasurementTable")
return (
<div>
<div className="flex justify-between px-2 py-1 bg-secondary-main">
<span className="text-base font-bold tracking-widest text-white uppercase">
{title}
{t(title)}
</span>
<span className="text-base font-bold text-white">{amount}</span>
</div>
@@ -31,7 +34,7 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
<div className="w-6 py-1 text-base text-center transition duration-300 bg-primary-dark text-primary-light group-hover:bg-secondary-main"></div>
<div className="flex items-center justify-between flex-1 px-2 py-4">
<span className="flex items-center flex-1 mb-1 text-base text-primary-light">
No tracked measurements
{t('No tracked measurements')}
</span>
</div>
</div>
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Button, Icon } from '../';
@@ -69,6 +70,8 @@ const SidePanel = ({
defaultComponentOpen,
childComponents,
}) => {
const { t } = useTranslation('SidePanel');
const [componentOpen, setComponentOpen] = useState(defaultComponentOpen);
const openStatus = componentOpen ? 'open' : 'closed';
@@ -101,7 +104,7 @@ const SidePanel = ({
className="text-primary-active"
/>
<span className="mt-2 text-white text-xs">
{childComponent.iconLabel}
{t(childComponent.iconLabel)}
</span>
</Button>
);
@@ -139,7 +142,7 @@ const SidePanel = ({
style={{ ...position[side] }}
/>
<span className="flex-1 text-primary-active">
{childComponent.label}
{t(childComponent.label)}
</span>
</Button>
</div>
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import OutsideClickHandler from 'react-outside-click-handler';
import { useTranslation } from 'react-i18next';
import { Icon, Tooltip, ListMenu } from '@ohif/ui';
@@ -83,6 +84,8 @@ const SplitButton = ({
renderer,
onInteraction,
}) => {
const { t } = useTranslation('Buttons');
const { primaryToolId, toggles } = bState;
/* Bubbles up individual item clicks */
const getSplitButtonItems = items =>
@@ -221,14 +224,14 @@ const SplitButton = ({
className={classes.Content({ ...state })}
data-cy={`${groupId}-list-menu`}
>
<ListMenu items={state.items} renderer={renderer} />
<ListMenu items={state.items} renderer={(args) => renderer({ ...args, t })} />
</div>
</div>
</OutsideClickHandler>
);
};
const DefaultListItemRenderer = ({ icon, label, isActive }) => (
const DefaultListItemRenderer = ({ icon, label, isActive, t }) => (
<div
className={classNames(
'flex flex-row items-center p-3 h-8 w-full hover:bg-primary-dark',
@@ -239,7 +242,7 @@ const DefaultListItemRenderer = ({ icon, label, isActive }) => (
<Icon name={icon} className="w-5 h-5 text-common-bright" />
</span>
<span className="mr-5 text-base whitespace-pre text-common-bright">
{label}
{t(label)}
</span>
</div>
);
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import { ButtonGroup, Button, StudyItem, ThumbnailList } from '../';
@@ -29,6 +30,8 @@ const StudyBrowser = ({
onClickUntrack,
activeDisplaySetInstanceUID,
}) => {
const { t } = useTranslation("StudyBrowser")
const getTabContent = () => {
const tabData = tabs.find(tab => tab.name === activeTabName);
@@ -96,7 +99,7 @@ const StudyBrowser = ({
}}
disabled={isDisabled}
>
{label}
{t(label)}
</Button>
);
})}
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Table, TableHead, TableBody, TableRow, TableCell } from '../';
@@ -8,6 +9,9 @@ const StudyListExpandedRow = ({
seriesTableDataSource,
children,
}) => {
const { t } = useTranslation('StudyList');
return (
<div className="w-full bg-black py-4 pl-12 pr-2">
<div className="block">{children}</div>
@@ -18,7 +22,7 @@ const StudyListExpandedRow = ({
{Object.keys(seriesTableColumns).map(columnKey => {
return (
<TableCell key={columnKey}>
{seriesTableColumns[columnKey]}
{t(seriesTableColumns[columnKey])}
</TableCell>
);
})}
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Icon, Typography, InputGroup } from '../../components';
@@ -11,6 +12,7 @@ const StudyListFilter = ({
isFiltering,
numOfStudies,
}) => {
const { t } = useTranslation("StudyList")
const { sortBy, sortDirection } = filterValues;
const filterSorting = { sortBy, sortDirection };
const setFilterSorting = sortingValues => {
@@ -29,7 +31,7 @@ const StudyListFilter = ({
<div className="flex flex-row justify-between px-12 mb-5">
<div className="flex flex-row">
<Typography variant="h4" className="mr-6 text-primary-light">
Study list
{t('Study list')}
</Typography>
</div>
<div className="flex flex-row">
@@ -42,7 +44,7 @@ const StudyListFilter = ({
startIcon={<Icon name="cancel" />}
onClick={clearFilters}
>
Clear filters
{t('Clear filters')}
</Button>
)}
<Typography variant="h4" className="mr-2" data-cy={"num-studies"}>
@@ -52,7 +54,7 @@ const StudyListFilter = ({
variant="h6"
className="self-end pb-1 text-common-light"
>
Studies
{t('Studies')}
</Typography>
</div>
</div>
@@ -77,7 +79,7 @@ const StudyListFilter = ({
<div className="container m-auto">
<div className="py-1 text-base text-center rounded-b bg-primary-main">
<p className="text-white">
Filter list to 100 studies or less to enable sorting
{t('Filter list to 100 studies or less to enable sorting')}
</p>
</div>
</div>
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Button, ButtonGroup, Typography, Select } from '../';
import { useTranslation } from 'react-i18next';
const StudyListPagination = ({
onChangePage,
@@ -8,6 +9,8 @@ const StudyListPagination = ({
perPage,
onChangePerPage,
}) => {
const { t } = useTranslation("StudyList")
const navigateToPage = page => {
const toPage = page < 1 ? 1 : page;
onChangePage(toPage);
@@ -42,7 +45,7 @@ const StudyListPagination = ({
onChange={onSelectedRange}
/>
<Typography className="text-base opacity-60">
Results per page
{t('Results per page')}
</Typography>
</div>
<div className="">
@@ -64,14 +67,14 @@ const StudyListPagination = ({
className="border-primary-main py-2 px-2 text-base"
color="white"
onClick={() => navigateToPage(currentPage - 1)}
>{`< Previous`}</Button>
>{t(`< Previous`)}</Button>
<Button
size="initial"
className="border-primary-main py-2 px-4 text-base"
color="white"
onClick={() => navigateToPage(currentPage + 1)}
>
{`Next >`}
{t(`Next >`)}
</Button>
</ButtonGroup>
</div>
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import './tooltip.css';
@@ -33,6 +34,7 @@ const Tooltip = ({
isDisabled,
}) => {
const [isActive, setIsActive] = useState(false);
const { t } = useTranslation('Buttons');
const handleMouseOver = () => {
if (!isActive) {
@@ -72,7 +74,7 @@ const Tooltip = ({
}
)}
>
{content}
{typeof content === 'string' ? t(content) : content}
<svg
className="absolute h-4 text-primary-dark stroke-secondary-main"
style={arrowPositionStyle[position]}
@@ -66,10 +66,10 @@ const UserPreferences = ({ availableLanguages, defaultLanguage, currentLanguage,
return (
<div className="p-2">
<Section title="General">
<Section title={t('General')}>
<div className="flex flex-row justify-center items-center w-72">
<Typography variant="subtitle" className="mr-5 text-right h-full">
Language
{t('Language')}
</Typography>
<Select
isClearable={false}
@@ -79,7 +79,7 @@ const UserPreferences = ({ availableLanguages, defaultLanguage, currentLanguage,
/>
</div>
</Section>
<Section title="Hotkeys">
<Section title={t('Hotkeys')}>
<HotkeysPreferences
disabled={disabled}
hotkeyDefinitions={state.hotkeyDefinitions}
@@ -135,7 +135,7 @@ UserPreferences.defaultProps = {
onCancel: noop,
onSubmit: noop,
onReset: noop,
disabled: true
disabled: false
};
export default UserPreferences;
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Icon, ButtonGroup, Button, Tooltip, CinePlayer } from '../';
import useOnClickOutside from '../../utils/useOnClickOutside';
import { useTranslation } from 'react-i18next';
const classes = {
infoHeader: 'text-base text-primary-light',
@@ -369,6 +370,8 @@ function PatientInfo({
isOpen,
showPatientInfoRef,
}) {
const { t } = useTranslation("PatientInfo")
while (patientAge.charAt(0) === '0') {
patientAge = patientAge.substr(1);
}
@@ -394,7 +397,7 @@ function PatientInfo({
</span>
<div className="flex pb-4 mt-4 mb-4 border-b border-secondary-main">
<div className={classnames(classes.firstRow)}>
<span className={classnames(classes.infoHeader)}>Sex</span>
<span className={classnames(classes.infoHeader)}>{t('Sex')}</span>
<span
className={classnames(classes.infoText)}
title={patientSex}
@@ -403,7 +406,7 @@ function PatientInfo({
</span>
</div>
<div className={classnames(classes.row)}>
<span className={classnames(classes.infoHeader)}>Age</span>
<span className={classnames(classes.infoHeader)}>{t('Age')}</span>
<span
className={classnames(classes.infoText)}
title={patientAge}
@@ -412,7 +415,7 @@ function PatientInfo({
</span>
</div>
<div className={classnames(classes.row)}>
<span className={classnames(classes.infoHeader)}>MRN</span>
<span className={classnames(classes.infoHeader)}>{t('MRN')}</span>
<span className={classnames(classes.infoText)} title={MRN}>
{MRN}
</span>
@@ -421,7 +424,7 @@ function PatientInfo({
<div className="flex">
<div className={classnames(classes.firstRow)}>
<span className={classnames(classes.infoHeader)}>
Thickness
{t('Thickness')}
</span>
<span
className={classnames(classes.infoText)}
@@ -432,7 +435,7 @@ function PatientInfo({
</div>
<div className={classnames(classes.row)}>
<span className={classnames(classes.infoHeader)}>
Spacing
{t('Spacing')}
</span>
<span
className={classnames(classes.infoText)}
@@ -443,7 +446,7 @@ function PatientInfo({
</div>
<div className={classnames(classes.row)}>
<span className={classnames(classes.infoHeader)}>
Scanner
{t('Scanner')}
</span>
<span
className={classnames(classes.infoText)}
@@ -5,6 +5,7 @@ import React, {
createRef,
useRef,
} from 'react';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
@@ -47,6 +48,8 @@ const ViewportDownloadForm = ({
maximumSize,
canvasClass,
}) => {
const { t } = useTranslation('Modals');
const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState(['jpg']);
@@ -272,8 +275,7 @@ const ViewportDownloadForm = ({
return (
<div>
<Typography variant="h6">
Please specify the dimensions, filename, and desired type for the output
image.
{t('Please specify the dimensions, filename, and desired type for the output image.')}
</Typography>
<div className="flex flex-col mt-6">
@@ -282,7 +284,7 @@ const ViewportDownloadForm = ({
data-cy="file-name"
value={filename}
onChange={evt => setFilename(evt.target.value)}
label="File Name"
label={t("File Name")}
/>
{renderErrorHandler('filename')}
</div>
@@ -294,7 +296,7 @@ const ViewportDownloadForm = ({
type="number"
min={minimumSize}
max={maximumSize}
label="Image width (px)"
label={t("Image width (px)")}
value={dimensions.width}
onChange={evt => onDimensionsChange(evt.target.value, 'width')}
data-cy="image-width"
@@ -306,7 +308,7 @@ const ViewportDownloadForm = ({
type="number"
min={minimumSize}
max={maximumSize}
label="Image height (px)"
label={t("Image height (px)")}
value={dimensions.height}
onChange={evt => onDimensionsChange(evt.target.value, 'height')}
data-cy="image-height"
@@ -335,7 +337,7 @@ const ViewportDownloadForm = ({
<div>
<InputLabelWrapper
sortDirection="none"
label="File Type"
label={t("File Type")}
isSortable={false}
onLabelClick={() => {}}
>
@@ -363,7 +365,7 @@ const ViewportDownloadForm = ({
checked={showAnnotations}
onChange={event => setShowAnnotations(event.target.checked)}
/>
<Typography>Show Annotations</Typography>
<Typography>{t("Show Annotations")}</Typography>
</label>
</div>
</div>
@@ -375,7 +377,7 @@ const ViewportDownloadForm = ({
className="p-4 rounded bg-secondary-dark border-secondary-primary"
data-cy="image-preview"
>
<Typography variant="h5">Image preview</Typography>
<Typography variant="h5">{t("Image preview")}</Typography>
{activeViewport && (<div
className="mx-auto my-0"
style={{
@@ -397,7 +399,7 @@ const ViewportDownloadForm = ({
</div>)}
{!activeViewport &&
<Typography className="mt-4">
Active viewport has no displayed image
{t("Active viewport has no displayed image")}
</Typography>
}
</div>
@@ -405,7 +407,7 @@ const ViewportDownloadForm = ({
<div className="flex justify-end mt-4">
<Button data-cy="cancel-btn" variant="outlined" onClick={onClose}>
Cancel
{t("Cancel")}
</Button>
<Button
className="ml-2"
@@ -414,7 +416,7 @@ const ViewportDownloadForm = ({
color="primary"
data-cy="download-btn"
>
Download
{t("Download")}
</Button>
</div>
</div>
@@ -7,6 +7,7 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
const ModalContext = createContext(null);
const { Provider } = ModalContext;
@@ -36,6 +37,8 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
title: null,
customClassName: '',
};
const { t } = useTranslation('Modals');
const [options, setOptions] = useState(DEFAULT_OPTIONS);
@@ -86,7 +89,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
className={classNames(customClassName, ModalContent.className)}
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
title={title}
title={t(title)}
closeButton={closeButton}
onClose={hide}
>