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

This commit is contained in:
Pavel authored and GitHub committed 2023-12-11 09:43:58 -05:00
1 parent 5a5c4f58af
commit d14a8f0199
55 files changed
+319 -114

No files matched your search

@@ -390,7 +390,7 @@ function WorkList({
onClick={() => {}}
data-cy={`mode-${mode.routeName}-${studyInstanceUid}`}
>
{t(`Modes:${mode.displayName}`)}
{mode.displayName}
</LegacyButton>
</Link>
)
+2 -1
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) : '';
};
+4 -1
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"
}
@@ -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"
}
@@ -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"
}
@@ -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?"
}
@@ -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.",
@@ -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"
}
@@ -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"
}
@@ -0,0 +1,3 @@
{
"Tracked series": "{{trackedSeries}} Tracked series"
}
@@ -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"
}
@@ -0,0 +1,5 @@
{
"Series is tracked": "Series is tracked",
"Series is untracked": "Series is untracked",
"Viewport": "Viewport"
}
@@ -0,0 +1,4 @@
{
"Copied": "Copied",
"Failed to copy": "Failed to copy"
}
@@ -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"
}
+16
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,
+1 -1
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",
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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?"
}
@@ -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."
}
@@ -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"
}
@@ -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"
}
@@ -0,0 +1,3 @@
{
"Tracked series": "{{trackedSeries}} Test Tracked series"
}
@@ -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"
}
@@ -0,0 +1,5 @@
{
"Series is tracked": "Test Series is tracked",
"Series is untracked": "Test Series is untracked",
"Viewport": "Test Viewport"
}
@@ -0,0 +1,4 @@
{
"Copied": "Test Copied",
"Failed to copy": "Test Failed to copy"
}
@@ -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"
}
+28 -12
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,
},
};
+4 -4
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": "检查机构"
}
@@ -37,7 +37,7 @@ const DisplaySetMessageListTooltip = ({ messages, id }): React.ReactNode => {
marginTop: '12px',
}}
>
Display Set Messages
{t('Display Set Messages')}
</div>
<ol
style={{
@@ -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>
);
@@ -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') {
@@ -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)}),
};
}
};
@@ -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>
@@ -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>
);
@@ -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
@@ -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);
},
@@ -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 && [
{
@@ -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);
@@ -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>
)}
@@ -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>
)}
@@ -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>
@@ -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();