Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/localize-loading-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ioai/lerobot-studio': patch
---

Localize hardcoded English UI strings (en/zh/ja): the episode loading status messages ("Fetching frame data for episode N...", "Loading episode N...", "Reading metadata...", "Processing charts...", "Initializing data loader..."), the "No features available" empty state, the filter "clear search" accessible label, the sample cover image alt text and the image panel load errors.
4 changes: 2 additions & 2 deletions src/react/components/PanelTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const PanelTitle: React.FC<IDockviewPanelHeaderProps> = (props) => {
<DropdownMenuSubContent className="max-h-[300px] w-64 overflow-y-auto">
{features.length === 0 ? (
<div className="px-2 py-1.5 text-xs text-muted-foreground">
No features available
{t('common.noFeaturesAvailable')}
</div>
) : (
features.map((key) => {
Expand Down Expand Up @@ -175,7 +175,7 @@ export const PanelTitle: React.FC<IDockviewPanelHeaderProps> = (props) => {
<DropdownMenuSubContent className="max-h-[300px] w-64 overflow-y-auto">
{features.length === 0 ? (
<div className="px-2 py-1.5 text-xs text-muted-foreground">
No features available
{t('common.noFeaturesAvailable')}
</div>
) : (
features.map((key) => {
Expand Down
2 changes: 1 addition & 1 deletion src/react/components/open/SampleDatasetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const SampleDatasetCard: React.FC<SampleDatasetCardProps> = ({
{fallbackImageUrl ? (
<img
src={fallbackImageUrl}
alt="Default Cover"
alt={t('panels.welcome.defaultCover')}
className="h-12 w-12 opacity-50 object-contain"
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { memo, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Search, X } from 'lucide-react';
import { Input } from '@/ui';
import { Button } from '@/ui';
Expand Down Expand Up @@ -89,6 +90,7 @@ export const NestedHierarchicalFilterList = memo(function NestedHierarchicalFilt
getItemColor,
className,
}: NestedHierarchicalFilterListProps) {
const { t } = useTranslation();
const allItemIds = useMemo(() => getAllFeatureFilterItemIds(featureNodes), [featureNodes]);
const filteredNodes = useMemo(
() => filterFeatureNodes(featureNodes, searchValue),
Expand Down Expand Up @@ -139,7 +141,7 @@ export const NestedHierarchicalFilterList = memo(function NestedHierarchicalFilt
e.stopPropagation();
onSearchChange('');
}}
aria-label="clear-search"
aria-label={t('common.clearSearch')}
>
<X className="h-3.5 w-3.5" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { memo, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Search, X } from 'lucide-react';
import { Input } from '@/ui';
import { Button } from '@/ui';
Expand Down Expand Up @@ -72,6 +73,7 @@ export const HierarchicalFilterList = memo(function HierarchicalFilterList({
getGroupColor,
className,
}: HierarchicalFilterListProps) {
const { t } = useTranslation();
const allItemIds = useMemo(
() => groups.flatMap((group) => group.items.map((item) => item.id)),
[groups],
Expand Down Expand Up @@ -121,7 +123,7 @@ export const HierarchicalFilterList = memo(function HierarchicalFilterList({
e.stopPropagation();
onSearchChange('');
}}
aria-label="clear-search"
aria-label={t('common.clearSearch')}
>
<X className="h-3.5 w-3.5" />
</Button>
Expand Down
6 changes: 4 additions & 2 deletions src/react/components/panels/ImagePanel/ImagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface ImagePanelProps {
*/
export const ImagePanel: React.FC<ImagePanelProps> = ({ params }) => {
const { t } = useTranslation();
const tRef = useRef(t);
tRef.current = t;
const { info, dataLoader, imageService, subscribeFrameIndex } = useLeRobotData();
const { selectedEpisodeIndex } = useLeRobotSelection();

Expand Down Expand Up @@ -116,7 +118,7 @@ export const ImagePanel: React.FC<ImagePanelProps> = ({ params }) => {
const pathResult = loader.getEpisodeDataPath(episode);
if (!pathResult) {
if (mountedRef.current) {
setError('Cannot determine parquet file path');
setError(tRef.current('panels.image.noParquetPath'));
setIsLoading(false);
}
continue;
Expand Down Expand Up @@ -189,7 +191,7 @@ export const ImagePanel: React.FC<ImagePanelProps> = ({ params }) => {
selectedEpisodeRef.current === episode &&
featureKeyRef.current === key;
if (isStillCurrent) {
setError(e instanceof Error ? e.message : 'Failed to load image');
setError(e instanceof Error ? e.message : tRef.current('panels.image.loadFailed'));
setIsLoading(false);
}
} finally {
Expand Down
31 changes: 26 additions & 5 deletions src/react/contexts/LeRobotProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { NumericalColumnMap } from '@/platform';
import type { DataSource } from '@/platform';
import type { ValidationReport } from '@/core';
import { DirectoryDataSource } from '@/platform';
import { useTranslation } from '../i18n/reactI18nextCompat';
import { useLoading } from './LoadingContext';
import {
LeRobotContext,
Expand Down Expand Up @@ -125,6 +126,10 @@ export const LeRobotDataProvider: React.FC<{ children: React.ReactNode }> = ({ c
const [lastValidationReport, setLastValidationReport] = useState<ValidationReport | null>(null);
const [healthDialogOpen, setHealthDialogOpen] = useState(false);

const { t } = useTranslation();
// Keep t out of callback deps so a locale switch never re-triggers dataset loading.
const tRef = useRef(t);
tRef.current = t;
const {
upsertTask,
completeTask,
Expand Down Expand Up @@ -367,7 +372,11 @@ export const LeRobotDataProvider: React.FC<{ children: React.ReactNode }> = ({ c
setIsLoading(true);
setError(null);
const taskId = 'lerobot-init';
upsertTask({ id: taskId, phase: 'index', message: 'Initializing data loader...' });
upsertTask({
id: taskId,
phase: 'index',
message: tRef.current('loading.message.initLoader'),
});

let loader: LeRobotDataLoader | null = null;
try {
Expand Down Expand Up @@ -420,7 +429,11 @@ export const LeRobotDataProvider: React.FC<{ children: React.ReactNode }> = ({ c
await import('../../platform/services/LeRobotDataLoader');
loader = new LeRobotDataLoaderCtor(source);

upsertTask({ id: taskId, phase: 'read', message: 'Reading metadata...' });
upsertTask({
id: taskId,
phase: 'read',
message: tRef.current('loading.message.readMetadata'),
});
const lerobotInfo = await loader.initialize();
if (generation !== initGenerationRef.current) {
await loader.dispose().catch(() => {});
Expand Down Expand Up @@ -482,7 +495,11 @@ export const LeRobotDataProvider: React.FC<{ children: React.ReactNode }> = ({ c
setIsPlaying(false);
setError(null);
const taskId = 'lerobot-select-episode';
upsertTask({ id: taskId, phase: 'read', message: `Loading episode ${index}...` });
upsertTask({
id: taskId,
phase: 'read',
message: tRef.current('loading.message.loadEpisode', { index }),
});

setSelectedEpisodeIndex(index);
setCurrentFrames([]);
Expand Down Expand Up @@ -514,13 +531,17 @@ export const LeRobotDataProvider: React.FC<{ children: React.ReactNode }> = ({ c
upsertTask({
id: taskId,
phase: 'read',
message: `Fetching frame data for episode ${index}...`,
message: tRef.current('loading.message.fetchFrames', { index }),
});
const numericalData = await dataLoader.loadAllNumericalData(index, eagerColumns);

if (loadingEpisodeRef.current !== index) return false;

upsertTask({ id: taskId, phase: 'render', message: 'Processing charts...' });
upsertTask({
id: taskId,
phase: 'render',
message: tRef.current('loading.message.processCharts'),
});
const frames = buildPlaybackFrames(numericalData, info.fps ?? 30);
setCurrentFrames(frames);
const subtaskSource = await dataLoader.loadEpisodeSubtaskSource(index);
Expand Down
11 changes: 11 additions & 0 deletions src/react/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"copy": "Copy",
"copied": "Copied",
"close": "Close",
"noFeaturesAvailable": "No features available",
"clearSearch": "Clear search",
"loading": "Loading...",
"addPanel": "Add Panel",
Expand Down Expand Up @@ -51,6 +52,13 @@
"render": "Rendering panels...",
"ready": "Ready",
"error": "Load Failed"
},
"message": {
"initLoader": "Initializing data loader...",
"readMetadata": "Reading metadata...",
"loadEpisode": "Loading episode {{index}}...",
"fetchFrames": "Fetching frame data for episode {{index}}...",
"processCharts": "Processing charts..."
}
},
"errors": {
Expand Down Expand Up @@ -511,6 +519,8 @@
}
},
"image": {
"noParquetPath": "Cannot determine parquet file path",
"loadFailed": "Failed to load image",
"title": "Image Panel",
"placeholder": "Waiting for image file...",
"debug": {
Expand Down Expand Up @@ -608,6 +618,7 @@
}
},
"welcome": {
"defaultCover": "Default cover",
"title": "Open Data",
"subtitle": "Select local file/folder or enter remote URL",
"quickStart": "Quick Start",
Expand Down
11 changes: 11 additions & 0 deletions src/react/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"copy": "コピー",
"copied": "コピーしました",
"close": "閉じる",
"noFeaturesAvailable": "利用可能な特徴量がありません",
"clearSearch": "検索をクリア",
"loading": "読み込み中...",
"addPanel": "パネルを追加",
Expand Down Expand Up @@ -51,6 +52,13 @@
"render": "パネルを描画中...",
"ready": "準備完了",
"error": "読み込み失敗"
},
"message": {
"initLoader": "データローダーを初期化中...",
"readMetadata": "メタデータを読み込み中...",
"loadEpisode": "エピソード {{index}} を読み込み中...",
"fetchFrames": "エピソード {{index}} のフレームデータを取得中...",
"processCharts": "チャートを処理中..."
}
},
"errors": {
Expand Down Expand Up @@ -515,6 +523,8 @@
}
},
"image": {
"noParquetPath": "Parquet ファイルのパスを特定できません",
"loadFailed": "画像の読み込みに失敗しました",
"title": "画像パネル",
"placeholder": "画像ファイルを待機中...",
"debug": {
Expand Down Expand Up @@ -610,6 +620,7 @@
}
},
"welcome": {
"defaultCover": "デフォルトカバー",
"title": "データを開く",
"subtitle": "ローカルファイル/フォルダを選択するか、リモート URL を入力",
"quickStart": "クイックスタート",
Expand Down
11 changes: 11 additions & 0 deletions src/react/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"copy": "复制",
"copied": "已复制",
"close": "关闭",
"noFeaturesAvailable": "没有可用的特征",
"clearSearch": "清空搜索",
"loading": "加载中...",
"addPanel": "添加面板",
Expand Down Expand Up @@ -51,6 +52,13 @@
"render": "正在渲染面板...",
"ready": "加载完成",
"error": "加载失败"
},
"message": {
"initLoader": "正在初始化数据加载器...",
"readMetadata": "正在读取元数据...",
"loadEpisode": "正在加载 Episode {{index}}...",
"fetchFrames": "正在获取 Episode {{index}} 的帧数据...",
"processCharts": "正在处理图表..."
}
},
"errors": {
Expand Down Expand Up @@ -511,6 +519,8 @@
}
},
"image": {
"noParquetPath": "无法确定 Parquet 文件路径",
"loadFailed": "图像加载失败",
"title": "图像面板",
"placeholder": "等待加载图像文件...",
"debug": {
Expand Down Expand Up @@ -608,6 +618,7 @@
}
},
"welcome": {
"defaultCover": "默认封面",
"title": "浏览数据",
"subtitle": "选择本地文件、文件夹或输入远程 URL",
"quickStart": "快速开始",
Expand Down
Loading