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

@@ -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);
}
});
};
}, []);