Add i18n translation for upload module

This commit is contained in:
yangdx
2025-03-26 20:33:56 +08:00
parent b804d74d34
commit b82e4825e9
6 changed files with 108 additions and 52 deletions

View File

@@ -21,18 +21,14 @@ export default function UploadDocumentsDialog() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [isUploading, setIsUploading] = useState(false) const [isUploading, setIsUploading] = useState(false)
const [progresses, setProgresses] = useState<Record<string, number>>({}) const [progresses, setProgresses] = useState<Record<string, number>>({})
// Track upload errors for each file
const [fileErrors, setFileErrors] = useState<Record<string, string>>({}) const [fileErrors, setFileErrors] = useState<Record<string, string>>({})
const handleDocumentsUpload = useCallback( const handleDocumentsUpload = useCallback(
async (filesToUpload: File[]) => { async (filesToUpload: File[]) => {
setIsUploading(true) setIsUploading(true)
// Reset error states before new upload
setFileErrors({}) setFileErrors({})
try { try {
// Use a single toast for the entire batch upload process
toast.promise( toast.promise(
(async () => { (async () => {
try { try {
@@ -40,14 +36,13 @@ export default function UploadDocumentsDialog() {
filesToUpload.map(async (file) => { filesToUpload.map(async (file) => {
try { try {
const result = await uploadDocument(file, (percentCompleted: number) => { const result = await uploadDocument(file, (percentCompleted: number) => {
console.debug(t('documentPanel.uploadDocuments.uploading', { name: file.name, percent: percentCompleted })) console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted }))
setProgresses((pre) => ({ setProgresses((pre) => ({
...pre, ...pre,
[file.name]: percentCompleted [file.name]: percentCompleted
})) }))
}) })
// Store error message if upload failed
if (result.status !== 'success') { if (result.status !== 'success') {
setFileErrors(prev => ({ setFileErrors(prev => ({
...prev, ...prev,
@@ -55,7 +50,6 @@ export default function UploadDocumentsDialog() {
})) }))
} }
} catch (err) { } catch (err) {
// Store error message from exception
setFileErrors(prev => ({ setFileErrors(prev => ({
...prev, ...prev,
[file.name]: errorMessage(err) [file.name]: errorMessage(err)
@@ -63,21 +57,18 @@ export default function UploadDocumentsDialog() {
} }
}) })
) )
// Keep dialog open to show final status
// User needs to close dialog manually
} catch (error) { } catch (error) {
console.error('Upload failed:', error) console.error('Upload failed:', error)
} }
})(), })(),
{ {
loading: t('documentPanel.uploadDocuments.uploading.batch'), loading: t('documentPanel.uploadDocuments.batch.uploading'),
success: t('documentPanel.uploadDocuments.success.batch'), success: t('documentPanel.uploadDocuments.batch.success'),
error: t('documentPanel.uploadDocuments.error.batch') error: t('documentPanel.uploadDocuments.batch.error')
} }
) )
} catch (err) { } catch (err) {
// Handle general upload errors toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }))
toast.error(`Upload error: ${errorMessage(err)}`)
} finally { } finally {
setIsUploading(false) setIsUploading(false)
} }
@@ -89,12 +80,10 @@ export default function UploadDocumentsDialog() {
<Dialog <Dialog
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
// Prevent closing dialog during upload
if (isUploading) { if (isUploading) {
return return
} }
if (!open) { if (!open) {
// Reset states when dialog is closed
setProgresses({}) setProgresses({})
setFileErrors({}) setFileErrors({})
} }

View File

@@ -6,6 +6,7 @@ import * as React from 'react'
import { FileText, Upload, X } from 'lucide-react' import { FileText, Upload, X } from 'lucide-react'
import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone' import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'
import { toast } from 'sonner' import { toast } from 'sonner'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useControllableState } from '@radix-ui/react-use-controllable-state' import { useControllableState } from '@radix-ui/react-use-controllable-state'
@@ -119,6 +120,7 @@ function formatBytes(
} }
function FileUploader(props: FileUploaderProps) { function FileUploader(props: FileUploaderProps) {
const { t } = useTranslation()
const { const {
value: valueProp, value: valueProp,
onValueChange, onValueChange,
@@ -143,12 +145,12 @@ function FileUploader(props: FileUploaderProps) {
const onDrop = React.useCallback( const onDrop = React.useCallback(
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => { (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) { if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
toast.error('Cannot upload more than 1 file at a time') toast.error(t('documentPanel.uploadDocuments.fileUploader.singleFileLimit'))
return return
} }
if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) { if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
toast.error(`Cannot upload more than ${maxFileCount} files`) toast.error(t('documentPanel.uploadDocuments.fileUploader.maxFilesLimit', { count: maxFileCount }))
return return
} }
@@ -164,7 +166,7 @@ function FileUploader(props: FileUploaderProps) {
if (rejectedFiles.length > 0) { if (rejectedFiles.length > 0) {
rejectedFiles.forEach(({ file }) => { rejectedFiles.forEach(({ file }) => {
toast.error(`File ${file.name} was rejected`) toast.error(t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name }))
}) })
} }
@@ -173,7 +175,7 @@ function FileUploader(props: FileUploaderProps) {
} }
}, },
[files, maxFileCount, multiple, onUpload, setFiles] [files, maxFileCount, multiple, onUpload, setFiles, t]
) )
function onRemove(index: number) { function onRemove(index: number) {
@@ -226,7 +228,7 @@ function FileUploader(props: FileUploaderProps) {
<div className="rounded-full border border-dashed p-3"> <div className="rounded-full border border-dashed p-3">
<Upload className="text-muted-foreground size-7" aria-hidden="true" /> <Upload className="text-muted-foreground size-7" aria-hidden="true" />
</div> </div>
<p className="text-muted-foreground font-medium">Drop the files here</p> <p className="text-muted-foreground font-medium">{t('documentPanel.uploadDocuments.fileUploader.dropHere')}</p>
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center justify-center gap-4 sm:px-5"> <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
@@ -235,18 +237,18 @@ function FileUploader(props: FileUploaderProps) {
</div> </div>
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
<p className="text-muted-foreground font-medium"> <p className="text-muted-foreground font-medium">
Drag and drop files here, or click to select files {t('documentPanel.uploadDocuments.fileUploader.dragAndDrop')}
</p> </p>
{description ? ( {description ? (
<p className="text-muted-foreground/70 text-sm">{description}</p> <p className="text-muted-foreground/70 text-sm">{description}</p>
) : ( ) : (
<p className="text-muted-foreground/70 text-sm"> <p className="text-muted-foreground/70 text-sm">
You can upload {t('documentPanel.uploadDocuments.fileUploader.uploadDescription', {
{maxFileCount > 1 count: maxFileCount,
? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount} isMultiple: maxFileCount === Infinity,
files (up to ${formatBytes(maxSize)} each)` maxSize: formatBytes(maxSize)
: ` a file with ${formatBytes(maxSize)}`} })}
Supported formats: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS {t('documentPanel.uploadDocuments.fileTypes')}
</p> </p>
)} )}
</div> </div>
@@ -301,6 +303,7 @@ interface FileCardProps {
} }
function FileCard({ file, progress, error, onRemove }: FileCardProps) { function FileCard({ file, progress, error, onRemove }: FileCardProps) {
const { t } = useTranslation()
return ( return (
<div className="relative flex items-center gap-2.5"> <div className="relative flex items-center gap-2.5">
<div className="flex flex-1 gap-2.5"> <div className="flex flex-1 gap-2.5">
@@ -323,7 +326,7 @@ function FileCard({ file, progress, error, onRemove }: FileCardProps) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}> <Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}>
<X className="size-4" aria-hidden="true" /> <X className="size-4" aria-hidden="true" />
<span className="sr-only">Remove file</span> <span className="sr-only">{t('documentPanel.uploadDocuments.fileUploader.removeFile')}</span>
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -48,12 +48,28 @@
"tooltip": "رفع المستندات", "tooltip": "رفع المستندات",
"title": "رفع المستندات", "title": "رفع المستندات",
"description": "اسحب وأفلت مستنداتك هنا أو انقر للتصفح.", "description": "اسحب وأفلت مستنداتك هنا أو انقر للتصفح.",
"single": {
"uploading": "جارٍ الرفع {{name}}: {{percent}}%", "uploading": "جارٍ الرفع {{name}}: {{percent}}%",
"success": "نجاح الرفع:\nتم رفع {{name}} بنجاح", "success": "نجاح الرفع:\nتم رفع {{name}} بنجاح",
"failed": "فشل الرفع:\n{{name}}\n{{message}}", "failed": "فشل الرفع:\n{{name}}\n{{message}}",
"error": "فشل الرفع:\n{{name}}\n{{error}}", "error": "فشل الرفع:\n{{name}}\n{{error}}"
},
"batch": {
"uploading": "جارٍ رفع الملفات...",
"success": "تم رفع الملفات بنجاح",
"error": "فشل رفع بعض الملفات"
},
"generalError": "فشل الرفع\n{{error}}", "generalError": "فشل الرفع\n{{error}}",
"fileTypes": "الأنواع المدعومة: TXT، MD، DOCX، PDF، PPTX، RTF، ODT، EPUB، HTML، HTM، TEX، JSON، XML، YAML، YML، CSV، LOG، CONF، INI، PROPERTIES، SQL، BAT، SH، C، CPP، PY، JAVA، JS، TS، SWIFT، GO، RB، PHP، CSS، SCSS، LESS" "fileTypes": "الأنواع المدعومة: TXT، MD، DOCX، PDF، PPTX، RTF، ODT، EPUB، HTML، HTM، TEX، JSON، XML، YAML، YML، CSV، LOG، CONF، INI، PROPERTIES، SQL، BAT، SH، C، CPP، PY، JAVA، JS، TS، SWIFT، GO، RB، PHP، CSS، SCSS، LESS",
"fileUploader": {
"singleFileLimit": "لا يمكن رفع أكثر من ملف واحد في المرة الواحدة",
"maxFilesLimit": "لا يمكن رفع أكثر من {{count}} ملفات",
"fileRejected": "تم رفض الملف {{name}}",
"dropHere": "أفلت الملفات هنا",
"dragAndDrop": "اسحب وأفلت الملفات هنا، أو انقر للاختيار",
"removeFile": "إزالة الملف",
"uploadDescription": "يمكنك رفع {{isMultiple ? 'عدة' : count}} ملفات (حتى {{maxSize}} لكل منها)"
}
}, },
"documentManager": { "documentManager": {
"title": "إدارة المستندات", "title": "إدارة المستندات",

View File

@@ -48,12 +48,28 @@
"tooltip": "Upload documents", "tooltip": "Upload documents",
"title": "Upload Documents", "title": "Upload Documents",
"description": "Drag and drop your documents here or click to browse.", "description": "Drag and drop your documents here or click to browse.",
"single": {
"uploading": "Uploading {{name}}: {{percent}}%", "uploading": "Uploading {{name}}: {{percent}}%",
"success": "Upload Success:\n{{name}} uploaded successfully", "success": "Upload Success:\n{{name}} uploaded successfully",
"failed": "Upload Failed:\n{{name}}\n{{message}}", "failed": "Upload Failed:\n{{name}}\n{{message}}",
"error": "Upload Failed:\n{{name}}\n{{error}}", "error": "Upload Failed:\n{{name}}\n{{error}}"
},
"batch": {
"uploading": "Uploading files...",
"success": "Files uploaded successfully",
"error": "Some files failed to upload"
},
"generalError": "Upload Failed\n{{error}}", "generalError": "Upload Failed\n{{error}}",
"fileTypes": "Supported types: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS" "fileTypes": "Supported types: TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
"fileUploader": {
"singleFileLimit": "Cannot upload more than 1 file at a time",
"maxFilesLimit": "Cannot upload more than {{count}} files",
"fileRejected": "File {{name}} was rejected",
"dropHere": "Drop the files here",
"dragAndDrop": "Drag and drop files here, or click to select files",
"removeFile": "Remove file",
"uploadDescription": "You can upload {{isMultiple ? 'multiple' : count}} files (up to {{maxSize}} each)"
}
}, },
"documentManager": { "documentManager": {
"title": "Document Management", "title": "Document Management",
@@ -95,15 +111,15 @@
"pipelineStatus": { "pipelineStatus": {
"title": "Pipeline Status", "title": "Pipeline Status",
"busy": "Pipeline Busy", "busy": "Pipeline Busy",
"requestPending": "Reques Pending", "requestPending": "Request Pending",
"jobName": "Job Name", "jobName": "Job Name",
"startTime": "Start Time", "startTime": "Start Time",
"progress": "Progress", "progress": "Progress",
"unit": "batch", "unit": "batch",
"latestMessage": "Latest Message", "latestMessage": "Latest Message",
"historyMessages": "History Message", "historyMessages": "History Messages",
"errors": { "errors": {
"fetchFailed": "Fail to get pipeline status\n{{error}}" "fetchFailed": "Failed to get pipeline status\n{{error}}"
} }
} }
}, },

View File

@@ -48,12 +48,28 @@
"tooltip": "Télécharger des documents", "tooltip": "Télécharger des documents",
"title": "Télécharger des documents", "title": "Télécharger des documents",
"description": "Glissez-déposez vos documents ici ou cliquez pour parcourir.", "description": "Glissez-déposez vos documents ici ou cliquez pour parcourir.",
"single": {
"uploading": "Téléchargement de {{name}} : {{percent}}%", "uploading": "Téléchargement de {{name}} : {{percent}}%",
"success": "Succès du téléchargement :\n{{name}} téléchargé avec succès", "success": "Succès du téléchargement :\n{{name}} téléchargé avec succès",
"failed": "Échec du téléchargement :\n{{name}}\n{{message}}", "failed": "Échec du téléchargement :\n{{name}}\n{{message}}",
"error": "Échec du téléchargement :\n{{name}}\n{{error}}", "error": "Échec du téléchargement :\n{{name}}\n{{error}}"
},
"batch": {
"uploading": "Téléchargement des fichiers...",
"success": "Fichiers téléchargés avec succès",
"error": "Certains fichiers n'ont pas pu être téléchargés"
},
"generalError": "Échec du téléchargement\n{{error}}", "generalError": "Échec du téléchargement\n{{error}}",
"fileTypes": "Types pris en charge : TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS" "fileTypes": "Types pris en charge : TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
"fileUploader": {
"singleFileLimit": "Impossible de télécharger plus d'un fichier à la fois",
"maxFilesLimit": "Impossible de télécharger plus de {{count}} fichiers",
"fileRejected": "Le fichier {{name}} a été rejeté",
"dropHere": "Déposez les fichiers ici",
"dragAndDrop": "Glissez et déposez les fichiers ici, ou cliquez pour sélectionner",
"removeFile": "Supprimer le fichier",
"uploadDescription": "Vous pouvez télécharger {{isMultiple ? 'plusieurs' : count}} fichiers (jusqu'à {{maxSize}} chacun)"
}
}, },
"documentManager": { "documentManager": {
"title": "Gestion des documents", "title": "Gestion des documents",

View File

@@ -48,12 +48,28 @@
"tooltip": "上传文档", "tooltip": "上传文档",
"title": "上传文档", "title": "上传文档",
"description": "拖拽文件到此处或点击浏览", "description": "拖拽文件到此处或点击浏览",
"single": {
"uploading": "正在上传 {{name}}{{percent}}%", "uploading": "正在上传 {{name}}{{percent}}%",
"success": "上传成功:\n{{name}} 上传完成", "success": "上传成功:\n{{name}} 上传完成",
"failed": "上传失败:\n{{name}}\n{{message}}", "failed": "上传失败:\n{{name}}\n{{message}}",
"error": "上传失败:\n{{name}}\n{{error}}", "error": "上传失败:\n{{name}}\n{{error}}"
},
"batch": {
"uploading": "正在上传文件...",
"success": "文件上传完成",
"error": "部分文件上传失败"
},
"generalError": "上传失败\n{{error}}", "generalError": "上传失败\n{{error}}",
"fileTypes": "支持的文件类型TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS" "fileTypes": "支持的文件类型TXT, MD, DOCX, PDF, PPTX, RTF, ODT, EPUB, HTML, HTM, TEX, JSON, XML, YAML, YML, CSV, LOG, CONF, INI, PROPERTIES, SQL, BAT, SH, C, CPP, PY, JAVA, JS, TS, SWIFT, GO, RB, PHP, CSS, SCSS, LESS",
"fileUploader": {
"singleFileLimit": "一次只能上传一个文件",
"maxFilesLimit": "最多只能上传 {{count}} 个文件",
"fileRejected": "文件 {{name}} 被拒绝",
"dropHere": "将文件拖放到此处",
"dragAndDrop": "拖放文件到此处,或点击选择文件",
"removeFile": "移除文件",
"uploadDescription": "您可以上传{{isMultiple ? '多个' : count}}个文件(每个文件最大{{maxSize}}"
}
}, },
"documentManager": { "documentManager": {
"title": "文档管理", "title": "文档管理",