feat(i18n): localize history item and story item dropdown menus

Covers the "..." action menu on both the History table (Play, Export
Audio, Export Package, Apply Effects, Regenerate, Delete) and on
individual story chat items (Play from here, Remove from Story).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
James Pine
2026-04-20 03:04:25 -07:00
co-authored by Claude Opus 4.7
parent 992ad96a29
commit ffa68dc356
4 changed files with 41 additions and 11 deletions
+7 -8
View File
@@ -673,7 +673,7 @@ export function HistoryTable() {
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-6 w-6 text-muted-foreground/50 hover:bg-muted-foreground/20 hover:text-muted-foreground" className="h-6 w-6 text-muted-foreground/50 hover:bg-muted-foreground/20 hover:text-muted-foreground"
aria-label="Actions" aria-label={t('history.actions.menu')}
disabled={isGenerating} disabled={isGenerating}
> >
<MoreHorizontal className="h-2 w-2" /> <MoreHorizontal className="h-2 w-2" />
@@ -684,37 +684,36 @@ export function HistoryTable() {
onClick={() => handlePlay(gen.id, gen.text, gen.profile_id)} onClick={() => handlePlay(gen.id, gen.text, gen.profile_id)}
> >
<Play className="mr-2 h-4 w-4" /> <Play className="mr-2 h-4 w-4" />
Play {t('history.actions.play')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleDownloadAudio(gen.id, gen.text)} onClick={() => handleDownloadAudio(gen.id, gen.text)}
disabled={exportGenerationAudio.isPending} disabled={exportGenerationAudio.isPending}
> >
<Download className="mr-2 h-4 w-4" /> <Download className="mr-2 h-4 w-4" />
Export Audio {t('history.actions.exportAudio')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleExportPackage(gen.id, gen.text)} onClick={() => handleExportPackage(gen.id, gen.text)}
disabled={exportGeneration.isPending} disabled={exportGeneration.isPending}
> >
<FileArchive className="mr-2 h-4 w-4" /> <FileArchive className="mr-2 h-4 w-4" />
Export Package {t('history.actions.exportPackage')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => handleApplyEffects(gen.id)}> <DropdownMenuItem onClick={() => handleApplyEffects(gen.id)}>
<Wand2 className="mr-2 h-4 w-4" /> <Wand2 className="mr-2 h-4 w-4" />
Apply Effects {t('history.actions.applyEffects')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => handleRegenerate(gen.id)}> <DropdownMenuItem onClick={() => handleRegenerate(gen.id)}>
<RotateCcw className="mr-2 h-4 w-4" /> <RotateCcw className="mr-2 h-4 w-4" />
Regenerate {t('history.actions.regenerate')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleDeleteClick(gen.id, gen.profile_name)} onClick={() => handleDeleteClick(gen.id, gen.profile_name)}
disabled={deleteGeneration.isPending} disabled={deleteGeneration.isPending}
// className="text-destructive focus:text-destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
Delete {t('common.delete')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -2,6 +2,7 @@ import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities'; import { CSS } from '@dnd-kit/utilities';
import { GripVertical, Mic, MoreHorizontal, Play, Trash2 } from 'lucide-react'; import { GripVertical, Mic, MoreHorizontal, Play, Trash2 } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
DropdownMenu, DropdownMenu,
@@ -34,6 +35,7 @@ export function StoryChatItem({
dragHandleProps, dragHandleProps,
isDragging, isDragging,
}: StoryChatItemProps) { }: StoryChatItemProps) {
const { t } = useTranslation();
const seek = useStoryStore((state) => state.seek); const seek = useStoryStore((state) => state.seek);
const serverUrl = useServerStore((state) => state.serverUrl); const serverUrl = useServerStore((state) => state.serverUrl);
const [avatarError, setAvatarError] = useState(false); const [avatarError, setAvatarError] = useState(false);
@@ -118,21 +120,26 @@ export function StoryChatItem({
<div className="shrink-0"> <div className="shrink-0">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Actions"> <Button
variant="ghost"
size="icon"
className="h-8 w-8"
aria-label={t('history.actions.menu')}
>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={handlePlay}> <DropdownMenuItem onClick={handlePlay}>
<Play className="mr-2 h-4 w-4" /> <Play className="mr-2 h-4 w-4" />
Play from here {t('storyContent.itemActions.playFromHere')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={onRemove} onClick={onRemove}
className="text-destructive focus:text-destructive" className="text-destructive focus:text-destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
Remove from Story {t('storyContent.itemActions.removeFromStory')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
+12
View File
@@ -104,6 +104,10 @@
"title": "No items in this story", "title": "No items in this story",
"hint": "Generate speech using the box below to add items" "hint": "Generate speech using the box below to add items"
}, },
"itemActions": {
"playFromHere": "Play from here",
"removeFromStory": "Remove from Story"
},
"toast": { "toast": {
"removeFailed": "Failed to remove item", "removeFailed": "Failed to remove item",
"reorderFailed": "Failed to reorder items", "reorderFailed": "Failed to reorder items",
@@ -112,6 +116,14 @@
} }
}, },
"history": { "history": {
"actions": {
"menu": "Actions",
"play": "Play",
"exportAudio": "Export Audio",
"exportPackage": "Export Package",
"applyEffects": "Apply Effects",
"regenerate": "Regenerate"
},
"deleteDialog": { "deleteDialog": {
"title": "Delete Generation", "title": "Delete Generation",
"body": "Are you sure you want to delete this generation from \"{{name}}\"? This action cannot be undone.", "body": "Are you sure you want to delete this generation from \"{{name}}\"? This action cannot be undone.",
@@ -104,6 +104,10 @@
"title": "此故事暂无项目", "title": "此故事暂无项目",
"hint": "使用下方输入框生成语音以添加项目" "hint": "使用下方输入框生成语音以添加项目"
}, },
"itemActions": {
"playFromHere": "从此处播放",
"removeFromStory": "从故事中移除"
},
"toast": { "toast": {
"removeFailed": "移除项目失败", "removeFailed": "移除项目失败",
"reorderFailed": "重新排序项目失败", "reorderFailed": "重新排序项目失败",
@@ -112,6 +116,14 @@
} }
}, },
"history": { "history": {
"actions": {
"menu": "操作",
"play": "播放",
"exportAudio": "导出音频",
"exportPackage": "导出包",
"applyEffects": "应用效果",
"regenerate": "重新生成"
},
"deleteDialog": { "deleteDialog": {
"title": "删除生成", "title": "删除生成",
"body": "确定要删除来自 \"{{name}}\" 的这次生成吗?此操作不可撤销。", "body": "确定要删除来自 \"{{name}}\" 的这次生成吗?此操作不可撤销。",