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:
parent
fdb6ca41d6
commit
1bf651e763
@ -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();
|
||||
},
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
@ -5,5 +5,7 @@
|
||||
"MAX": "Máximo",
|
||||
"NonTargets": "No objetivos",
|
||||
"Relabel": "Re-etiquetar",
|
||||
"Targets": "Objetivos"
|
||||
}
|
||||
"Targets": "Objetivos",
|
||||
"Export": "Exportar",
|
||||
"Create Report": "Crear reporte"
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
4
platform/i18n/src/locales/pt-BR/MeasurementTable.json
Normal file
4
platform/i18n/src/locales/pt-BR/MeasurementTable.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Export": "Exportar",
|
||||
"Create Report": "Criar relatório"
|
||||
}
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
14
platform/i18n/src/locales/test-LNG/AboutModal.json
Normal file
14
platform/i18n/src/locales/test-LNG/AboutModal.json
Normal 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"
|
||||
}
|
||||
54
platform/i18n/src/locales/test-LNG/Buttons.json
Normal file
54
platform/i18n/src/locales/test-LNG/Buttons.json
Normal 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"
|
||||
}
|
||||
8
platform/i18n/src/locales/test-LNG/CineDialog.json
Normal file
8
platform/i18n/src/locales/test-LNG/CineDialog.json
Normal 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"
|
||||
}
|
||||
16
platform/i18n/src/locales/test-LNG/Common.json
Normal file
16
platform/i18n/src/locales/test-LNG/Common.json
Normal 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"
|
||||
}
|
||||
5
platform/i18n/src/locales/test-LNG/DatePicker.json
Normal file
5
platform/i18n/src/locales/test-LNG/DatePicker.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Clear dates": "Clear dates",
|
||||
"End Date": "End Date",
|
||||
"Start Date": "Start Date"
|
||||
}
|
||||
6
platform/i18n/src/locales/test-LNG/Header.json
Normal file
6
platform/i18n/src/locales/test-LNG/Header.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"About": "Test About",
|
||||
"INVESTIGATIONAL USE ONLY": "Test Investigational",
|
||||
"Options": "Test Options",
|
||||
"Preferences": "Test Preferences"
|
||||
}
|
||||
12
platform/i18n/src/locales/test-LNG/MeasurementTable.json
Normal file
12
platform/i18n/src/locales/test-LNG/MeasurementTable.json
Normal 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"
|
||||
}
|
||||
13
platform/i18n/src/locales/test-LNG/Modals.json
Normal file
13
platform/i18n/src/locales/test-LNG/Modals.json
Normal 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."
|
||||
}
|
||||
3
platform/i18n/src/locales/test-LNG/Modes.json
Normal file
3
platform/i18n/src/locales/test-LNG/Modes.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Basic Viewer": "Test Basic Viewer"
|
||||
}
|
||||
8
platform/i18n/src/locales/test-LNG/PatientInfo.json
Normal file
8
platform/i18n/src/locales/test-LNG/PatientInfo.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Age": "Test Age",
|
||||
"Sex": "Test Sex",
|
||||
"MRN": "Test MRN",
|
||||
"Thickness": "Test Thickness",
|
||||
"Spacing": "Test Spacing",
|
||||
"Scanner": "Test Scanner"
|
||||
}
|
||||
4
platform/i18n/src/locales/test-LNG/SidePanel.json
Normal file
4
platform/i18n/src/locales/test-LNG/SidePanel.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Studies": "Test Studies",
|
||||
"Measurements": "Test Measurements"
|
||||
}
|
||||
5
platform/i18n/src/locales/test-LNG/StudyBrowser.json
Normal file
5
platform/i18n/src/locales/test-LNG/StudyBrowser.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Primary": "Test Primary",
|
||||
"Recent": "Test Recent",
|
||||
"All": "Test All"
|
||||
}
|
||||
21
platform/i18n/src/locales/test-LNG/StudyList.json
Normal file
21
platform/i18n/src/locales/test-LNG/StudyList.json
Normal 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"
|
||||
}
|
||||
3
platform/i18n/src/locales/test-LNG/ToolTip.json
Normal file
3
platform/i18n/src/locales/test-LNG/ToolTip.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Zoom": "toolTip1"
|
||||
}
|
||||
14
platform/i18n/src/locales/test-LNG/UserPreferencesModal.json
Normal file
14
platform/i18n/src/locales/test-LNG/UserPreferencesModal.json
Normal 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"
|
||||
}
|
||||
14
platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json
Normal file
14
platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json
Normal 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"
|
||||
}
|
||||
37
platform/i18n/src/locales/test-LNG/index.js
Normal file
37
platform/i18n/src/locales/test-LNG/index.js
Normal 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
|
||||
},
|
||||
};
|
||||
@ -54,6 +54,7 @@ const languagesMap = {
|
||||
zh: 'Chinese',
|
||||
'zh-CN': 'Chinese (China)',
|
||||
'zh-TW': 'Chinese (Taiwan)',
|
||||
'test-LNG': 'Test Language',
|
||||
};
|
||||
|
||||
const getLanguageLabel = (language) => {
|
||||
|
||||
@ -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}
|
||||
>
|
||||
|
||||
@ -352,7 +352,7 @@ function WorkList({
|
||||
className={classnames('font-bold', { 'ml-2': !isFirst })}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{mode.displayName}
|
||||
{t(`Modes:${mode.displayName}`)}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user