chore: update French i18n (#5561)

This commit is contained in:
Martin Bellehumeur, M. Eng. authored and GitHub committed 2025-12-12 10:46:56 -05:00
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>
@@ -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 (
@@ -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,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}
@@ -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>
@@ -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;
}
+2 -2
View File
@@ -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>
@@ -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>
+3 -2
View File
@@ -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>