chore: update French i18n (#5561)
This commit is contained in:
1 parent
ba140a05f7
commit
181c0f32c5
185 files changed
+2779
-374
No files matched your search
@@ -19,6 +19,7 @@ import {
|
||||
} from '@ohif/ui-next';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
import { contourSegmentation } from '@cornerstonejs/tools/utilities';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Segment } from '@cornerstonejs/tools/types';
|
||||
|
||||
const { LogicalOperation } = contourSegmentation;
|
||||
@@ -60,6 +61,7 @@ function SegmentSelector({
|
||||
segments: Segment[];
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
return (
|
||||
<div className="flex justify-between gap-6">
|
||||
<div>{label}</div>
|
||||
@@ -69,7 +71,7 @@ function SegmentSelector({
|
||||
value={value}
|
||||
>
|
||||
<SelectTrigger className="overflow-hidden">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
<SelectValue placeholder={t(placeholder)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{segments.map(segment => (
|
||||
@@ -89,6 +91,7 @@ function SegmentSelector({
|
||||
function LogicalContourOperationOptions() {
|
||||
const { servicesManager } = useSystem();
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
const { segmentationsWithRepresentations } = useActiveViewportSegmentationRepresentations();
|
||||
|
||||
const activeRepresentation = segmentationsWithRepresentations?.find(
|
||||
@@ -184,7 +187,7 @@ function LogicalContourOperationOptions() {
|
||||
})}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div>{operation.label}</div>
|
||||
<div>{t(operation.label)}</div>
|
||||
</div>
|
||||
<div className="bg-primary-dark flex h-[62px] w-[88px] items-center justify-center rounded-lg">
|
||||
<Icons.ByName name={operation.helperIcon}></Icons.ByName>
|
||||
@@ -210,7 +213,7 @@ function LogicalContourOperationOptions() {
|
||||
applyLogicalContourOperation();
|
||||
}}
|
||||
>
|
||||
{operation.label}
|
||||
{t(operation.label)}
|
||||
</Button>
|
||||
</div>
|
||||
<Separator className="bg-input mt-2 h-[1px]" />
|
||||
@@ -221,7 +224,7 @@ function LogicalContourOperationOptions() {
|
||||
onCheckedChange={setCreateNewSegment}
|
||||
></Switch>
|
||||
<Label htmlFor="logical-contour-operations-create-new-segment-switch">
|
||||
Create a new segment
|
||||
{t('Create a new segment')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="pl-9">
|
||||
@@ -230,7 +233,7 @@ function LogicalContourOperationOptions() {
|
||||
disabled={!createNewSegment}
|
||||
id="logical-contour-operations-create-new-segment-input"
|
||||
type="text"
|
||||
placeholder="New segment name"
|
||||
placeholder={t('New segment name')}
|
||||
value={newSegmentName}
|
||||
onChange={e => setNewSegmentName(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Input, Label, Separator } from '@ohif/ui-next';
|
||||
import { useRunCommand } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function SimplifyContourOptions() {
|
||||
const [areaThreshold, setAreaThreshold] = useState(10);
|
||||
|
||||
const runCommand = useRunCommand();
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
|
||||
return (
|
||||
<div className="flex w-auto w-[252px] flex-col gap-[8px] text-base font-normal leading-none">
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Fill contour holes</div>
|
||||
<div>{t('Fill contour holes')}</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
@@ -18,18 +20,18 @@ function SimplifyContourOptions() {
|
||||
runCommand('removeContourHoles');
|
||||
}}
|
||||
>
|
||||
Fill Holes
|
||||
{t('Fill Holes')}
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Remove Small Contours</div>
|
||||
<div>{t('Remove Small Contours')}</div>
|
||||
<div className="flex items-center gap-2 self-end">
|
||||
<Label
|
||||
htmlFor="simplify-contour-options"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
Area Threshold
|
||||
{t('Area Threshold')}
|
||||
</Label>
|
||||
<Input
|
||||
id="simplify-contour-options"
|
||||
@@ -48,12 +50,12 @@ function SimplifyContourOptions() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove Small Contours
|
||||
{t('Remove Small Contours')}
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Create New Segment from Holes</div>
|
||||
<div>{t('Create New Segment from Holes')}</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
@@ -61,7 +63,7 @@ function SimplifyContourOptions() {
|
||||
runCommand('convertContourHoles');
|
||||
}}
|
||||
>
|
||||
Create New Segment
|
||||
{t('Create New Segment')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Button, Separator } from '@ohif/ui-next';
|
||||
import { useRunCommand } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function SmoothContoursOptions() {
|
||||
const runCommand = useRunCommand();
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
|
||||
return (
|
||||
<div className="flex w-auto w-[245px] flex-col gap-[8px] text-base font-normal leading-none">
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Smooth all edges</div>
|
||||
<div>{t('Smooth all edges')}</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
@@ -16,12 +18,12 @@ function SmoothContoursOptions() {
|
||||
runCommand('smoothContours');
|
||||
}}
|
||||
>
|
||||
Smooth Edges
|
||||
{t('Smooth Edges')}
|
||||
</Button>
|
||||
<Separator className="bg-input mt-[20px] h-[1px]" />
|
||||
</div>
|
||||
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
|
||||
<div>Remove extra points</div>
|
||||
<div>{t('Remove extra points')}</div>
|
||||
<Button
|
||||
className="border-primary/60 border"
|
||||
variant="ghost"
|
||||
@@ -29,7 +31,7 @@ function SmoothContoursOptions() {
|
||||
runCommand('decimateContours');
|
||||
}}
|
||||
>
|
||||
Remove Points
|
||||
{t('Remove Points')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { utilities as cstUtils } from '@cornerstonejs/tools';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { useUIStateStore } from '@ohif/extension-default';
|
||||
|
||||
import LogicalContourOperationsOptions from './components/LogicalContourOperationsOptions';
|
||||
@@ -46,7 +47,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
if (!segmentations?.length) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: 'No segmentations available',
|
||||
disabledText: i18n.t('SegmentationPanel:No segmentations available'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
if (!segmentations?.length) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: disabledText ?? 'No segmentations available',
|
||||
disabledText: disabledText ?? i18n.t('SegmentationPanel:No segmentations available'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
if (!Object.keys(activeSegmentation.segments).length) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: 'Add segment to enable this tool',
|
||||
disabledText: i18n.t('SegmentationPanel:Add segment to enable this tool'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
if (!toolGroup) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: disabledText ?? 'Not available on the current viewport',
|
||||
disabledText: disabledText ?? i18n.t('SegmentationPanel:Not available on the current viewport'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
if (!toolGroup.hasTool(toolName) && !toolNames) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledText: disabledText ?? 'Not available on the current viewport',
|
||||
disabledText: disabledText ?? i18n.t('SegmentationPanel:Not available on the current viewport'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager, co
|
||||
<>
|
||||
<Toolbox
|
||||
buttonSectionId={toolbarService.sections.dynamicToolbox}
|
||||
title="Threshold Tools"
|
||||
title="Buttons:Threshold Tools"
|
||||
/>
|
||||
<PanelSegmentation
|
||||
servicesManager={servicesManager}
|
||||
|
||||
@@ -4,9 +4,9 @@ import PropTypes from 'prop-types';
|
||||
import { metaData, Enums, utilities, eventTarget } from '@cornerstonejs/core';
|
||||
import { Enums as csToolsEnums, UltrasoundPleuraBLineTool } from '@cornerstonejs/tools';
|
||||
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
||||
import { ViewportOverlay } from '@ohif/ui-next';
|
||||
import { ViewportOverlay, formatDICOMDate } from '@ohif/ui-next';
|
||||
import type { InstanceMetadata } from '@ohif/core/src/types';
|
||||
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { utils } from '@ohif/core';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import i18n from 'i18next';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
import { formatDICOMDate } from '@ohif/ui-next';
|
||||
|
||||
/**
|
||||
* Checks if value is valid.
|
||||
@@ -24,17 +26,6 @@ export function formatNumberPrecision(number, precision = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats DICOM date.
|
||||
*
|
||||
* @param {string} date
|
||||
* @param {string} strFormat
|
||||
* @returns {string} formatted date.
|
||||
*/
|
||||
export function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
|
||||
return moment(date, 'YYYYMMDD').format(strFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* DICOM Time is stored as HHmmss.SSS, where:
|
||||
* HH 24 hour time:
|
||||
@@ -71,3 +62,5 @@ export function getCompression(imageId) {
|
||||
|
||||
return 'Lossless / Uncompressed';
|
||||
}
|
||||
|
||||
export { formatDICOMDate };
|
||||
@@ -31,7 +31,7 @@ export const ExportSegmentationSubMenuItem: React.FC<ExportSegmentationSubMenuIt
|
||||
allowExport,
|
||||
actions,
|
||||
}) => {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
DropdownMenuItem,
|
||||
Icons,
|
||||
} from '@ohif/ui-next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function MeasumentsMenu(props) {
|
||||
const { group, classNames } = props;
|
||||
const { t } = useTranslation('MeasurementTable');
|
||||
if (!group.items?.length) {
|
||||
console.log('No items to iterate', group.items);
|
||||
return null;
|
||||
@@ -44,7 +46,7 @@ export function MeasumentsMenu(props) {
|
||||
className={`h-6 w-6 transition-opacity ${
|
||||
isSelected || !isVisible ? 'opacity-100' : 'opacity-50 group-hover:opacity-100'
|
||||
}`}
|
||||
aria-label={isVisible ? 'Hide' : 'Show'}
|
||||
aria-label={isVisible ? t('Hide') : t('Show')}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onAction(e, ['jumpToMeasurement', 'toggleVisibilityMeasurement']);
|
||||
@@ -70,7 +72,7 @@ export function MeasumentsMenu(props) {
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={e => onAction(e, 'removeMeasurement')}>
|
||||
<Icons.Delete className="text-foreground" />
|
||||
<span className="pl-2">Delete</span>
|
||||
<span className="pl-2">{t('Delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function SegmentationToolConfig() {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
|
||||
// Get initial states based on current configuration
|
||||
const [previewEdits, setPreviewEdits] = useState(false);
|
||||
@@ -38,7 +40,7 @@ export default function SegmentationToolConfig() {
|
||||
checked={previewEdits}
|
||||
onCheckedChange={handlePreviewEditsChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Preview edits before creating</span>
|
||||
<span className="text-foreground text-base">{t('Preview edits before creating')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -46,7 +48,7 @@ export default function SegmentationToolConfig() {
|
||||
checked={useCenterAsSegmentIndex}
|
||||
onCheckedChange={handleUseCenterAsSegmentIndexChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Use center as segment index</span>
|
||||
<span className="text-foreground text-base">{t('Use center as segment index')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -54,7 +56,7 @@ export default function SegmentationToolConfig() {
|
||||
checked={toggleSegmentEnabled}
|
||||
onCheckedChange={handleToggleSegmentEnabledChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Hover on segment border to activate</span>
|
||||
<span className="text-foreground text-base">{t('Hover on segment border to activate')}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -62,7 +64,7 @@ export default function SegmentationToolConfig() {
|
||||
checked={segmentLabelEnabled}
|
||||
onCheckedChange={handleSegmentLabelEnabledChange}
|
||||
/>
|
||||
<span className="text-foreground text-base">Show segment name on hover</span>
|
||||
<span className="text-foreground text-base">{t('Show segment name on hover')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Button, Icons } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function StudyMeasurementsActions({ items, StudyInstanceUID, measurementFilter, actions }) {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('MeasurementTable');
|
||||
const disabled = !items?.length;
|
||||
|
||||
if (disabled) {
|
||||
@@ -45,7 +47,7 @@ export function StudyMeasurementsActions({ items, StudyInstanceUID, measurementF
|
||||
}}
|
||||
>
|
||||
<Icons.Add />
|
||||
Create SR
|
||||
{t('Create SR')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -63,7 +65,7 @@ export function StudyMeasurementsActions({ items, StudyInstanceUID, measurementF
|
||||
}}
|
||||
>
|
||||
<Icons.Delete />
|
||||
Delete
|
||||
{t('Delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+6
-4
@@ -14,6 +14,7 @@ import {
|
||||
Switch,
|
||||
} from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useViewportDisplaySets } from '../../hooks/useViewportDisplaySets';
|
||||
import SelectItemWithModality from '../SelectItemWithModality';
|
||||
@@ -21,6 +22,7 @@ import { useViewportRendering } from '../../hooks';
|
||||
|
||||
function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: string }>) {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
const [pendingForegrounds, setPendingForegrounds] = useState<string[]>([]);
|
||||
const [pendingSegmentations, setPendingSegmentations] = useState<string[]>([]);
|
||||
const { toggleColorbar } = useViewportRendering(viewportId);
|
||||
@@ -230,7 +232,7 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
disabled={potentialForegroundDisplaySets.length === 0}
|
||||
>
|
||||
<Icons.Plus className="h-4 w-4" />
|
||||
Foreground
|
||||
{t('Common:Foreground')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -242,7 +244,7 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
dataCY={`AddSegmentationDataOverlay-${viewportId}`}
|
||||
>
|
||||
<Icons.Plus className="h-4 w-4" />
|
||||
Segmentation
|
||||
{t('Tools:Segmentation')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -320,7 +322,7 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
onValueChange={value => handlePendingSegmentationSelection(pendingId, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue placeholder="SELECT A SEGMENTATION" />
|
||||
<SelectValue placeholder={t('Common:SELECT A SEGMENTATION')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{potentialOverlayDisplaySets.map(item => (
|
||||
@@ -428,7 +430,7 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
onValueChange={value => handlePendingForegroundSelection(pendingId, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue placeholder="SELECT A FOREGROUND" />
|
||||
<SelectValue placeholder={t('Common:SELECT A FOREGROUND')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{potentialForegroundDisplaySets.map(item => (
|
||||
|
||||
@@ -98,7 +98,7 @@ export function Colormap({ viewportId }: { viewportId?: string } = {}): ReactEle
|
||||
{colormaps.map((colormap, index) => (
|
||||
<AllInOneMenu.Item
|
||||
key={index}
|
||||
label={colormap.description}
|
||||
label={t(`Colormaps:${colormap.Name}`, { defaultValue: colormap.Name })}
|
||||
useIconSpace={false}
|
||||
onClick={() => {
|
||||
setCurrentColormap(colormap);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { ReactElement, useState, useEffect, useCallback } from 'react';
|
||||
import { VolumeLightingProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeLighting({ viewportId, hasShade }: VolumeLightingProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
@@ -11,6 +12,7 @@ export function VolumeLighting({ viewportId, hasShade }: VolumeLightingProps): R
|
||||
diffuse: null,
|
||||
specular: null,
|
||||
});
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
// Single callback to handle all lighting property changes
|
||||
const onLightingChange = useCallback(
|
||||
@@ -45,9 +47,9 @@ export function VolumeLighting({ viewportId, hasShade }: VolumeLightingProps): R
|
||||
|
||||
// Configuration for our lighting properties
|
||||
const lightingProperties = [
|
||||
{ key: 'ambient', label: 'Ambient' },
|
||||
{ key: 'diffuse', label: 'Diffuse' },
|
||||
{ key: 'specular', label: 'Specular' },
|
||||
{ key: 'ambient', label: t('Ambient') },
|
||||
{ key: 'diffuse', label: t('Diffuse') },
|
||||
{ key: 'specular', label: t('Specular') },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
+4
-1
@@ -5,10 +5,13 @@ import { VolumeShift } from './VolumeShift';
|
||||
import { VolumeLighting } from './VolumeLighting';
|
||||
import { VolumeShade } from './VolumeShade';
|
||||
import { useViewportRendering } from '../../hooks/useViewportRendering';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeRenderingOptions({ viewportId }: { viewportId?: string } = {}): ReactElement {
|
||||
const { volumeRenderingQualityRange } = useViewportRendering(viewportId);
|
||||
const [hasShade, setShade] = useState(false);
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
return (
|
||||
<AllInOneMenu.ItemPanel>
|
||||
<VolumeRenderingQuality
|
||||
@@ -17,7 +20,7 @@ export function VolumeRenderingOptions({ viewportId }: { viewportId?: string } =
|
||||
/>
|
||||
<VolumeShift viewportId={viewportId} />
|
||||
<div className="mt-2 flex h-8 !h-[20px] w-full flex-shrink-0 items-center justify-start px-2 text-base">
|
||||
<div className="text-muted-foreground text-sm">Lighting</div>
|
||||
<div className="text-muted-foreground text-sm">{t('Lighting')}</div>
|
||||
</div>
|
||||
<div className="bg-background mt-1 mb-1 h-px w-full"></div>
|
||||
<div className="hover:bg-accent flex h-8 w-full flex-shrink-0 items-center px-2 text-base hover:rounded">
|
||||
|
||||
+4
-2
@@ -4,17 +4,19 @@ import React, { ReactElement } from 'react';
|
||||
import { VolumeRenderingPresetsContent } from './VolumeRenderingPresetsContent';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useViewportRendering } from '../../hooks/useViewportRendering';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeRenderingPresets({ viewportId }: { viewportId?: string } = {}): ReactElement {
|
||||
const { volumeRenderingPresets } = useViewportRendering(viewportId);
|
||||
const { servicesManager } = useSystem();
|
||||
const { uiDialogService } = servicesManager.services;
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
const onClickPresets = () => {
|
||||
uiDialogService.show({
|
||||
id: 'volume-rendering-presets',
|
||||
content: VolumeRenderingPresetsContent,
|
||||
title: 'Rendering Presets',
|
||||
title: t('Rendering Presets'),
|
||||
isDraggable: true,
|
||||
contentProps: {
|
||||
presets: volumeRenderingPresets,
|
||||
@@ -25,7 +27,7 @@ export function VolumeRenderingPresets({ viewportId }: { viewportId?: string } =
|
||||
|
||||
return (
|
||||
<AllInOneMenu.Item
|
||||
label="Rendering Presets"
|
||||
label={t('Rendering Presets')}
|
||||
icon={<Icons.VolumeRendering />}
|
||||
rightIcon={<Icons.ByName name="action-new-dialog" />}
|
||||
onClick={onClickPresets}
|
||||
|
||||
+4
-2
@@ -3,6 +3,7 @@ import React, { ReactElement, useState, useCallback } from 'react';
|
||||
import { PresetDialog } from '@ohif/ui-next';
|
||||
import { ViewportPreset, VolumeRenderingPresetsContentProps } from '../../types/ViewportPresets';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props extends VolumeRenderingPresetsContentProps {
|
||||
hide: () => void;
|
||||
@@ -12,6 +13,7 @@ export function VolumeRenderingPresetsContent({ presets, viewportId, hide }: Pro
|
||||
const { commandsManager } = useSystem();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [selectedPreset, setSelectedPreset] = useState<ViewportPreset | null>(null);
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchValue(event.target.value);
|
||||
@@ -41,7 +43,7 @@ export function VolumeRenderingPresetsContent({ presets, viewportId, hide }: Pro
|
||||
<PresetDialog.PresetSearch
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search all"
|
||||
placeholder={t('Search all', 'Search all')}
|
||||
/>
|
||||
</PresetDialog.PresetFilter>
|
||||
<PresetDialog.PresetGrid>
|
||||
@@ -71,7 +73,7 @@ export function VolumeRenderingPresetsContent({ presets, viewportId, hide }: Pro
|
||||
</PresetDialog.PresetBody>
|
||||
<FooterAction className="mt-4 flex-shrink-0">
|
||||
<FooterAction.Right>
|
||||
<FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
|
||||
<FooterAction.Secondary onClick={hide}>{t('Common:Cancel')}</FooterAction.Secondary>
|
||||
</FooterAction.Right>
|
||||
</FooterAction>
|
||||
</PresetDialog>
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ import React, { ReactElement, useCallback, useState, useEffect } from 'react';
|
||||
import { VolumeRenderingQualityProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeRenderingQuality({
|
||||
volumeRenderingQualityRange,
|
||||
@@ -11,6 +12,7 @@ export function VolumeRenderingQuality({
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const { min, max, step } = volumeRenderingQualityRange;
|
||||
const [quality, setQuality] = useState(null);
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
const onChange = useCallback(
|
||||
(value: number) => {
|
||||
@@ -51,7 +53,7 @@ export function VolumeRenderingQuality({
|
||||
onChange={onChange}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<Numeric.Label className="w-16">Quality</Numeric.Label>
|
||||
<Numeric.Label className="w-16">{t('Quality')}</Numeric.Label>
|
||||
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
|
||||
</div>
|
||||
</Numeric.Container>
|
||||
|
||||
@@ -2,11 +2,13 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import { VolumeShadeProps } from '../../types/ViewportPresets';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeShade({
|
||||
viewportId,
|
||||
onClickShade = bool => {},
|
||||
}: VolumeShadeProps): ReactElement {
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const [shade, setShade] = useState(true);
|
||||
@@ -29,7 +31,7 @@ export function VolumeShade({
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="flex-grow">Shade</span>
|
||||
<span className="flex-grow">{t('Shade')}</span>
|
||||
<Switch
|
||||
className="ml-2 flex-shrink-0"
|
||||
key={key}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'r
|
||||
import { VolumeShiftProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function VolumeShift({ viewportId }: VolumeShiftProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
@@ -19,6 +20,7 @@ export function VolumeShift({ viewportId }: VolumeShiftProps): ReactElement {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const { actor } = viewport.getActors()[0];
|
||||
const ofun = actor.getProperty().getScalarOpacity(0);
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
useEffect(() => {
|
||||
if (isBlocking) {
|
||||
@@ -65,7 +67,7 @@ export function VolumeShift({ viewportId }: VolumeShiftProps): ReactElement {
|
||||
onMouseUp={() => setIsBlocking(false)}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<Numeric.Label className="w-16">Shift</Numeric.Label>
|
||||
<Numeric.Label className="w-16">{t('Shift')}</Numeric.Label>
|
||||
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
|
||||
</div>
|
||||
</Numeric.Container>
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import { AllInOneMenu, ScrollArea, Switch, Tabs, TabsList, TabsTrigger } from '@ohif/ui-next';
|
||||
import { useViewportRendering } from '../../hooks/useViewportRendering';
|
||||
import { WindowLevelPreset } from '../../types/WindowLevel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function WindowLevel({ viewportId }: { viewportId?: string } = {}): ReactElement {
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
const { viewportDisplaySets } = useViewportRendering(viewportId);
|
||||
const [activeDisplaySetUID, setActiveDisplaySetUID] = useState<string | undefined>(
|
||||
viewportDisplaySets?.[0]?.displaySetInstanceUID
|
||||
@@ -68,7 +70,7 @@ export function WindowLevel({ viewportId }: { viewportId?: string } = {}): React
|
||||
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
|
||||
onClick={() => setShowPreview(!showPreview)}
|
||||
>
|
||||
<span className="flex-shrink-0">Preview in viewport</span>
|
||||
<span className="flex-shrink-0">{t('Preview in viewport')}</span>
|
||||
<Switch
|
||||
className="ml-auto flex-shrink-0"
|
||||
checked={showPreview}
|
||||
@@ -91,7 +93,7 @@ export function WindowLevel({ viewportId }: { viewportId?: string } = {}): React
|
||||
{windowLevelPresets.map((preset, index) => (
|
||||
<AllInOneMenu.Item
|
||||
key={index}
|
||||
label={preset.description}
|
||||
label={t(preset.description)}
|
||||
secondaryLabel={`${preset.window} / ${preset.level}`}
|
||||
useIconSpace={false}
|
||||
onClick={() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ExportSegmentationSubMenuItem } from '../components/ExportSegmentationS
|
||||
*/
|
||||
export const CustomDropdownMenuContent = () => {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
const {
|
||||
onSegmentationAdd,
|
||||
onSegmentationRemoveFromViewport,
|
||||
|
||||
@@ -19,7 +19,7 @@ export const CustomSegmentStatisticsHeader = ({
|
||||
}: CustomSegmentStatisticsHeaderProps) => {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const segment = segmentation.segments[segmentIndex];
|
||||
|
||||
@@ -95,6 +95,10 @@ function ViewportDownloadFormNew({
|
||||
<ImageModal.ImageSize
|
||||
width={dimensions.width.toString()}
|
||||
height={dimensions.height.toString()}
|
||||
widthLabel={t('Width')}
|
||||
heightLabel={t('Height')}
|
||||
widthPlaceholder={t('Width')}
|
||||
heightPlaceholder={t('Height')}
|
||||
onWidthChange={e => {
|
||||
onDimensionsChange({
|
||||
...dimensions,
|
||||
@@ -110,7 +114,7 @@ function ViewportDownloadFormNew({
|
||||
maxWidth={MAX_TEXTURE_SIZE.toString()}
|
||||
maxHeight={MAX_TEXTURE_SIZE.toString()}
|
||||
>
|
||||
{t('Image size')} <span className="text-muted-foreground">px</span>
|
||||
{t('Image size in pixels')}
|
||||
</ImageModal.ImageSize>
|
||||
|
||||
<ImageModal.SwitchOption
|
||||
|
||||
@@ -44,7 +44,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentationWithTools = props => {
|
||||
const { t } = useTranslation('SegmentationTable');
|
||||
const { t } = useTranslation('SegmentationPanel');
|
||||
const tKey = `${props.segmentationRepresentationType ?? 'Segmentation'} tools`;
|
||||
const tValue = t(tKey);
|
||||
|
||||
@@ -99,7 +99,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
|
||||
name: 'panelSegmentationWithToolsLabelMap',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: i18n.t('SegmentationTable:Labelmap'),
|
||||
label: i18n.t('SegmentationPanel:Labelmap'),
|
||||
component: props =>
|
||||
wrappedPanelSegmentationWithTools({
|
||||
...props,
|
||||
@@ -110,7 +110,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
|
||||
name: 'panelSegmentationWithToolsContour',
|
||||
iconName: 'tab-contours',
|
||||
iconLabel: 'Segmentation',
|
||||
label: i18n.t('SegmentationTable:Contour'),
|
||||
label: i18n.t('SegmentationPanel:Contour'),
|
||||
component: props =>
|
||||
wrappedPanelSegmentationWithTools({
|
||||
...props,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { utils } from '@ohif/ui-next';
|
||||
import { ViewportDataOverlayMenuWrapper } from './components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenuWrapper';
|
||||
import { ViewportOrientationMenuWrapper } from './components/ViewportOrientationMenu/ViewportOrientationMenuWrapper';
|
||||
@@ -14,7 +15,7 @@ import AdvancedRenderingControls from './components/AdvancedRenderingControls';
|
||||
|
||||
const getDisabledState = (disabledText?: string) => ({
|
||||
disabled: true,
|
||||
disabledText: disabledText ?? 'Not available on the current viewport',
|
||||
disabledText: disabledText ?? i18n.t('Buttons:Not available on the current viewport'),
|
||||
});
|
||||
|
||||
export default function getToolbarModule({ servicesManager, extensionManager }: withAppTypes) {
|
||||
@@ -396,7 +397,9 @@ export default function getToolbarModule({ servicesManager, extensionManager }:
|
||||
);
|
||||
|
||||
if (!hasAnySupportedModality) {
|
||||
return getDisabledState(disabledText || 'Tool not available for this modality');
|
||||
return getDisabledState(
|
||||
disabledText || i18n.t('Buttons:Tool not available for this modality')
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
function mapMeasurementToDisplay(measurement, displaySetService) {
|
||||
const { referenceSeriesUID } = measurement;
|
||||
@@ -14,7 +15,8 @@ function mapMeasurementToDisplay(measurement, displaySetService) {
|
||||
const { findingSites, finding, label: baseLabel, displayText: baseDisplayText } = measurement;
|
||||
|
||||
const firstSite = findingSites?.[0];
|
||||
const label = baseLabel || finding?.text || firstSite?.text || '(empty)';
|
||||
const label =
|
||||
baseLabel || finding?.text || firstSite?.text || i18n.t('MeasurementTable:empty');
|
||||
|
||||
// Initialize displayText with the structure used in Length.ts and CobbAngle.ts
|
||||
const displayText = {
|
||||
|
||||
@@ -67,7 +67,12 @@ const CornerstoneViewportDownloadForm = ({
|
||||
return () => {
|
||||
Object.keys(toolModeAndBindings).forEach(toolName => {
|
||||
const { mode, bindings } = toolModeAndBindings[toolName];
|
||||
toolGroup.setToolMode(toolName, mode, { bindings });
|
||||
try {
|
||||
toolGroup.setToolMode(toolName, mode, { bindings });
|
||||
} catch (error) {
|
||||
// Handle errors when restoring tool mode during cleanup (e.g., when tool state is undefined)
|
||||
console.debug('Error restoring tool mode during cleanup:', toolName, error);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { AboutModal } from '@ohif/ui-next';
|
||||
import detect from 'browser-detect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function AboutModalDefault() {
|
||||
const { t } = useTranslation('AboutModal');
|
||||
const { os, version, name } = detect();
|
||||
const browser = `${name[0].toUpperCase()}${name.substr(1)} ${version}`;
|
||||
const versionNumber = process.env.VERSION_NUMBER;
|
||||
@@ -18,11 +20,11 @@ function AboutModalDefault() {
|
||||
|
||||
<AboutModal.Body>
|
||||
<AboutModal.DetailItem
|
||||
label="Commit Hash"
|
||||
label={t('Commit Hash')}
|
||||
value={commitHash}
|
||||
/>
|
||||
<AboutModal.DetailItem
|
||||
label="Current Browser & OS"
|
||||
label={t('Current Browser & OS')}
|
||||
value={`${browser}, ${os}`}
|
||||
/>
|
||||
<AboutModal.SocialItem
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
function waitForElement(selector, maxAttempts = 20, interval = 25) {
|
||||
return new Promise(resolve => {
|
||||
let attempts = 0;
|
||||
@@ -23,8 +25,8 @@ export default {
|
||||
steps: [
|
||||
{
|
||||
id: 'scroll',
|
||||
title: 'Scrolling Through Images',
|
||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
||||
title: i18n.t('Onboarding:Scrolling Through Images'),
|
||||
text: i18n.t('Onboarding:You can scroll through the images using the mouse wheel or scrollbar.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
@@ -37,8 +39,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
title: i18n.t('Onboarding:Zooming In and Out'),
|
||||
text: i18n.t('Onboarding:You can zoom the images using the right click.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
@@ -51,8 +53,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'pan',
|
||||
title: 'Panning the Image',
|
||||
text: 'You can pan the images using the middle click.',
|
||||
title: i18n.t('Onboarding:Panning the Image'),
|
||||
text: i18n.t('Onboarding:You can pan the images using the middle click.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
@@ -65,8 +67,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'windowing',
|
||||
title: 'Adjusting Window Level',
|
||||
text: 'You can modify the window level using the left click.',
|
||||
title: i18n.t('Onboarding:Adjusting Window Level'),
|
||||
text: i18n.t('Onboarding:You can modify the window level using the left click.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
@@ -79,8 +81,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'length',
|
||||
title: 'Using the Measurement Tools',
|
||||
text: 'You can measure the length of a region using the Length tool.',
|
||||
title: i18n.t('Onboarding:Using the Measurement Tools'),
|
||||
text: i18n.t('Onboarding:You can measure the length of a region using the Length tool.'),
|
||||
attachTo: {
|
||||
element: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
on: 'bottom',
|
||||
@@ -90,12 +92,12 @@ export default {
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () =>
|
||||
waitForElement('[data-cy="MeasurementTools-split-button-primary]'),
|
||||
waitForElement('[data-cy="MeasurementTools-split-button-primary"]'),
|
||||
},
|
||||
{
|
||||
id: 'drawAnnotation',
|
||||
title: 'Drawing Length Annotations',
|
||||
text: 'Use the length tool on the viewport to measure the length of a region.',
|
||||
title: i18n.t('Onboarding:Drawing Length Annotations'),
|
||||
text: i18n.t('Onboarding:Use the length tool on the viewport to measure the length of a region.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'right',
|
||||
@@ -108,8 +110,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'trackMeasurement',
|
||||
title: 'Tracking Measurements in the Panel',
|
||||
text: 'Click yes to track the measurements in the measurement panel.',
|
||||
title: i18n.t('Onboarding:Tracking Measurements in the Panel'),
|
||||
text: i18n.t('Onboarding:Click yes to track the measurements in the measurement panel.'),
|
||||
attachTo: {
|
||||
element: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
on: 'bottom',
|
||||
@@ -122,8 +124,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'openMeasurementPanel',
|
||||
title: 'Opening the Measurements Panel',
|
||||
text: 'Click the measurements button to open the measurements panel.',
|
||||
title: i18n.t('Onboarding:Opening the Measurements Panel'),
|
||||
text: i18n.t('Onboarding:Click the measurements button to open the measurements panel.'),
|
||||
attachTo: {
|
||||
element: '#trackedMeasurements-btn',
|
||||
on: 'left-start',
|
||||
@@ -136,8 +138,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'scrollAwayFromMeasurement',
|
||||
title: 'Scrolling Away from a Measurement',
|
||||
text: 'Scroll the images using the mouse wheel away from the measurement.',
|
||||
title: i18n.t('Onboarding:Scrolling Away from a Measurement'),
|
||||
text: i18n.t('Onboarding:Scroll the images using the mouse wheel away from the measurement.'),
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
@@ -150,8 +152,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'jumpToMeasurement',
|
||||
title: 'Jumping to Measurements in the Panel',
|
||||
text: 'Click the measurement in the measurement panel to jump to it.',
|
||||
title: i18n.t('Onboarding:Jumping to Measurements in the Panel'),
|
||||
text: i18n.t('Onboarding:Click the measurement in the measurement panel to jump to it.'),
|
||||
attachTo: {
|
||||
element: '[data-cy="data-row"]',
|
||||
on: 'left-start',
|
||||
@@ -164,8 +166,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'changeLayout',
|
||||
title: 'Changing Layout',
|
||||
text: 'You can change the layout of the viewer using the layout button.',
|
||||
title: i18n.t('Onboarding:Changing Layout'),
|
||||
text: i18n.t('Onboarding:You can change the layout of the viewer using the layout button.'),
|
||||
attachTo: {
|
||||
element: '[data-cy="Layout"]',
|
||||
on: 'bottom',
|
||||
@@ -178,8 +180,8 @@ export default {
|
||||
},
|
||||
{
|
||||
id: 'selectLayout',
|
||||
title: 'Selecting the MPR Layout',
|
||||
text: 'Select the MPR layout to view the images in MPR mode.',
|
||||
title: i18n.t('Onboarding:Selecting the MPR Layout'),
|
||||
text: i18n.t('Onboarding:Select the MPR layout to view the images in MPR mode.'),
|
||||
attachTo: {
|
||||
element: '[data-cy="MPR"]',
|
||||
on: 'left-start',
|
||||
@@ -196,7 +198,7 @@ export default {
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Skip all',
|
||||
text: i18n.t('Onboarding:Skip all'),
|
||||
action() {
|
||||
this.complete();
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { useSystem, hotkeys as hotkeysModule } from '@ohif/core';
|
||||
import { UserPreferencesModal, FooterAction } from '@ohif/ui-next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -19,14 +19,40 @@ interface HotkeyDefinitions {
|
||||
|
||||
function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
const { hotkeysManager } = useSystem();
|
||||
const { t } = useTranslation('UserPreferencesModal');
|
||||
const { t, i18n: i18nextInstance } = useTranslation('UserPreferencesModal');
|
||||
|
||||
const { hotkeyDefinitions = {}, hotkeyDefaults = {} } = hotkeysManager;
|
||||
|
||||
const fallbackHotkeyDefinitions = useMemo(
|
||||
() =>
|
||||
hotkeysManager.getValidHotkeyDefinitions(
|
||||
hotkeysModule.defaults.hotkeyBindings
|
||||
) as HotkeyDefinitions,
|
||||
[hotkeysManager]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!Object.keys(hotkeyDefaults).length) {
|
||||
hotkeysManager.setDefaultHotKeys(hotkeysModule.defaults.hotkeyBindings);
|
||||
}
|
||||
|
||||
if (!Object.keys(hotkeyDefinitions).length) {
|
||||
hotkeysManager.setHotkeys(fallbackHotkeyDefinitions);
|
||||
}
|
||||
}, [hotkeysManager, hotkeyDefaults, hotkeyDefinitions, fallbackHotkeyDefinitions]);
|
||||
|
||||
const resolvedHotkeyDefaults = Object.keys(hotkeyDefaults).length
|
||||
? (hotkeyDefaults as HotkeyDefinitions)
|
||||
: fallbackHotkeyDefinitions;
|
||||
|
||||
const initialHotkeyDefinitions = Object.keys(hotkeyDefinitions).length
|
||||
? (hotkeyDefinitions as HotkeyDefinitions)
|
||||
: resolvedHotkeyDefaults;
|
||||
|
||||
const currentLanguage = currentLanguageFn();
|
||||
|
||||
const [state, setState] = useState({
|
||||
hotkeyDefinitions: hotkeyDefinitions as HotkeyDefinitions,
|
||||
hotkeyDefinitions: initialHotkeyDefinitions,
|
||||
languageValue: currentLanguage.value,
|
||||
});
|
||||
|
||||
@@ -51,12 +77,52 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
setState(state => ({
|
||||
...state,
|
||||
languageValue: defaultLanguage.value,
|
||||
hotkeyDefinitions: hotkeyDefaults as HotkeyDefinitions,
|
||||
hotkeyDefinitions: resolvedHotkeyDefaults,
|
||||
}));
|
||||
|
||||
hotkeysManager.restoreDefaultBindings();
|
||||
};
|
||||
|
||||
const displayNames = React.useMemo(() => {
|
||||
if (typeof Intl === 'undefined' || typeof Intl.DisplayNames !== 'function') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const locales = [state.languageValue, currentLanguage.value, i18nextInstance.language, 'en'];
|
||||
const uniqueLocales = Array.from(new Set(locales.filter(Boolean)));
|
||||
|
||||
try {
|
||||
return new Intl.DisplayNames(uniqueLocales, { type: 'language', fallback: 'none' });
|
||||
} catch (error) {
|
||||
console.warn('Intl.DisplayNames not supported for locales', uniqueLocales, error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [state.languageValue, currentLanguage.value, i18nextInstance.language]);
|
||||
|
||||
const getLanguageLabel = React.useCallback(
|
||||
(languageValue: string, fallbackLabel: string) => {
|
||||
const translationKey = `LanguageName.${languageValue}`;
|
||||
if (i18nextInstance.exists(translationKey, { ns: 'UserPreferencesModal' })) {
|
||||
return t(translationKey);
|
||||
}
|
||||
|
||||
if (displayNames) {
|
||||
try {
|
||||
const localized = displayNames.of(languageValue);
|
||||
if (localized && localized.toLowerCase() !== languageValue.toLowerCase()) {
|
||||
return localized.charAt(0).toUpperCase() + localized.slice(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`Unable to resolve display name for ${languageValue}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return fallbackLabel;
|
||||
},
|
||||
[displayNames, i18nextInstance, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<UserPreferencesModal>
|
||||
<UserPreferencesModal.Body>
|
||||
@@ -79,7 +145,7 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
key={lang.value}
|
||||
value={lang.value}
|
||||
>
|
||||
{lang.label}
|
||||
{getLanguageLabel(lang.value, lang.label)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -120,6 +186,9 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
onClick={() => {
|
||||
if (state.languageValue !== currentLanguage.value) {
|
||||
i18n.changeLanguage(state.languageValue);
|
||||
// Force page reload after language change to ensure all translations are applied
|
||||
window.location.reload();
|
||||
return; // Exit early since we're reloading
|
||||
}
|
||||
hotkeysManager.setHotkeys(state.hotkeyDefinitions);
|
||||
hotkeysModule.stopRecord();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Types } from '@ohif/core';
|
||||
import i18n from 'i18next';
|
||||
|
||||
const defaultDisplaySetSelector = {
|
||||
studyMatchingRules: [
|
||||
@@ -113,8 +114,8 @@ const priorViewport1 = {
|
||||
*/
|
||||
const hpMNCompare: Types.HangingProtocol.Protocol = {
|
||||
id: '@ohif/hpCompare',
|
||||
description: 'Compare two studies in various layouts',
|
||||
name: 'Compare Two Studies',
|
||||
description: i18n.t('Hps:Compare two studies in various layouts'),
|
||||
name: i18n.t('Hps:Compare Two Studies'),
|
||||
numberOfPriorsReferenced: 1,
|
||||
protocolMatchingRules: [
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
RMLOPrior,
|
||||
LMLOPrior,
|
||||
} from './utils/mammoDisplaySetSelector';
|
||||
import i18n from 'i18next';
|
||||
|
||||
const rightDisplayArea = {
|
||||
storeAsInitialCamera: true,
|
||||
@@ -30,7 +31,7 @@ const leftDisplayArea = {
|
||||
const hpMammography = {
|
||||
id: '@ohif/hpMammo',
|
||||
hasUpdatedPriorsInformation: false,
|
||||
name: 'Mammography Breast Screening',
|
||||
name: i18n.t('Hps:Mammography Breast Screening'),
|
||||
protocolMatchingRules: [
|
||||
{
|
||||
id: 'Mammography',
|
||||
|
||||
@@ -3,7 +3,8 @@ import classnames from 'classnames';
|
||||
|
||||
import listComponentGenerator from './listComponentGenerator';
|
||||
import './ViewportOverlay.css';
|
||||
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { formatDICOMDate } from '@ohif/ui-next';
|
||||
import { formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
const { formatPN } = utils;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import i18n from 'i18next';
|
||||
import * as cornerstone from '@cornerstonejs/core';
|
||||
import { formatDICOMDate } from '@ohif/ui-next';
|
||||
|
||||
/**
|
||||
* Checks if value is valid.
|
||||
@@ -24,17 +26,6 @@ export function formatNumberPrecision(number, precision) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats DICOM date.
|
||||
*
|
||||
* @param {string} date
|
||||
* @param {string} strFormat
|
||||
* @returns {string} formatted date.
|
||||
*/
|
||||
export function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
|
||||
return moment(date, 'YYYYMMDD').format(strFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* DICOM Time is stored as HHmmss.SSS, where:
|
||||
* HH 24 hour time:
|
||||
@@ -71,3 +62,5 @@ export function getCompression(imageId) {
|
||||
|
||||
return 'Lossless / Uncompressed';
|
||||
}
|
||||
|
||||
export { formatDICOMDate };
|
||||
@@ -1,11 +1,11 @@
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Formats DICOM date.
|
||||
* Re-exported from @ohif/ui-next for backward compatibility.
|
||||
*
|
||||
* @param {string} date
|
||||
* @param {string} strFormat
|
||||
*/
|
||||
export default function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
|
||||
return moment(date, 'YYYYMMDD').format(strFormat);
|
||||
}
|
||||
import { formatDICOMDate } from '@ohif/ui-next';
|
||||
|
||||
export { formatDICOMDate };
|
||||
export default formatDICOMDate;
|
||||
@@ -1,5 +1,5 @@
|
||||
import formatDICOMPatientName from './formatDICOMPatientName';
|
||||
import formatDICOMDate from './formatDICOMDate';
|
||||
import { formatDICOMDate } from '@ohif/ui-next';
|
||||
import formatDICOMTime from './formatDICOMTime';
|
||||
import formatNumberPrecision from './formatNumberPrecision';
|
||||
import isValidNumber from './isValidNumber';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import areaOfPolygon from './areaOfPolygon';
|
||||
|
||||
import { PubSubService } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
const EVENTS = {
|
||||
LABEL_UPDATED: 'labelUpdated',
|
||||
@@ -168,7 +169,8 @@ class RoiAnnotation extends PubSubService {
|
||||
* @returns {String} Text with geometry type and label
|
||||
*/
|
||||
getDetailedLabel() {
|
||||
const label = this.label ? `${this.label}` : '(empty)';
|
||||
const translatedEmpty = i18n?.t('MeasurementTable:empty') || '(empty)';
|
||||
const label = this.label ? `${this.label}` : translatedEmpty;
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,10 +253,10 @@ export default function PanelPetSUV() {
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="w-28 self-end"
|
||||
className="self-end px-4"
|
||||
onClick={updateMetadata}
|
||||
>
|
||||
Reload Data
|
||||
{t('Reload Data')}
|
||||
</Button>
|
||||
</div>
|
||||
</PanelSection.Content>
|
||||
|
||||
+6
-6
@@ -14,14 +14,14 @@ import { useTranslation } from 'react-i18next';
|
||||
export const ROI_STAT = 'roi_stat';
|
||||
const RANGE = 'range';
|
||||
|
||||
const options = [
|
||||
{ value: ROI_STAT, label: 'Max', placeHolder: 'Max' },
|
||||
{ value: RANGE, label: 'Range', placeHolder: 'Range' },
|
||||
];
|
||||
|
||||
function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
|
||||
const { t } = useTranslation('ROIThresholdConfiguration');
|
||||
|
||||
const options = [
|
||||
{ value: ROI_STAT, label: t('Max'), placeHolder: t('Max') },
|
||||
{ value: RANGE, label: t('Range'), placeHolder: t('Range') },
|
||||
];
|
||||
|
||||
const handlePercentageOfMaxSUVChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let value = e.target.value;
|
||||
|
||||
@@ -101,7 +101,7 @@ function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
|
||||
<div className="mr-2 text-sm">
|
||||
<div className="flex flex-col space-y-2">
|
||||
{/* Header */}
|
||||
<Label>Lower & Upper Ranges</Label>
|
||||
<Label>{t('Lower & Upper Ranges')}</Label>
|
||||
|
||||
{/* CT Row */}
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -6,6 +6,7 @@ import ROIThresholdConfiguration, {
|
||||
import * as cs3dTools from '@cornerstonejs/tools';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useSegmentations } from '@ohif/extension-cornerstone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LOWER_CT_THRESHOLD_DEFAULT = -1024;
|
||||
const UPPER_CT_THRESHOLD_DEFAULT = 1024;
|
||||
@@ -46,6 +47,7 @@ function RectangleROIOptions() {
|
||||
const { commandsManager } = useSystem();
|
||||
const segmentations = useSegmentations();
|
||||
const activeSegmentation = segmentations[0];
|
||||
const { t } = useTranslation('ROIThresholdConfiguration');
|
||||
|
||||
const runCommand = useCallback(
|
||||
(commandName, commandOptions = {}) => {
|
||||
@@ -92,7 +94,7 @@ function RectangleROIOptions() {
|
||||
className="my-3 mr-auto w-20"
|
||||
onClick={handleROIThresholding}
|
||||
>
|
||||
Run
|
||||
{t('Run')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { PanelPetSUV, PanelROIThresholdExport } from './Panels';
|
||||
import { Toolbox } from '@ohif/extension-default';
|
||||
import PanelTMTV from './Panels/PanelTMTV';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
function getPanelModule({ commandsManager, extensionManager, servicesManager }) {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
@@ -14,7 +15,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
return (
|
||||
<Toolbox
|
||||
buttonSectionId={toolbarService.sections.roiThresholdToolbox}
|
||||
title="Threshold Tools"
|
||||
title={i18n.t('ROIThresholdConfiguration:Threshold Tools')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +29,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
<>
|
||||
<Toolbox
|
||||
buttonSectionId={toolbarService.sections.roiThresholdToolbox}
|
||||
title="Threshold Tools"
|
||||
title={i18n.t('ROIThresholdConfiguration:Threshold Tools')}
|
||||
/>
|
||||
<PanelTMTV
|
||||
commandsManager={commandsManager}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Enums as csToolsEnums, UltrasoundPleuraBLineTool } from '@cornerstonejs/tools';
|
||||
import { eventTarget, utilities } from '@cornerstonejs/core';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
/* Layout */
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
* @returns The USAnnotationPanel component
|
||||
*/
|
||||
export default function USAnnotationPanel() {
|
||||
const { t } = useTranslation('USAnnotationPanel');
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
|
||||
/** ──────────────────────────────────────────────────────
|
||||
@@ -166,7 +168,7 @@ export default function USAnnotationPanel() {
|
||||
className="cursor-pointer"
|
||||
onClick={() => setDepthGuideCommand(!depthGuide)}
|
||||
>
|
||||
Depth guide toggle
|
||||
{t('Depth guide toggle')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -198,7 +200,7 @@ export default function USAnnotationPanel() {
|
||||
className="cursor-pointer"
|
||||
onClick={() => setShowPleuraPercentageCommand(!showPleuraPct)}
|
||||
>
|
||||
Show pleura percentage
|
||||
{t('Show pleura percentage')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,7 +210,7 @@ export default function USAnnotationPanel() {
|
||||
const renderSectorAnnotations = () => (
|
||||
<PanelSection.Content>
|
||||
<div className="flex flex-col gap-4 p-2">
|
||||
<Label>Sector Annotations</Label>
|
||||
<Label>{t('Sector Annotations')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Tabs
|
||||
defaultValue={UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.BLINE}
|
||||
@@ -216,10 +218,10 @@ export default function USAnnotationPanel() {
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value={UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.PLEURA}>
|
||||
<Icons.Plus /> Pleura line
|
||||
<Icons.Plus /> {t('Pleura line')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={UltrasoundPleuraBLineTool.USPleuraBLineAnnotationType.BLINE}>
|
||||
<Icons.Plus /> B-line
|
||||
<Icons.Plus /> {t('B-line')}
|
||||
</TabsTrigger>
|
||||
<Separator orientation="vertical" />
|
||||
<Separator orientation="vertical" />
|
||||
@@ -239,7 +241,7 @@ export default function USAnnotationPanel() {
|
||||
}
|
||||
>
|
||||
<Icons.Delete className="text-foreground" />
|
||||
<span className="pl-2">B-line annotation</span>
|
||||
<span className="pl-2">{t('B-line annotation')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
@@ -247,7 +249,7 @@ export default function USAnnotationPanel() {
|
||||
}
|
||||
>
|
||||
<Icons.Delete className="text-foreground" />
|
||||
<span className="pl-2">Pleura annotation</span>
|
||||
<span className="pl-2">{t('Pleura annotation')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -261,7 +263,7 @@ export default function USAnnotationPanel() {
|
||||
className="data-[state=checked]:bg-blue-500"
|
||||
/>
|
||||
<label htmlFor="show-overlay-switch" className="cursor-pointer text-blue-300">
|
||||
Show Overlay
|
||||
{t('Show Overlay')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -286,7 +288,7 @@ export default function USAnnotationPanel() {
|
||||
</Button> */}
|
||||
<Button variant="ghost" onClick={() => downloadJSON()}>
|
||||
<Icons.Download className="h-5 w-5" />
|
||||
<span>JSON</span>
|
||||
<span>{t('JSON')}</span>
|
||||
</Button>
|
||||
<Button variant="ghost" onClick={() => setShowOverlayCommand(!showOverlay)}>
|
||||
{showOverlay ? <Icons.Hide className="h-5 w-5" /> : <Icons.Show className="h-5 w-5" />}
|
||||
@@ -297,9 +299,9 @@ export default function USAnnotationPanel() {
|
||||
<thead>
|
||||
<tr className="border-b border-blue-900 text-blue-300">
|
||||
<th></th>
|
||||
<th className="py-2 px-3 text-left">Frame</th>
|
||||
<th className="py-2 px-3 text-center">Pleura lines</th>
|
||||
<th className="py-2 px-3 text-center">B-lines</th>
|
||||
<th className="py-2 px-3 text-left">{t('Frame')}</th>
|
||||
<th className="py-2 px-3 text-center">{t('Pleura lines')}</th>
|
||||
<th className="py-2 px-3 text-center">{t('B-lines')}</th>
|
||||
<th className="w-10"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -399,7 +401,7 @@ export default function USAnnotationPanel() {
|
||||
>
|
||||
{/* Workflow */}
|
||||
<PanelSection>
|
||||
<PanelSection.Header>Workflow</PanelSection.Header>
|
||||
<PanelSection.Header>{t('Workflow')}</PanelSection.Header>
|
||||
{renderWorkflowToggles()}
|
||||
</PanelSection>
|
||||
|
||||
@@ -411,13 +413,13 @@ export default function USAnnotationPanel() {
|
||||
|
||||
{/* Annotations */}
|
||||
<PanelSection>
|
||||
<PanelSection.Header> Annotations </PanelSection.Header>
|
||||
<PanelSection.Header>{t('Annotations')}</PanelSection.Header>
|
||||
{renderSectorAnnotations()}
|
||||
</PanelSection>
|
||||
|
||||
{/* Annotated frames */}
|
||||
<PanelSection className="flex-1">
|
||||
<PanelSection.Header> Annotated Frames </PanelSection.Header>
|
||||
<PanelSection.Header>{t('Annotated Frames')}</PanelSection.Header>
|
||||
{renderAnnotatedFrames()}
|
||||
</PanelSection>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user