feat(i18n): enhanced i18n support (#3761)

This commit is contained in:
Pavel 2023-12-11 19:43:58 +05:00 committed by GitHub
parent 5a5c4f58af
commit d14a8f0199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 319 additions and 114 deletions

View File

@ -1,5 +1,6 @@
import React from 'react';
import { WrappedPanelStudyBrowser, PanelMeasurementTable } from './Panels';
import i18n from 'i18next';
// TODO:
// - No loading UI exists yet
@ -22,7 +23,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
name: 'seriesList',
iconName: 'tab-studies',
iconLabel: 'Studies',
label: 'Studies',
label: i18n.t('SidePanel:Studies'),
component: WrappedPanelStudyBrowser.bind(null, {
commandsManager,
extensionManager,
@ -33,8 +34,8 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
name: 'measure',
iconName: 'tab-linear',
iconLabel: 'Measure',
label: 'Measurements',
secondaryLabel: 'Measurements',
label: i18n.t('SidePanel:Measurements'),
secondaryLabel: i18n.t('SidePanel:Measurements'),
component: wrappedMeasurementPanel,
},
];

View File

@ -1,4 +1,5 @@
import { ButtonEnums } from '@ohif/ui';
import i18n from 'i18next';
const RESPONSE = {
NO_NEVER: -1,
@ -26,24 +27,24 @@ function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) {
function _askTrackMeasurements(uiViewportDialogService, viewportId) {
return new Promise(function (resolve, reject) {
const message = 'Track measurements for this series?';
const message = i18n.t('MeasurementTable:Track measurements for this series?');
const actions = [
{
id: 'prompt-begin-tracking-cancel',
type: ButtonEnums.type.secondary,
text: 'No',
text: i18n.t('Common:No'),
value: RESPONSE.CANCEL,
},
{
id: 'prompt-begin-tracking-no-do-not-ask-again',
type: ButtonEnums.type.secondary,
text: 'No, do not ask again',
text: i18n.t('MeasurementTable:No, do not ask again'),
value: RESPONSE.NO_NEVER,
},
{
id: 'prompt-begin-tracking-yes',
type: ButtonEnums.type.primary,
text: 'Yes',
text: i18n.t('Common:Yes'),
value: RESPONSE.SET_STUDY_AND_SERIES,
},
];

View File

@ -1,3 +1,5 @@
import i18n from 'i18next';
const RESPONSE = {
NO_NEVER: -1,
CANCEL: 0,
@ -32,17 +34,17 @@ function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) {
function _askTrackMeasurements(UIViewportDialogService, viewportId) {
return new Promise(function (resolve, reject) {
const message = 'Track measurements for this series?';
const message = i18n.t('MeasurementTable:Track measurements for this series?');
const actions = [
{ type: 'cancel', text: 'No', value: RESPONSE.CANCEL },
{ type: 'cancel', text: i18n.t('MeasurementTable:No'), value: RESPONSE.CANCEL },
{
type: 'secondary',
text: 'No, do not ask again for this series',
text: i18n.t('MeasurementTable:No, do not ask again'),
value: RESPONSE.NO_NOT_FOR_SERIES,
},
{
type: 'primary',
text: 'Yes',
text: i18n.t('MeasurementTable:Yes'),
value: RESPONSE.SET_STUDY_AND_SERIES,
},
];

View File

@ -1,5 +1,6 @@
import { Types } from '@ohif/core';
import { PanelMeasurementTableTracking, PanelStudyBrowserTracking } from './panels';
import i18n from 'i18next';
// TODO:
// - No loading UI exists yet
@ -11,7 +12,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
name: 'seriesList',
iconName: 'tab-studies',
iconLabel: 'Studies',
label: 'Studies',
label: i18n.t('SidePanel:Studies'),
component: PanelStudyBrowserTracking.bind(null, {
commandsManager,
extensionManager,
@ -23,7 +24,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
name: 'trackedMeasurements',
iconName: 'tab-linear',
iconLabel: 'Measure',
label: 'Measurements',
label: i18n.t('SidePanel:Measurements'),
component: PanelMeasurementTableTracking.bind(null, {
commandsManager,
extensionManager,

View File

@ -222,10 +222,10 @@ function TrackedCornerstoneViewport(props) {
patientAge: PatientAge || '',
MRN: PatientID || '',
thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}` : '',
thicknessUnits: 'mm',
thicknessUnits: t('mm'),
spacing:
SpacingBetweenSlices !== undefined
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm`
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}${t('mm')}`
: '',
scanner: ManufacturerModelName || '',
},
@ -312,6 +312,7 @@ function _getNextMeasurementUID(
}
function _getStatusComponent(isTracked) {
const { t } = useTranslation('TrackedCornerstoneViewport');
const trackedIcon = isTracked ? 'status-tracked' : 'status-untracked';
return (
@ -330,15 +331,11 @@ function _getStatusComponent(isTracked) {
<span className="text-common-light text-base">
{isTracked ? (
<>
Series is
<span className="font-bold text-white"> tracked</span> and can be viewed <br />{' '}
in the measurement panel
{t('Series is tracked and can be viewed in the measurement panel')}
</>
) : (
<>
Measurements for
<span className="font-bold text-white"> untracked </span>
series <br /> will not be shown in the <br /> measurements panel
{t('Measurements for untracked series will not be shown in the measurements panel')}
</>
)}
</span>

View File

@ -1,6 +1,7 @@
import toolbarButtons from './toolbarButtons.js';
import { hotkeys } from '@ohif/core';
import { id } from './id';
import i18n from 'i18next';
const configs = {
Length: {},
@ -45,7 +46,7 @@ function modeFactory({ modeConfiguration }) {
return {
id,
routeName: 'dev',
displayName: 'Basic Dev Viewer',
displayName: i18n.t('Modes:Basic Dev Viewer'),
/**
* Lifecycle hooks
*/

View File

@ -4,6 +4,7 @@ import { id } from './id';
import initToolGroups from './initToolGroups';
import moreTools from './moreTools';
import moreToolsMpr from './moreToolsMpr';
import i18n from 'i18next';
// Allow this mode by excluding non-imaging modalities such as SR, SEG
// Also, SM is not a simple imaging modalities, so exclude it.
@ -63,7 +64,7 @@ function modeFactory() {
// We should not be.
id,
routeName: 'basic-test',
displayName: 'Basic Test Mode',
displayName: i18n.t('Modes:Basic Test Mode'),
/**
* Lifecycle hooks
*/

View File

@ -4,6 +4,7 @@ import { id } from './id';
import initToolGroups from './initToolGroups';
import moreTools from './moreTools';
import moreToolsMpr from './moreToolsMpr';
import i18n from 'i18next';
// Allow this mode by excluding non-imaging modalities such as SR, SEG
// Also, SM is not a simple imaging modalities, so exclude it.
@ -69,7 +70,7 @@ function modeFactory({ modeConfiguration }) {
// We should not be.
id,
routeName: 'viewer',
displayName: 'Basic Viewer',
displayName: i18n.t('Modes:Basic Viewer'),
/**
* Lifecycle hooks
*/

View File

@ -1,4 +1,5 @@
import { hotkeys } from '@ohif/core';
import i18n from 'i18next';
import { id } from './id';
import toolbarButtons from './toolbarButtons';
@ -41,7 +42,7 @@ function modeFactory({ modeConfiguration }) {
// We should not be.
id,
routeName: 'microscopy',
displayName: 'Microscopy',
displayName: i18n.t('Modes:Microscopy'),
/**
* Lifecycle hooks

View File

@ -2,6 +2,7 @@ import { hotkeys } from '@ohif/core';
import { id } from './id';
import toolbarButtons from './toolbarButtons';
import initToolGroups from './initToolGroups';
import i18n from 'i18next';
const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
@ -44,7 +45,7 @@ function modeFactory({ modeConfiguration }) {
* Mode name, which is displayed in the viewer's UI in the workList, for the
* user to select the mode.
*/
displayName: 'Segmentation',
displayName: i18n.t('Modes:Segmentation'),
/**
* Runs when the Mode Route is mounted to the DOM. Usually used to initialize
* Services and other resources.

View File

@ -4,6 +4,7 @@ import { id } from './id.js';
import initToolGroups, { toolGroupIds } from './initToolGroups.js';
import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js';
import setFusionActiveVolume from './utils/setFusionActiveVolume.js';
import i18n from 'i18next';
const { MetadataProvider } = classes;
@ -38,7 +39,7 @@ function modeFactory({ modeConfiguration }) {
// We should not be.
id,
routeName: 'tmtv',
displayName: 'Total Metabolic Tumor Volume',
displayName: i18n.t('Modes:Total Metabolic Tumor Volume'),
/**
* Lifecycle hooks
*/

View File

@ -390,7 +390,7 @@ function WorkList({
onClick={() => {}}
data-cy={`mode-${mode.routeName}-${studyInstanceUid}`}
>
{t(`Modes:${mode.displayName}`)}
{mode.displayName}
</LegacyButton>
</Link>
)

View File

@ -1,4 +1,5 @@
import moment from 'moment';
import i18n from 'i18next';
/**
* Format date
@ -7,7 +8,7 @@ import moment from 'moment';
* @param {string} format Desired date format
* @returns {string} Formatted date
*/
export default (date, format = 'DD-MMM-YYYY') => {
export default (date, format = i18n.t('Common:localDateFormat','DD-MMM-YYYY')) => {
// moment(undefined) returns the current date, so return the empty string instead
return date ? moment(date).format(format) : '';
};

View File

@ -4,8 +4,10 @@
"Layout": "Layout",
"LOAD": "LOAD",
"Measurements": "Measurements",
"mm": "mm",
"More": "More",
"Next": "Next",
"No": "No",
"NoStudyDate": "No Study Date",
"Play": "Play",
"Previous": "Previous",
@ -14,5 +16,6 @@
"Series": "Series",
"Show": "Show",
"Stop": "Stop",
"StudyDate": "Study Date"
"StudyDate": "Study Date",
"Yes": "Yes"
}

View File

@ -0,0 +1,8 @@
{
"Context": "Context",
"Error Message": "Error Message",
"Something went wrong": "Something went wrong",
"in": "in",
"Sorry, something went wrong there. Try again.": "Sorry, something went wrong there. Try again.",
"Stack Trace": "Stack Trace"
}

View File

@ -0,0 +1,6 @@
{
"Field can't be empty": "Field can't be empty",
"Hotkey is already in use": "\"{{action}}\" is already using the \"{{pressedKeys}}\" shortcut.",
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut": "It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut",
"Shortcut combination is not allowed": "{{pressedKeys}} shortcut combination is not allowed"
}

View File

@ -3,8 +3,10 @@
"Delete": "Delete",
"Description": "Description",
"MAX": "MAX",
"Measurements": "Measurements",
"No, do not ask again": "No, do not ask again",
"NonTargets": "NonTargets",
"Relabel": "Relabel",
"Targets": "Targets",
"Measurements": "Measurements"
"Track measurements for this series?": "Track measurements for this series?"
}

View File

@ -1,4 +1,5 @@
{
"Display Set Messages": "Display Set Messages",
"1": "No valid instances found in series.",
"2": "Display set has missing position information.",
"3": "Display set is not a reconstructable 3D volume.",

View File

@ -0,0 +1,8 @@
{
"Basic Dev Viewer": "Basic Dev Viewer",
"Basic Test Mode": "Basic Test Mode",
"Basic Viewer": "Basic Viewer",
"Microscopy": "Microscopy",
"Segmentation": "Segmentation",
"Total Metabolic Tumor Volume": "Total Metabolic Tumor Volume"
}

View File

@ -0,0 +1,18 @@
{
"Active": "Active",
"Add new segmentation": "Add new segmentation",
"Add segment": "Add segment",
"Add segmentation": "Add segmentation",
"Delete": "Delete",
"Display inactive segmentations": "Display inactive segmentations",
"Export DICOM SEG": "Export DICOM SEG",
"Download DICOM SEG": "Download DICOM SEG",
"Download DICOM RTSTRUCT": "Download DICOM RTSTRUCT",
"Fill": "Fill",
"Inactive segmentations": "Inactive segmentations",
"Opacity": "Opacity",
"Outline": "Outline",
"Rename": "Rename",
"Segmentation": "Segmentation",
"Size": "Size"
}

View File

@ -0,0 +1,3 @@
{
"Tracked series": "{{trackedSeries}} Tracked series"
}

View File

@ -3,16 +3,18 @@
"ClearFilters": "Clear Filters",
"Description": "Description",
"Empty": "Empty",
"Filter list to 100 studies or less to enable sorting": "Filter the list to 100 studies or less to enable sorting",
"Instances": "Instances",
"MRN": "MRN",
"NumOfStudiesHiggerThan100Message": "Filter the list to 100 studies or less to enable sorting",
"Modality": "Modality",
"MRN": "MRN",
"Next": "Next >",
"No studies available": "No studies available",
"Number of studies": "Number of studies",
"Page": "Page",
"PatientName": "Patient Name",
"Previous": "< Back",
"Page": "Page",
"Next": "Next >",
"Results per page": "Results per page",
"Number of studies": "Studies",
"StudyDate": "Study Date",
"StudyList": "Study List"
"StudyList": "Study List",
"Upload": "Upload"
}

View File

@ -0,0 +1,5 @@
{
"Series is tracked": "Series is tracked",
"Series is untracked": "Series is untracked",
"Viewport": "Viewport"
}

View File

@ -0,0 +1,4 @@
{
"Copied": "Copied",
"Failed to copy": "Failed to copy"
}

View File

@ -0,0 +1,6 @@
{
"Series is tracked and can be viewed in the measurement panel":
"Series is tracked and can be viewed in the measurement panel",
"Measurements for untracked series will not be shown in the measurements panel":
"Measurements for untracked series will not be shown in the measurements panel"
}

View File

@ -4,11 +4,19 @@ import CineDialog from './CineDialog.json';
import Common from './Common.json';
import DataSourceConfiguration from './DataSourceConfiguration.json';
import DatePicker from './DatePicker.json';
import ErrorBoundary from './ErrorBoundary.json';
import Header from './Header.json';
import HotkeysValidators from './HotkeysValidators.json';
import MeasurementTable from './MeasurementTable.json';
import Modes from './Modes.json';
import SegmentationTable from './SegmentationTable.json';
import SidePanel from './SidePanel.json';
import StudyBrowser from './StudyBrowser.json';
import StudyItem from './StudyItem.json';
import StudyList from './StudyList.json';
import TooltipClipboard from './TooltipClipboard.json';
import ThumbnailTracked from './ThumbnailTracked.json';
import TrackedCornerstoneViewport from './TrackedCornerstoneViewport.json';
import UserPreferencesModal from './UserPreferencesModal.json';
import ViewportDownloadForm from './ViewportDownloadForm.json';
import Messages from './Messages.json';
@ -21,11 +29,19 @@ export default {
Common,
DataSourceConfiguration,
DatePicker,
ErrorBoundary,
Header,
HotkeysValidators,
MeasurementTable,
Modes,
SegmentationTable,
SidePanel,
StudyBrowser,
StudyItem,
StudyList,
TooltipClipboard,
ThumbnailTracked,
TrackedCornerstoneViewport,
UserPreferencesModal,
ViewportDownloadForm,
Messages,

View File

@ -3,9 +3,9 @@
"ClearFilters": "Limpiar filtros",
"Description": "Descripción",
"Empty": "vacío",
"Filter list to 100 studies or less to enable sorting": "Filtre la lista a 100 estudios o menos para habilitar la clasificación",
"Instances": "Instancias",
"MRN": "MRN",
"NumOfStudiesHiggerThan100Message": "Filtre la lista a 100 estudios o menos para habilitar la clasificación",
"Modality": "Modalidad",
"PatientName": "Nombre paciente",
"Previous": "< Anterior",

View File

@ -3,8 +3,10 @@
"Image": "Test Image",
"Layout": "Test Layout",
"Measurements": "Test Measurements",
"mm": "Test mm",
"More": "Test More",
"Next": "Test Next",
"No": "Test No",
"Play": "Test Play",
"Previous": "Test Previous",
"Reset": "Test Reset",
@ -12,5 +14,6 @@
"Series": "Test Series",
"Show": "Test Show",
"Stop": "Test Stop",
"StudyDate": "Test Study Date"
"StudyDate": "Test Study Date",
"Yes": "Test Yes"
}

View File

@ -0,0 +1,8 @@
{
"Context": "Test Context",
"Error Message": "Test Error Message",
"Something went wrong": "Test Something went wrong",
"in": "in",
"Sorry, something went wrong there. Try again.": "Test Sorry, something went wrong there. Try again.",
"Stack Trace": "Test Stack Trace"
}

View File

@ -0,0 +1,6 @@
{
"Field can't be empty": "Test Field can't be empty",
"Hotkey is already in use": "Test \"{{action}}\" is already using the \"{{pressedKeys}}\" shortcut.",
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut": "Test It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut",
"Shortcut combination is not allowed": "Test {{pressedKeys}} shortcut combination is not allowed"
}

View File

@ -6,7 +6,9 @@
"Delete": "Delete",
"Description": "Description",
"MAX": "MAX",
"No, do not ask again": "Test No, do not ask again",
"NonTargets": "NonTargets",
"Relabel": "Relabel",
"Targets": "Targets"
"Targets": "Targets",
"Track measurements for this series?": "Test Track measurements for this series?"
}

View File

@ -0,0 +1,16 @@
{
"Display Set Messages": "Test Display Set Messages",
"1": "Test No valid instances found in series.",
"2": "Test Display set has missing position information.",
"3": "Test Display set is not a reconstructable 3D volume.",
"4": "Test Multi frame display sets do not have pixel measurement information.",
"5": "Test Multi frame display sets do not have orientation information.",
"6": "Test Multi frame display sets do not have position information.",
"7": "Test Display set has missing frames.",
"8": "Test Display set has irregular spacing.",
"9": "Test Display set has inconsistent dimensions between frames.",
"10": "Test Display set has frames with inconsistent number of components.",
"11": "Test Display set has frames with inconsistent orientations.",
"12": "Test Display set has inconsistent position information.",
"13": "Test Unsupported display set."
}

View File

@ -1,3 +1,8 @@
{
"Basic Viewer": "Test Basic Viewer"
"Basic Dev Viewer": "Test Basic Dev Viewer",
"Basic Test Mode": "Test Basic Test Mode",
"Basic Viewer": "Test Basic Viewer",
"Microscopy": "Test Microscopy",
"Segmentation": "Test Segmentation",
"Total Metabolic Tumor Volume": "Test Total Metabolic Tumor Volume"
}

View File

@ -0,0 +1,18 @@
{
"Active": "Test Active",
"Add new segmentation": "Test Add new segmentation",
"Add segment": "Test Add segment",
"Add segmentation": "Test Add segmentation",
"Delete": "Test Delete",
"Display inactive segmentations": "Test Display inactive segmentations",
"Export DICOM SEG": "Test Export DICOM SEG",
"Download DICOM SEG": "Test Download DICOM SEG",
"Download DICOM RTSTRUCT": "Test Download DICOM RTSTRUCT",
"Fill": "Test Fill",
"Inactive segmentations": "Test Inactive segmentations",
"Opacity": "Test Opacity",
"Outline": "Test Outline",
"Rename": "Test Rename",
"Segmentation": "Test Segmentation",
"Size": "Test Size"
}

View File

@ -0,0 +1,3 @@
{
"Tracked series": "{{trackedSeries}} Test Tracked series"
}

View File

@ -1,21 +1,23 @@
{
"Previous": "Test < Previous",
"AccessionNumber": "Test Accession #",
"Accession": "Test Accession",
"Next >": "Test Next >",
"< Previous": "Test < Previous",
"Results per page": "Test Results per page",
"Description": "Test Description",
"Empty": "Test Empty",
"Filter list to 100 studies or less to enable sorting": "Test Filter list to 100 studies or less to enable sorting",
"Instances": "Test Instances",
"MRN": "Test MRN",
"Modality": "Test Modality",
"Next": "Test Next >",
"No studies available": "Test No studies available",
"Number of studies": "Test Number of studies",
"PatientName": "Test PatientName",
"Patient Name": "Test Patient Name",
"Description": "Test Description",
"Results per page": "Test Results per page",
"StudyDate": "Test Study Date",
"Study date": "Test Study Date",
"Number of 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"
"Upload": "Test Upload"
}

View File

@ -0,0 +1,5 @@
{
"Series is tracked": "Test Series is tracked",
"Series is untracked": "Test Series is untracked",
"Viewport": "Test Viewport"
}

View File

@ -0,0 +1,4 @@
{
"Copied": "Test Copied",
"Failed to copy": "Test Failed to copy"
}

View File

@ -0,0 +1,6 @@
{
"Series is tracked and can be viewed in the measurement panel":
"Test Series is tracked and can be viewed in the measurement panel",
"Measurements for untracked series will not be shown in the measurements panel":
"Test Measurements for untracked series will not be shown in the measurements panel"
}

View File

@ -3,17 +3,25 @@ import Buttons from './Buttons.json';
import CineDialog from './CineDialog.json';
import Common from './Common.json';
import DatePicker from './DatePicker.json';
import ErrorBoundary from './ErrorBoundary.json';
import Header from './Header.json';
import HotkeysValidators from './HotkeysValidators.json';
import MeasurementTable from './MeasurementTable.json';
import Messages from './Messages.json';
import Modals from './Modals.json';
import Modes from './Modes.json';
import PatientInfo from './PatientInfo.json';
import SegmentationTable from './SegmentationTable.json';
import SidePanel from './SidePanel.json';
import StudyBrowser from './StudyBrowser.json';
import StudyItem from './StudyItem.json';
import StudyList from './StudyList.json';
import ToolTip from './ToolTip.json';
import TooltipClipboard from './TooltipClipboard.json';
import ThumbnailTracked from './ThumbnailTracked.json';
import TrackedCornerstoneViewport from './TrackedCornerstoneViewport.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': {
@ -22,16 +30,24 @@ export default {
CineDialog,
Common,
DatePicker,
ErrorBoundary,
Header,
HotkeysValidators,
MeasurementTable,
Messages,
Modals,
Modes,
PatientInfo,
SegmentationTable,
SidePanel,
StudyBrowser,
StudyItem,
StudyList,
ToolTip,
TooltipClipboard,
ThumbnailTracked,
TrackedCornerstoneViewport,
UserPreferencesModal,
ViewportDownloadForm,
ToolTip,
StudyBrowser,
PatientInfo,
Modes,
SidePanel,
Modals,
},
};

View File

@ -1,5 +1,6 @@
{
"Empty": "无",
"Filter list to 100 studies or less to enable sorting": "将检查列表过滤到 100 个或更少以启用排序",
"Modality": "成像设备",
"PatientName": "患者姓名",
"StudyDate": "检查日期",
@ -14,14 +15,13 @@
"Instances": "图像数",
"Accession": "检查号",
"Results per page": "每页条数",
"< Previous": "上一页",
"Next >": "下一页",
"Previous": "上一页",
"Next": "下一页",
"Page": "页码",
"Start Date": "开始日期",
"Series": "序列",
"No studies available": "没有数据",
"Loading...": "加载中...",
"Select...": "选择...",
"InstitutionName": "检查机构",
"Filter list to 100 studies or less to enable sorting": "将检查列表过滤到 100 个或更少以启用排序"
"InstitutionName": "检查机构"
}

View File

@ -37,7 +37,7 @@ const DisplaySetMessageListTooltip = ({ messages, id }): React.ReactNode => {
marginTop: '12px',
}}
>
Display Set Messages
{t('Display Set Messages')}
</div>
<ol
style={{

View File

@ -1,12 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import Icon from '../Icon';
import Typography from '../Typography';
// TODO: Add loading spinner to OHIF + use it here.
const EmptyStudies = ({ className }) => {
const { t } = useTranslation('StudyList');
return (
<div className={classnames('inline-flex flex-col items-center', className)}>
<Icon
@ -17,7 +19,7 @@ const EmptyStudies = ({ className }) => {
className="text-primary-light"
variant="h5"
>
{'No studies available'}
{t('No studies available')}
</Typography>
</div>
);

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react';
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import i18n from 'i18next';
import Modal from '../Modal';
import Icon from '../Icon';
@ -9,9 +11,10 @@ import IconButton from '../IconButton';
const isProduction = process.env.NODE_ENV === 'production';
const DefaultFallback = ({ error, context, resetErrorBoundary, fallbackRoute }) => {
const { t } = useTranslation('ErrorBoundary');
const [showDetails, setShowDetails] = useState(false);
const title = `Something went wrong${!isProduction && ` in ${context}`}.`;
const subtitle = `Sorry, something went wrong there. Try again.`;
const title = `${t('Something went wrong')}${!isProduction && ` ${t('in')} ${context}`}.`;
const subtitle = t('Sorry, something went wrong there. Try again.');
return (
<div
className="ErrorFallback bg-primary-dark h-full w-full"
@ -21,8 +24,8 @@ const DefaultFallback = ({ error, context, resetErrorBoundary, fallbackRoute })
<p className="text-primary-light text-base">{subtitle}</p>
{!isProduction && (
<div className="bg-secondary-dark mt-5 space-y-2 rounded-md p-5 font-mono">
<p className="text-primary-light">Context: {context}</p>
<p className="text-primary-light">Error Message: {error.message}</p>
<p className="text-primary-light">{t('Context')}: {context}</p>
<p className="text-primary-light">{t('Error Message')}: {error.message}</p>
<IconButton
variant="contained"
@ -32,7 +35,7 @@ const DefaultFallback = ({ error, context, resetErrorBoundary, fallbackRoute })
onClick={() => setShowDetails(!showDetails)}
>
<React.Fragment>
<div>{'Stack Trace'}</div>
<div>{t('Stack Trace')}</div>
<Icon
width="15px"
height="15px"
@ -83,7 +86,7 @@ const ErrorBoundary = ({
closeButton
shouldCloseOnEsc
isOpen={isOpen}
title={'Something went wrong'}
title={i18n.t('ErrorBoundary:Something went wrong')}
onClose={() => {
setIsOpen(false);
if (fallbackRoute && typeof window !== 'undefined') {

View File

@ -1,4 +1,5 @@
import { MODIFIER_KEYS, DISALLOWED_COMBINATIONS } from './hotkeysConfig';
import i18n from 'i18next';
const formatPressedKeys = pressedKeysArray => pressedKeysArray.join('+');
@ -20,8 +21,8 @@ const findConflictingCommand = (hotkeys, currentCommandName, pressedKeys) => {
};
const ERROR_MESSAGES = {
MODIFIER: "It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut",
EMPTY: "Field can't be empty.",
MODIFIER: i18n.t('HotkeysValidators:It\'s not possible to define only modifier keys (ctrl, alt and shift) as a shortcut'),
EMPTY: i18n.t('HotkeysValidators:Field can\'t be empty'),
};
// VALIDATORS
@ -46,7 +47,7 @@ const conflictingValidator = ({ commandName, pressedKeys, hotkeys }) => {
if (conflictingCommand) {
return {
error: `"${conflictingCommand.label}" is already using the "${pressedKeys}" shortcut.`,
error: i18n.t('HotkeysValidators:Hotkey is already in use', {action: conflictingCommand.label, pressedKeys: pressedKeys }),
};
}
};
@ -62,7 +63,7 @@ const disallowedValidator = ({ pressedKeys = [] }) => {
if (hasDisallowedCombinations) {
return {
error: `"${formatPressedKeys(pressedKeys)}" shortcut combination is not allowed`,
error: i18n.t('HotkeysValidators:Shortcut combination is not allowed', {pressedKeys: formatPressedKeys(pressedKeys)}),
};
}
};

View File

@ -1,7 +1,9 @@
import React from 'react';
import Icon from '../Icon';
import { useTranslation } from 'react-i18next';
function AddSegmentRow({ onClick }) {
const { t } = useTranslation('SegmentationTable');
return (
<div
className="flex hover:cursor-pointer"
@ -13,7 +15,7 @@ function AddSegmentRow({ onClick }) {
<div className="grid h-[28px] w-[28px] place-items-center">
<Icon name="icon-add" />
</div>
<span className="text-[13px]">Add Segment</span>
<span className="text-[13px]">{t('Add segment')}</span>
</div>
</div>
</div>

View File

@ -1,7 +1,9 @@
import React from 'react';
import Icon from '../Icon';
import { useTranslation } from 'react-i18next';
function NoSegmentationRow({ onSegmentationAdd }) {
const { t } = useTranslation('SegmentationTable');
return (
<div
className="group"
@ -11,7 +13,7 @@ function NoSegmentationRow({ onSegmentationAdd }) {
<div className="grid h-[28px] w-[28px] place-items-center">
<Icon name="icon-add" />
</div>
<span className="text-[13px]">Add Segmentation</span>
<span className="text-[13px]">{t('Add segmentation')}</span>
</div>
</div>
);

View File

@ -4,6 +4,7 @@ import InputRange from '../InputRange';
import CheckBox from '../CheckBox';
import InputNumber from '../InputNumber';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
const getRoundedValue = value => {
return Math.round(value * 100) / 100;
@ -17,19 +18,20 @@ const ActiveSegmentationConfig = ({
setRenderFill,
setFillAlpha,
}) => {
const { t } = useTranslation('SegmentationTable');
return (
<div className="flex justify-between px-3 pt-[13px] text-[12px]">
<div className="flex flex-col items-start">
<div className="mb-[12px] text-white">Active</div>
<div className="mb-[12px] text-white">{t('Active')}</div>
<CheckBox
label="Outline"
label={t('Outline')}
checked={config.renderOutline}
labelClassName="text-[12px] pl-1 pt-1"
className="mb-[9px]"
onChange={setRenderOutline}
/>
<CheckBox
label="Fill"
label={t('Fill')}
checked={config.renderFill}
labelClassName="text-[12px] pl-1 pt-1"
className="mb-[9px]"
@ -38,7 +40,7 @@ const ActiveSegmentationConfig = ({
</div>
<div className="col-span-2 flex flex-col items-center">
<div className="mb-[12px] text-[10px] text-[#b3b3b3]">Opacity</div>
<div className="mb-[12px] text-[10px] text-[#b3b3b3]">{t('Opacity')}</div>
<InputRange
minValue={0}
maxValue={100}
@ -64,7 +66,7 @@ const ActiveSegmentationConfig = ({
</div>
<div className="flex flex-col items-center">
<div className="mb-[12px] text-[10px] text-[#b3b3b3]">Size</div>
<div className="mb-[12px] text-[10px] text-[#b3b3b3]">{t('Size')}</div>
<InputNumber
value={config.outlineWidthActive}
onChange={setOutlineWidthActive}
@ -82,10 +84,11 @@ const InactiveSegmentationConfig = ({
setRenderInactiveSegmentations,
setFillAlphaInactive,
}) => {
const { t } = useTranslation('SegmentationTable');
return (
<div className="px-3">
<CheckBox
label="Display Inactive Segmentations"
label={t('Display inactive segmentations')}
checked={config.renderInactiveSegmentations}
labelClassName="text-[12px]"
className="mb-[9px]"
@ -93,7 +96,7 @@ const InactiveSegmentationConfig = ({
/>
<div className="flex items-center space-x-2 pl-4">
<span className="text-[10px] text-[#b3b3b3]">Opacity</span>
<span className="text-[10px] text-[#b3b3b3]">{t('Opacity')}</span>
<InputRange
minValue={0}
maxValue={100}
@ -120,6 +123,7 @@ const SegmentationConfig = ({
setRenderInactiveSegmentations,
setRenderOutline,
}) => {
const { t } = useTranslation('SegmentationTable');
const { initialConfig } = segmentationConfig;
const [isMinimized, setIsMinimized] = useState(true);
return (
@ -144,7 +148,7 @@ const SegmentationConfig = ({
'rotate-90 transform': !isMinimized,
})}
/>
<span className="text-[12px] font-[300] text-[#d8d8d8]">{'Inactive Segmentations'}</span>
<span className="text-[12px] font-[300] text-[#d8d8d8]">{t('Inactive segmentations')}</span>
</div>
{!isMinimized && (
<InactiveSegmentationConfig

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Select, Icon, Dropdown } from '../../components';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
function SegmentationDropDownRow({
segmentations = [],
@ -23,6 +24,7 @@ function SegmentationDropDownRow({
value: s.id,
label: s.label,
}));
const { t } = useTranslation('SegmentationTable');
if (!activeSegmentation) {
return null;
@ -46,7 +48,7 @@ function SegmentationDropDownRow({
...(!disableEditing
? [
{
title: 'Add New Segmentation',
title: t('Add new segmentation'),
onClick: () => {
onSegmentationAdd();
},
@ -56,7 +58,7 @@ function SegmentationDropDownRow({
...(!disableEditing
? [
{
title: 'Rename',
title: t('Rename'),
onClick: () => {
onSegmentationEdit(activeSegmentation.id);
},
@ -64,7 +66,7 @@ function SegmentationDropDownRow({
]
: []),
{
title: 'Delete',
title: t('Delete'),
onClick: () => {
onSegmentationDelete(activeSegmentation.id);
},
@ -72,7 +74,7 @@ function SegmentationDropDownRow({
...(!disableEditing
? [
{
title: 'Export DICOM SEG',
title: t('Export DICOM SEG'),
onClick: () => {
storeSegmentation(activeSegmentation.id);
},
@ -81,13 +83,13 @@ function SegmentationDropDownRow({
: []),
...[
{
title: 'Download DICOM SEG',
title: t('Download DICOM SEG'),
onClick: () => {
onSegmentationDownload(activeSegmentation.id);
},
},
{
title: 'Download DICOM RTSTRUCT',
title: t('Download DICOM RTSTRUCT'),
onClick: () => {
onSegmentationDownloadRTSS(activeSegmentation.id);
},

View File

@ -6,6 +6,7 @@ import SegmentationDropDownRow from './SegmentationDropDownRow';
import NoSegmentationRow from './NoSegmentationRow';
import AddSegmentRow from './AddSegmentRow';
import SegmentationGroupSegment from './SegmentationGroupSegment';
import { useTranslation } from 'react-i18next';
const SegmentationGroupTable = ({
segmentations,
@ -70,11 +71,12 @@ const SegmentationGroupTable = ({
const activeSegmentation = segmentations?.find(
segmentation => segmentation.id === activeSegmentationId
);
const { t } = useTranslation('SegmentationTable');
return (
<div className="flex min-h-0 flex-col bg-black text-[13px] font-[300]">
<PanelSection
title="Segmentation"
title={t('Segmentation')}
actionIcons={
activeSegmentation && [
{

View File

@ -1,7 +1,6 @@
import classnames from 'classnames';
import PropTypes from 'prop-types';
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Icon from '../Icon';
import Tooltip from '../Tooltip';
@ -131,8 +130,6 @@ const getTabIconClassNames = (numTabs: number, isActiveTab: boolean) => {
};
const SidePanel = ({ side, className, activeTabIndex: activeTabIndexProp, tabs, onOpen }) => {
const { t } = useTranslation('SidePanel');
const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
const [activeTabIndex, setActiveTabIndex] = useState(0);

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 '../Icon';
@ -16,6 +17,7 @@ const StudyItem = ({
isActive,
onClick,
}) => {
const { t } = useTranslation('StudyItem');
return (
<div
className={classnames(
@ -57,7 +59,7 @@ const StudyItem = ({
name="tracked"
className="text-primary-light mr-2 w-4"
/>
{trackedSeries} Tracked Series
{t('Tracked series', {trackedSeries: trackedSeries})}
</div>
</div>
)}

View File

@ -48,7 +48,7 @@ const StudyListFilter = ({
onClick={onUploadClick}
>
<Icon name="icon-upload"></Icon>
<span>Upload</span>
<span>{t('Upload')}</span>
</div>
)}
</div>
@ -67,6 +67,12 @@ const StudyListFilter = ({
{t('ClearFilters')}
</LegacyButton>
)}
<Typography
variant="h6"
className="text-primary-light"
>
{`${t('Number of studies')}: `}
</Typography>
<Typography
variant="h6"
className="mr-2"
@ -74,12 +80,6 @@ const StudyListFilter = ({
>
{numOfStudies > 100 ? '>100' : numOfStudies}
</Typography>
<Typography
variant="h6"
className="text-primary-light"
>
{t('Number of studies')}
</Typography>
</div>
</div>
</div>
@ -99,7 +99,7 @@ const StudyListFilter = ({
{numOfStudies > 100 && (
<div className="container m-auto">
<div className="bg-primary-main rounded-b py-1 text-center text-base">
<p className="text-white">{t('NumOfStudiesHiggerThan100Message')}</p>
<p className="text-white">{t('Filter list to 100 studies or less to enable sorting')}</p>
</div>
</div>
)}

View File

@ -6,8 +6,9 @@ import Icon from '../Icon';
import Thumbnail from '../Thumbnail';
import Tooltip from '../Tooltip';
import { StringNumber } from '../../types';
import { useTranslation } from 'react-i18next';
const ThumbnailTracked = ({
function ThumbnailTracked ({
displaySetInstanceUID,
className,
imageSrc,
@ -24,7 +25,8 @@ const ThumbnailTracked = ({
viewportIdentificator,
isTracked,
isActive,
}) => {
}) {
const { t } = useTranslation('ThumbnailTracked');
const trackedIcon = isTracked ? 'circled-checkmark' : 'dotted-circle';
const viewportIdentificatorLabel = viewportIdentificator.join(', ');
const renderViewportLabels = () => {
@ -42,7 +44,7 @@ const ThumbnailTracked = ({
position="right"
content={
<div className="max-w-40 text-left">
Series is displayed <br /> in viewport {viewportIdentificatorLabel}
{`${t('Viewport')}: ${viewportIdentificatorLabel}`}
</div>
}
>
@ -85,13 +87,11 @@ const ThumbnailTracked = ({
</div>
<div className="flex flex-1 flex-col">
<span>
Series is
<span className="text-white">{isTracked ? ' tracked' : ' untracked'}</span>
<span className="text-white">{isTracked ? t('Series is tracked') : t('Series is untracked')}</span>
</span>
{!!viewportIdentificator.length && (
<span>
in viewport
<span className="ml-1 text-white">{viewportIdentificatorLabel}</span>
{`${t('Viewport')}: ${viewportIdentificatorLabel}`}
</span>
)}
</div>

View File

@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import classnames from 'classnames';
@ -9,6 +10,8 @@ const DELAY_TO_HIDE = 10; // it needs at least a little delay to prevent tooltip
const DELAY_TO_HIDE_AFTER_COPYING = 1000;
const TooltipClipboard = ({ children, text }) => {
const { t } = useTranslation('TooltipClipboard');
const [isActive, setIsActive] = useState(false);
const [message, setMessage] = useState(null);
const [isCopying, setIsCopying] = useState(false);
@ -21,10 +24,10 @@ const TooltipClipboard = ({ children, text }) => {
setIsCopying(true);
try {
await navigator.clipboard.writeText(text);
setMessage('Copied!');
setMessage(t('Copied'));
} catch (err) {
console.error('Failed to copy: ', err);
setMessage('Failed to copy!');
setMessage(t('Failed to copy'));
} finally {
refreshElementPosition();