feat: Support i18n
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Job } from '@/types/job'
|
||||
import {
|
||||
Dialog,
|
||||
@@ -31,14 +32,8 @@ const jobTypeBadgeVariant = {
|
||||
voice_clone: 'outline' as const,
|
||||
}
|
||||
|
||||
const jobTypeLabel = {
|
||||
custom_voice: '自定义音色',
|
||||
voice_design: '音色设计',
|
||||
voice_clone: '声音克隆',
|
||||
}
|
||||
|
||||
const formatTimestamp = (timestamp: string) => {
|
||||
return new Date(timestamp).toLocaleString('zh-CN', {
|
||||
const formatTimestamp = (timestamp: string, locale: string) => {
|
||||
return new Date(timestamp).toLocaleString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
@@ -47,18 +42,26 @@ const formatTimestamp = (timestamp: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
const getLanguageDisplay = (lang: string | undefined) => {
|
||||
if (!lang || lang === 'Auto') return '自动检测'
|
||||
return lang
|
||||
}
|
||||
|
||||
const formatBooleanDisplay = (value: boolean | undefined) => {
|
||||
return value ? '是' : '否'
|
||||
}
|
||||
|
||||
const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps) => {
|
||||
const { t, i18n } = useTranslation(['job', 'common'])
|
||||
|
||||
if (!job) return null
|
||||
|
||||
const jobTypeLabel = {
|
||||
custom_voice: t('job:typeCustomVoice'),
|
||||
voice_design: t('job:typeVoiceDesign'),
|
||||
voice_clone: t('job:typeVoiceClone'),
|
||||
}
|
||||
|
||||
const getLanguageDisplay = (lang: string | undefined) => {
|
||||
if (!lang || lang === 'Auto') return t('job:autoDetect')
|
||||
return lang
|
||||
}
|
||||
|
||||
const formatBooleanDisplay = (value: boolean | undefined) => {
|
||||
return value ? t('common:yes') : t('common:no')
|
||||
}
|
||||
|
||||
const canPlay = job.status === 'completed'
|
||||
const audioUrl = canPlay ? jobApi.getAudioUrl(job.id, job.audio_url) : ''
|
||||
|
||||
@@ -74,35 +77,35 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<span className="text-sm text-muted-foreground">#{job.id}</span>
|
||||
</DialogTitle>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatTimestamp(job.created_at)}
|
||||
{formatTimestamp(job.created_at, i18n.language)}
|
||||
</span>
|
||||
</div>
|
||||
<DialogDescription>查看任务的详细参数和生成结果</DialogDescription>
|
||||
<DialogDescription>{t('job:detailsDescription')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="max-h-[calc(90vh-120px)] pr-4">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">基本信息</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:basicInfo')}</h3>
|
||||
<div className="space-y-1.5 text-sm bg-muted/30 p-3 rounded-lg">
|
||||
{job.type === 'custom_voice' && job.parameters?.speaker && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">发音人: </span>
|
||||
<span className="text-muted-foreground">{t('job:speaker')}</span>
|
||||
<span>{job.parameters.speaker}</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-muted-foreground">语言: </span>
|
||||
<span className="text-muted-foreground">{t('job:language')}</span>
|
||||
<span>{getLanguageDisplay(job.parameters?.language)}</span>
|
||||
</div>
|
||||
{job.type === 'voice_clone' && (
|
||||
<>
|
||||
<div>
|
||||
<span className="text-muted-foreground">快速模式: </span>
|
||||
<span className="text-muted-foreground">{t('job:fastMode')}</span>
|
||||
<span>{formatBooleanDisplay(job.parameters?.x_vector_only_mode)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">使用缓存: </span>
|
||||
<span className="text-muted-foreground">{t('job:useCache')}</span>
|
||||
<span>{formatBooleanDisplay(job.parameters?.use_cache)}</span>
|
||||
</div>
|
||||
</>
|
||||
@@ -113,9 +116,9 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">合成文本</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:synthesisText')}</h3>
|
||||
<div className="text-sm bg-muted/30 p-3 rounded-lg border">
|
||||
{job.parameters?.text || <span className="text-muted-foreground">未设置</span>}
|
||||
{job.parameters?.text || <span className="text-muted-foreground">{t('job:notSet')}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -123,7 +126,7 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">音色描述</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:voiceDescription')}</h3>
|
||||
<div className="text-sm bg-blue-50 dark:bg-blue-950/30 p-3 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
{job.parameters.instruct}
|
||||
</div>
|
||||
@@ -135,7 +138,7 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">情绪指导</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:emotionGuidance')}</h3>
|
||||
<div className="text-sm bg-muted/30 p-3 rounded-lg border">
|
||||
{job.parameters.instruct}
|
||||
</div>
|
||||
@@ -147,9 +150,9 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">参考文本</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:referenceText')}</h3>
|
||||
<div className="text-sm bg-muted/30 p-3 rounded-lg border">
|
||||
{job.parameters?.ref_text || <span className="text-muted-foreground">未提供</span>}
|
||||
{job.parameters?.ref_text || <span className="text-muted-foreground">{t('job:notProvided')}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -159,38 +162,38 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="flex items-center gap-2 text-sm font-semibold hover:text-foreground transition-colors w-full">
|
||||
高级参数
|
||||
{t('job:advancedParameters')}
|
||||
<ChevronDown className="w-4 h-4 transition-transform ui-expanded:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pt-3">
|
||||
<div className="space-y-1.5 text-sm bg-muted/30 p-3 rounded-lg border">
|
||||
{job.parameters?.max_new_tokens !== undefined && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">最大生成长度: </span>
|
||||
<span className="text-muted-foreground">{t('job:maxNewTokens')}</span>
|
||||
<span>{job.parameters.max_new_tokens}</span>
|
||||
</div>
|
||||
)}
|
||||
{job.parameters?.temperature !== undefined && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">温度: </span>
|
||||
<span className="text-muted-foreground">{t('job:temperature')}</span>
|
||||
<span>{job.parameters.temperature}</span>
|
||||
</div>
|
||||
)}
|
||||
{job.parameters?.top_k !== undefined && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">Top K: </span>
|
||||
<span className="text-muted-foreground">{t('job:topK')}</span>
|
||||
<span>{job.parameters.top_k}</span>
|
||||
</div>
|
||||
)}
|
||||
{job.parameters?.top_p !== undefined && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">Top P: </span>
|
||||
<span className="text-muted-foreground">{t('job:topP')}</span>
|
||||
<span>{job.parameters.top_p}</span>
|
||||
</div>
|
||||
)}
|
||||
{job.parameters?.repetition_penalty !== undefined && (
|
||||
<div>
|
||||
<span className="text-muted-foreground">重复惩罚: </span>
|
||||
<span className="text-muted-foreground">{t('job:repetitionPenalty')}</span>
|
||||
<span>{job.parameters.repetition_penalty}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -204,7 +207,7 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<div className="flex items-start gap-2 p-3 bg-red-50 dark:bg-red-950/30 rounded-lg border border-red-200 dark:border-red-800">
|
||||
<AlertCircle className="w-4 h-4 text-destructive mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm text-destructive mb-1">错误信息</h3>
|
||||
<h3 className="font-semibold text-sm text-destructive mb-1">{t('job:errorMessage')}</h3>
|
||||
<p className="text-sm text-destructive">{job.error_message}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,7 +218,7 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-sm">音频播放</h3>
|
||||
<h3 className="font-semibold text-sm">{t('job:audioPlayback')}</h3>
|
||||
<AudioPlayer audioUrl={audioUrl} jobId={job.id} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user