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 2021-06-15 10:56:33 -04:00 committed by GitHub
parent fdb6ca41d6
commit 1bf651e763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 365 additions and 63 deletions

View File

@ -10,6 +10,10 @@ import {
useModal,
} from '@ohif/ui';
import i18n from '@ohif/i18n';
const { availableLanguages, defaultLanguage, currentLanguage } = i18n;
import { useAppConfig } from '@state';
function Toolbar({ servicesManager }) {
@ -108,8 +112,12 @@ function ViewerLayout({
hotkeyDefaults
),
hotkeyDefinitions,
currentLanguage: currentLanguage(),
availableLanguages,
defaultLanguage,
onCancel: hide,
onSubmit: ({ hotkeyDefinitions }) => {
onSubmit: ({ hotkeyDefinitions, language }) => {
i18n.changeLanguage(language.value);
hotkeysManager.setHotkeys(hotkeyDefinitions);
hide();
},

View File

@ -1,13 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, ButtonGroup, Icon, IconButton } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
import { Button, ButtonGroup } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick }) {
const { t } = useTranslation('MeasurementTable');
return (
<React.Fragment>
<ButtonGroup color="black" size="inherit">
<Button className="text-base px-2 py-2" onClick={onExportClick}>
Export
{t('Export')}
</Button>
</ButtonGroup>
<Button
@ -17,7 +21,7 @@ function ActionButtons({ onExportClick, onCreateReportClick }) {
color="black"
onClick={onCreateReportClick}
>
Create Report
{t('Create Report')}
</Button>
</React.Fragment>
);

View File

@ -15,18 +15,20 @@ function timeout(delay) {
* Tests
*/
const threshold = 2400
describe('Queue', () => {
it('should bind functions to the queue', async () => {
const queue = new Queue(2);
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
timer(1200).then(now => {
timer(threshold).then(now => {
const elapsed = now - start;
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(elapsed >= threshold && elapsed < 2 * threshold).toBe(true);
});
const end = await timer(1200);
expect(end - start > 2400).toBe(true);
const end = await timer(threshold);
expect(end - start > 2 * threshold).toBe(true);
expect(mockedTimeout).toBeCalledTimes(2);
});
it('should prevent task execution when queue limit is reached', async () => {
@ -34,15 +36,15 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
const promise = timer(1200).then(time => time - start);
const promise = timer(threshold).then(time => time - start);
try {
await timer(1200);
await timer(threshold);
} catch (e) {
expect(Date.now() - start < 1200).toBe(true);
expect(Date.now() - start < threshold).toBe(true);
expect(e.message).toBe('Queue limit reached');
}
const elapsed = await promise;
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(elapsed >= threshold && elapsed < 2 * threshold).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
it('should safely bind tasks to the queue', async () => {
@ -51,16 +53,16 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bindSafe(mockedTimeout, mockedErrorHandler);
const start = Date.now();
const promise = timer(1200).then(time => time - start);
await timer(1200);
expect(Date.now() - start < 1200).toBe(true);
const promise = timer(threshold).then(time => time - start);
await timer(threshold);
expect(Date.now() - start < threshold).toBe(true);
expect(mockedErrorHandler).toBeCalledTimes(1);
expect(mockedErrorHandler).nthCalledWith(
1,
expect.objectContaining({ message: 'Queue limit reached' })
);
const elapsed = await promise;
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(elapsed >= threshold && elapsed < 2 * threshold).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
});

View File

@ -1,6 +1,8 @@
cp -r src/locales/test-LNG src/temp
rm -rf src/locales/
mkdir -p src/locales/
cd src/locales/
npx locize --config-path ../../.locize download --ver latest
cd ../../
node ./writeLocaleIndexFiles.js
cp -r src/temp src/locales/test-LNG

View File

@ -5,5 +5,7 @@
"MAX": "Máximo",
"NonTargets": "No objetivos",
"Relabel": "Re-etiquetar",
"Targets": "Objetivos"
}
"Targets": "Objetivos",
"Export": "Exportar",
"Create Report": "Crear reporte"
}

View File

@ -7,6 +7,7 @@ import nl from './nl/';
import pt_BR from './pt-BR/';
import vi from './vi/';
import zh from './zh/';
import test_lng from './test-LNG/';
export default {
...ar,
@ -18,4 +19,5 @@ export default {
...pt_BR,
...vi,
...zh,
...test_lng
};

View File

@ -0,0 +1,4 @@
{
"Export": "Exportar",
"Create Report": "Criar relatório"
}

View File

@ -5,6 +5,7 @@ import Common from './Common.json';
import DatePicker from './DatePicker.json';
import Header from './Header.json';
import UserPreferencesModal from './UserPreferencesModal.json';
import MeasurementTable from './MeasurementTable.json';
export default {
'pt-BR': {
@ -15,5 +16,6 @@ export default {
DatePicker,
Header,
UserPreferencesModal,
MeasurementTable,
},
};

View File

@ -0,0 +1,14 @@
{
"Browser": "Browser",
"Build Number": "Build Number",
"Latest Master Commits": "Latest Master Commits",
"More details": "More details",
"Name": "Name",
"OHIF Viewer - About": "OHIF Viewer - About",
"OS": "OS",
"Report an issue": "Report an issue",
"Repository URL": "Repository URL",
"Value": "Value",
"Version Information": "Version Information",
"Visit the forum": "Visit the forum"
}

View File

@ -0,0 +1,54 @@
{
"Acquired": "Test Acquired",
"Angle": "Test Angle",
"Axial": "Test Axial",
"Bidirectional": "Test Bidirectional",
"Brush": "Test Brush",
"CINE": "Test CINE",
"Cancel": "Test Cancel",
"Circle": "Test Circle",
"Clear": "Test Clear",
"Coronal": "Test Coronal",
"Crosshairs": "Test Crosshairs",
"Download": "Test Download",
"Ellipse": "Test Ellipse",
"Elliptical": "Test Elliptical",
"Flip H": "Test Flip H",
"Flip V": "Test Flip V",
"Freehand": "Test Freehand",
"Invert": "Test Invert",
"Layout": "Test $t(Common:Layout)",
"Length": "Test Length",
"Levels": "Test Levels",
"Magnify": "Test Magnify",
"Manual": "Test Manual",
"Measurements": "Test Measurements",
"More": "Test $t(Common:More)",
"Next": "Test $t(Common:Next)",
"Pan": "Test Pan",
"Play": "Test $t(Common:Play)",
"Previous": "Test $t(Common:Previous)",
"Probe": "Test Probe",
"ROI Window": "Test ROI Window",
"Rectangle": "Test Rectangle",
"Reset": "Test $t(Common:Reset)",
"Reset to Defaults": "Test $t(Common:Reset) to Defaults",
"Rotate Right": "Test Rotate Right",
"Sagittal": "Test Sagittal",
"Save": "Test Save",
"Stack Scroll": "Test Stack Scroll",
"Stop": "Test $t(Common:Stop)",
"Themes": "Test Themes",
"Zoom": "Test Zoom",
"Grid Layout": "Test Grid Layout",
"W/L Presets": "Test W/L Presets",
"More Measure Tools": "Test More Measure Tools",
"More Tools": "Test More Tools",
"Capture": "Test Capture",
"Annotation": "Test Annotation",
"Soft Tissue": "Test Soft Tissue",
"Lung": "Test Lung",
"Liver": "Test Liver",
"Bone": "Test Bone",
"Cine": "Test Cine"
}

View File

@ -0,0 +1,8 @@
{
"Next image": "$t(Common:Next) $t(Common:Image)",
"Play / Stop": "$t(Common:Play) / $t(Common:Stop)",
"Previous image": "$t(Common:Previous) $t(Common:Image)",
"Skip to first image": "Skip to first $t(Common:Image)",
"Skip to last image": "Skip to last $t(Common:Image)",
"fps": "fps"
}

View File

@ -0,0 +1,16 @@
{
"Close": "Test Close",
"Image": "Test Image",
"Layout": "Test Layout",
"Measurements": "Test Measurements",
"More": "Test More",
"Next": "Test Next",
"Play": "Test Play",
"Previous": "Test Previous",
"Reset": "Test Reset",
"RowsPerPage": "Test rows per page",
"Series": "Test Series",
"Show": "Test Show",
"Stop": "Test Stop",
"StudyDate": "Test Study Date"
}

View File

@ -0,0 +1,5 @@
{
"Clear dates": "Clear dates",
"End Date": "End Date",
"Start Date": "Start Date"
}

View File

@ -0,0 +1,6 @@
{
"About": "Test About",
"INVESTIGATIONAL USE ONLY": "Test Investigational",
"Options": "Test Options",
"Preferences": "Test Preferences"
}

View File

@ -0,0 +1,12 @@
{
"Measurements": "Test Measurements",
"No tracked measurements": "Test No tracked measurements",
"Create Report": "Test Create Report",
"Export": "Test Export",
"Delete": "Delete",
"Description": "Description",
"MAX": "MAX",
"NonTargets": "NonTargets",
"Relabel": "Relabel",
"Targets": "Targets"
}

View File

@ -0,0 +1,13 @@
{
"Download High Quality Image": "Test Download High Quality Image",
"Cancel": "Test Cancel",
"Download": "Test Download",
"File Name": "Test File Name",
"Active viewport has no displayed image": "Test Active viewport has no displayed image",
"Image preview": "Test Image preview",
"Show Annotations": "Test Show Annotations",
"File Type": "Test File Type",
"Image height (px)": "Test Image height (px)",
"Image width (px)": "Test Image width (px)",
"Please specify the dimensions, filename, and desired type for the output image.": "Test Please specify the dimensions, filename, and desired type for the output image."
}

View File

@ -0,0 +1,3 @@
{
"Basic Viewer": "Test Basic Viewer"
}

View File

@ -0,0 +1,8 @@
{
"Age": "Test Age",
"Sex": "Test Sex",
"MRN": "Test MRN",
"Thickness": "Test Thickness",
"Spacing": "Test Spacing",
"Scanner": "Test Scanner"
}

View File

@ -0,0 +1,4 @@
{
"Studies": "Test Studies",
"Measurements": "Test Measurements"
}

View File

@ -0,0 +1,5 @@
{
"Primary": "Test Primary",
"Recent": "Test Recent",
"All": "Test All"
}

View File

@ -0,0 +1,21 @@
{
"AccessionNumber": "Test Accession #",
"Accession": "Test Accession",
"Next >": "Test Next >",
"< Previous": "Test < Previous",
"Results per page": "Test Results per page",
"Empty": "Test Empty",
"MRN": "Test MRN",
"Modality": "Test Modality",
"PatientName": "Test PatientName",
"Patient Name": "Test Patient Name",
"Description": "Test Description",
"StudyDate": "Test Study Date",
"Study date": "Test Study Date",
"Studies": "Test Studies",
"Series": "Test Series",
"Instances": "Test Instances",
"Study List": "Test Study List",
"Study list": "Test Study list",
"Filter list to 100 studies or less to enable sorting": "Test Filter list to 100 studies or less to enable sorting"
}

View File

@ -0,0 +1,3 @@
{
"Zoom": "toolTip1"
}

View File

@ -0,0 +1,14 @@
{
"Cancel": "$t(Buttons:Cancel)",
"No hotkeys found": "No hotkeys are configured for this application. Hotkeys can be configured in the application's app-config.js file.",
"Reset to Defaults": "$t(Buttons:Reset to Defaults)",
"ResetDefaultMessage": "Preferences successfully reset to default. <br /> You must <strong>Save</strong> to perform this action.",
"Save": "$t(Buttons:Save)",
"SaveMessage": "Test Preferences saved",
"User Preferences": "Test User Preferences",
"Function": "Test function",
"Shortcut": "Test shortcut",
"Language": "Test language",
"Hotkeys": "Test hotkeys",
"General": "Test general"
}

View File

@ -0,0 +1,14 @@
{
"emptyFilenameError": "The file name cannot be empty.",
"fileType": "File Type",
"filename": "File Name",
"formTitle": "Please specify the dimensions, filename, and desired type for the output image.",
"imageHeight": "Image height (px)",
"imagePreview": "Image Preview",
"imageWidth": "Image width (px)",
"keepAspectRatio": "Keep aspect ratio",
"loadingPreview": "Loading Image Preview...",
"minHeightError": "The minimum valid height is 100px.",
"minWidthError": "The minimum valid width is 100px.",
"showAnnotations": "Show Annotations"
}

View File

@ -0,0 +1,37 @@
import AboutModal from './AboutModal.json';
import Buttons from './Buttons.json';
import CineDialog from './CineDialog.json';
import Common from './Common.json';
import DatePicker from './DatePicker.json';
import Header from './Header.json';
import MeasurementTable from './MeasurementTable.json';
import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json';
import ViewportDownloadForm from './ViewportDownloadForm.json';
import ToolTip from './ToolTip.json';
import StudyBrowser from './StudyBrowser.json';
import SidePanel from './SidePanel.json';
import PatientInfo from './PatientInfo.json';
import Modes from './Modes.json';
import Modals from './Modals.json';
export default {
'test-LNG': {
AboutModal,
Buttons,
CineDialog,
Common,
DatePicker,
Header,
MeasurementTable,
StudyList,
UserPreferencesModal,
ViewportDownloadForm,
ToolTip,
StudyBrowser,
PatientInfo,
Modes,
SidePanel,
Modals
},
};

View File

@ -54,6 +54,7 @@ const languagesMap = {
zh: 'Chinese',
'zh-CN': 'Chinese (China)',
'zh-TW': 'Chinese (Taiwan)',
'test-LNG': 'Test Language',
};
const getLanguageLabel = (language) => {

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

View File

@ -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

View File

@ -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]}

View File

@ -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>

View File

@ -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>

View File

@ -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>
);

View File

@ -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>
);
})}

View File

@ -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>
);
})}

View File

@ -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>

View File

@ -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>

View File

@ -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]}

View File

@ -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;

View File

@ -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)}

View File

@ -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>

View File

@ -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}
>

View File

@ -352,7 +352,7 @@ function WorkList({
className={classnames('font-bold', { 'ml-2': !isFirst })}
onClick={() => {}}
>
{mode.displayName}
{t(`Modes:${mode.displayName}`)}
</Button>
</Link>
);