From 6e13a30a59814a450e058dfabe4c6adb1942a36e Mon Sep 17 00:00:00 2001 From: James Pine Date: Mon, 20 Apr 2026 03:29:58 -0700 Subject: [PATCH] feat(i18n): localize Audio Channels tab (list, dialogs, device picker) Covers the "Audio Channels" title and "New Channel" button, the empty state, per-channel section labels (Output Devices / Assigned Voices), the Available Devices right pane with its three contextual hints, the "No voices assigned" fallback, and both Create/Edit dialogs (titles, descriptions, field labels, Select placeholders, and the "(default)" badge). Co-Authored-By: Claude Opus 4.7 (1M context) --- app/src/components/AudioTab/AudioTab.tsx | 78 ++++++++++----------- app/src/i18n/locales/en/translation.json | 40 +++++++++++ app/src/i18n/locales/zh-CN/translation.json | 40 +++++++++++ 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/app/src/components/AudioTab/AudioTab.tsx b/app/src/components/AudioTab/AudioTab.tsx index 5df94f0b..5f7549fb 100644 --- a/app/src/components/AudioTab/AudioTab.tsx +++ b/app/src/components/AudioTab/AudioTab.tsx @@ -1,6 +1,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Check, CheckCircle2, Edit, Plus, Speaker, Trash2 } from 'lucide-react'; import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -33,6 +34,7 @@ interface AudioDevice { } export function AudioTab() { + const { t } = useTranslation(); const platform = usePlatform(); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [editingChannel, setEditingChannel] = useState(null); @@ -119,14 +121,14 @@ export function AudioTab() { if (channelsLoading || devicesLoading) { return (
-
Loading...
+
{t('audioChannels.loading')}
); } const handleChannelDelete = async (e: React.MouseEvent, channelId: string) => { e.stopPropagation(); - if (await confirm('Delete this channel?')) { + if (await confirm(t('audioChannels.confirmDelete'))) { deleteChannel.mutate(channelId); } }; @@ -140,10 +142,10 @@ export function AudioTab() { return (
-

Audio Channels

+

{t('audioChannels.title')}

@@ -158,13 +160,10 @@ export function AudioTab() { {allChannels.length === 0 ? (
-

- No audio channels yet. Create your first channel to route voices to specific - devices. -

+

{t('audioChannels.empty.message')}

) : ( @@ -195,7 +194,7 @@ export function AudioTab() {
- Output Devices + {t('audioChannels.labels.outputDevices')}
{channel.device_ids.length > 0 @@ -224,7 +223,7 @@ export function AudioTab() {
- Assigned Voices + {t('audioChannels.labels.assignedVoices')}
@@ -270,13 +269,13 @@ export function AudioTab() { )} >
-

Available Devices

+

{t('audioChannels.devices.title')}

{selectedChannelId ? selectedChannel?.is_default - ? 'Default channel uses system default device' - : 'Click devices to add or remove them from the selected channel' - : 'Select a channel to assign devices'} + ? t('audioChannels.devices.defaultNote') + : t('audioChannels.devices.toggleHint') + : t('audioChannels.devices.selectHint')}

{allDevices.length > 0 ? ( @@ -344,8 +343,8 @@ export function AudioTab() {

{platform.metadata.isTauri - ? 'No audio devices found' - : 'Audio device selection requires Tauri'} + ? t('audioChannels.devices.empty') + : t('audioChannels.devices.requiresTauri')}

)} @@ -394,6 +393,7 @@ export function AudioTab() { } function ChannelVoicesList({ channelId }: { channelId: string }) { + const { t } = useTranslation(); const { data: voices } = useQuery({ queryKey: ['channel-voices', channelId], queryFn: () => apiClient.getChannelVoices(channelId), @@ -416,7 +416,7 @@ function ChannelVoicesList({ channelId }: { channelId: string }) { )) ) : ( - No voices assigned + {t('audioChannels.noVoicesAssigned')} )}
); @@ -430,6 +430,7 @@ interface CreateChannelDialogProps { } function CreateChannelDialog({ open, onOpenChange, devices, onCreate }: CreateChannelDialogProps) { + const { t } = useTranslation(); const [name, setName] = useState(''); const [selectedDevices, setSelectedDevices] = useState([]); @@ -445,23 +446,21 @@ function CreateChannelDialog({ open, onOpenChange, devices, onCreate }: CreateCh - Create Audio Channel - - Create a new audio channel (bus) to route voices to specific output devices. - + {t('audioChannels.createDialog.title')} + {t('audioChannels.createDialog.description')}
- + setName(e.target.value)} - placeholder="e.g., Virtual Cable, Broadcast" + placeholder={t('audioChannels.fields.namePlaceholder')} />
- + setName(e.target.value)} />
- + { @@ -625,7 +625,7 @@ function EditChannelDialog({ }} > - + {profiles.map((profile) => ( @@ -663,10 +663,10 @@ function EditChannelDialog({
diff --git a/app/src/i18n/locales/en/translation.json b/app/src/i18n/locales/en/translation.json index 1960f583..51b5bea0 100644 --- a/app/src/i18n/locales/en/translation.json +++ b/app/src/i18n/locales/en/translation.json @@ -21,6 +21,46 @@ "settings": "Settings", "updateBadge": "Update" }, + "audioChannels": { + "title": "Audio Channels", + "newChannel": "New Channel", + "loading": "Loading…", + "confirmDelete": "Delete this channel?", + "noVoicesAssigned": "No voices assigned", + "selectDevice": "Select device", + "addDevice": "Add device", + "addVoice": "Add voice", + "defaultSuffix": "default", + "empty": { + "message": "No audio channels yet. Create your first channel to route voices to specific devices.", + "action": "Create Channel" + }, + "labels": { + "outputDevices": "Output Devices", + "assignedVoices": "Assigned Voices" + }, + "devices": { + "title": "Available Devices", + "defaultNote": "Default channel uses system default device", + "toggleHint": "Click devices to add or remove them from the selected channel", + "selectHint": "Select a channel to assign devices", + "empty": "No audio devices found", + "requiresTauri": "Audio device selection requires Tauri" + }, + "fields": { + "name": "Channel Name", + "namePlaceholder": "e.g., Virtual Cable, Broadcast" + }, + "createDialog": { + "title": "Create Audio Channel", + "description": "Create a new audio channel (bus) to route voices to specific output devices.", + "action": "Create" + }, + "editDialog": { + "title": "Edit Channel", + "description": "Update channel settings and voice assignments." + } + }, "profileForm": { "createTitle": "Create Voice", "editTitle": "Edit Voice", diff --git a/app/src/i18n/locales/zh-CN/translation.json b/app/src/i18n/locales/zh-CN/translation.json index 02c86c02..28c97841 100644 --- a/app/src/i18n/locales/zh-CN/translation.json +++ b/app/src/i18n/locales/zh-CN/translation.json @@ -21,6 +21,46 @@ "settings": "设置", "updateBadge": "更新" }, + "audioChannels": { + "title": "音频通道", + "newChannel": "新建通道", + "loading": "加载中…", + "confirmDelete": "删除此通道?", + "noVoicesAssigned": "未分配声音", + "selectDevice": "选择设备", + "addDevice": "添加设备", + "addVoice": "添加声音", + "defaultSuffix": "默认", + "empty": { + "message": "暂无音频通道。创建您的第一个通道,将声音路由到特定设备。", + "action": "创建通道" + }, + "labels": { + "outputDevices": "输出设备", + "assignedVoices": "已分配声音" + }, + "devices": { + "title": "可用设备", + "defaultNote": "默认通道使用系统默认设备", + "toggleHint": "点击设备以将其添加到或从选定通道中移除", + "selectHint": "选择通道以分配设备", + "empty": "未找到音频设备", + "requiresTauri": "音频设备选择需要 Tauri" + }, + "fields": { + "name": "通道名称", + "namePlaceholder": "例如:虚拟线缆、广播" + }, + "createDialog": { + "title": "创建音频通道", + "description": "创建新的音频通道(总线),将声音路由到特定的输出设备。", + "action": "创建" + }, + "editDialog": { + "title": "编辑通道", + "description": "更新通道设置和声音分配。" + } + }, "profileForm": { "createTitle": "创建声音", "editTitle": "编辑声音",