feat(i18n): add internationalization support across multiple components

This commit is contained in:
2025-10-29 16:40:20 +08:00
parent 184c232cc8
commit 9199ed7c29
34 changed files with 1455 additions and 761 deletions

View File

@@ -36,6 +36,7 @@ import {
SelectAll as SelectAllIcon,
Clear as ClearIcon,
} from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import type { IChunk } from '@/interfaces/database/knowledge';
import knowledgeService from '@/services/knowledge_service';
@@ -54,6 +55,7 @@ interface ChunkListResultProps {
function ChunkListResult(props: ChunkListResultProps) {
const { doc_id, chunks, total, loading, error, page, pageSize, onPageChange, onRefresh, docName } = props;
const { t } = useTranslation();
// 选择状态
const [selectedChunks, setSelectedChunks] = useState<string[]>([]);
@@ -140,7 +142,7 @@ function ChunkListResult(props: ChunkListResultProps) {
<Paper sx={{ p: 3, textAlign: 'center' }}>
<CircularProgress />
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
chunk数据...
{t('chunkPage.loadingChunkData')}
</Typography>
</Paper>
);
@@ -160,10 +162,10 @@ function ChunkListResult(props: ChunkListResultProps) {
return (
<Paper sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="h6" color="text.secondary">
chunk数据
{t('chunkPage.noChunkData')}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
chunk数据
{t('chunkPage.noChunkDataDescription')}
</Typography>
</Paper>
);
@@ -189,7 +191,7 @@ function ChunkListResult(props: ChunkListResultProps) {
onChange={(e) => handleSelectAll(e.target.checked)}
/>
}
label={`全选 (已选择 ${selectedChunks.length} )`}
label={`${t('chunkPage.selectAll')} (${t('chunkPage.selected')} ${selectedChunks.length} ${t('chunkPage.items')})`}
/>
<Box sx={{ flexGrow: 1 }} />
@@ -205,7 +207,7 @@ function ChunkListResult(props: ChunkListResultProps) {
variant="outlined"
size="small"
>
({selectedDisabledCount})
{t('chunkPage.enable')} ({selectedDisabledCount})
</Button>
)}
@@ -218,7 +220,7 @@ function ChunkListResult(props: ChunkListResultProps) {
variant="outlined"
size="small"
>
({selectedEnabledCount})
{t('chunkPage.disable')} ({selectedEnabledCount})
</Button>
)}
@@ -230,7 +232,7 @@ function ChunkListResult(props: ChunkListResultProps) {
variant="outlined"
size="small"
>
({selectedChunks.length})
{t('common.delete')} ({selectedChunks.length})
</Button>
<Button
@@ -239,7 +241,7 @@ function ChunkListResult(props: ChunkListResultProps) {
variant="outlined"
size="small"
>
{t('chunkPage.clearSelection')}
</Button>
</Stack>
)}
@@ -250,10 +252,10 @@ function ChunkListResult(props: ChunkListResultProps) {
<Paper sx={{ p: 3, mb: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h6">
Chunk列表 ( {page} {totalPages} )
{t('chunkPage.chunkList')} ({t('chunkPage.page')} {page} {t('chunkPage.pageOf')} {totalPages} {t('chunkPage.pages')})
</Typography>
<Typography variant="body2" color="text.secondary">
{total} chunk
{t('chunkPage.total')} {total} {t('chunkPage.chunks')}
</Typography>
</Box>
@@ -307,7 +309,7 @@ function ChunkListResult(props: ChunkListResultProps) {
<Stack direction="row" spacing={1} alignItems="center">
{chunk.image_id && (
<Tooltip title="包含图片">
<Tooltip title={t('chunkPage.containsImage')}>
<Avatar sx={{ bgcolor: 'info.main', width: 24, height: 24 }}>
<ImageIcon fontSize="small" />
</Avatar>
@@ -315,7 +317,7 @@ function ChunkListResult(props: ChunkListResultProps) {
)}
<Chip
icon={chunk.available_int === 1 ? <VisibilityIcon /> : <VisibilityOffIcon />}
label={chunk.available_int === 1 ? '已启用' : '未启用'}
label={chunk.available_int === 1 ? t('chunkPage.enabled') : t('chunkPage.disabled')}
size="small"
color={chunk.available_int === 1 ? 'success' : 'default'}
variant={chunk.available_int === 1 ? 'filled' : 'outlined'}
@@ -326,7 +328,7 @@ function ChunkListResult(props: ChunkListResultProps) {
{/* 内容区域 */}
<Box sx={{ mb: 2 }}>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
{t('chunkPage.contentPreview')}
</Typography>
<Paper
variant="outlined"
@@ -347,7 +349,7 @@ function ChunkListResult(props: ChunkListResultProps) {
color: 'text.primary',
}}
>
{chunk.content_with_weight || '无内容'}
{chunk.content_with_weight || t('chunkPage.noContent')}
</Typography>
</Paper>
</Box>
@@ -356,7 +358,7 @@ function ChunkListResult(props: ChunkListResultProps) {
{chunk.image_id && (
<Box sx={{ mb: 2 }}>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
{t('chunkPage.relatedImage')}
</Typography>
<Box
sx={{
@@ -370,7 +372,7 @@ function ChunkListResult(props: ChunkListResultProps) {
<Box
component="img"
src={`${import.meta.env.VITE_API_BASE_URL}/v1/document/image/${chunk.image_id}`}
alt="Chunk相关图片"
alt={t('chunkPage.chunkRelatedImage')}
sx={{
maxWidth: '100%',
maxHeight: '200px',
@@ -390,14 +392,14 @@ function ChunkListResult(props: ChunkListResultProps) {
{((chunk.important_kwd ?? []).length > 0 || (chunk.question_kwd ?? []).length > 0 || (chunk.tag_kwd ?? []).length > 0) && (
<Box>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
{t('chunkPage.keywordInfo')}
</Typography>
<Stack spacing={1}>
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
<Box>
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
:
{t('chunkPage.important')}:
</Typography>
{chunk.important_kwd.map((keyword, kwdIndex) => (
<Chip
@@ -415,7 +417,7 @@ function ChunkListResult(props: ChunkListResultProps) {
{chunk.question_kwd && chunk.question_kwd.length > 0 && (
<Box>
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
:
{t('chunkPage.question')}:
</Typography>
{chunk.question_kwd.map((keyword, kwdIndex) => (
<Chip
@@ -433,7 +435,7 @@ function ChunkListResult(props: ChunkListResultProps) {
{chunk.tag_kwd && chunk.tag_kwd.length > 0 && (
<Box>
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
:
{t('chunkPage.tag')}:
</Typography>
{chunk.tag_kwd.map((keyword, kwdIndex) => (
<Chip
@@ -476,22 +478,22 @@ function ChunkListResult(props: ChunkListResultProps) {
open={deleteDialogOpen}
onClose={() => setDeleteDialogOpen(false)}
>
<DialogTitle></DialogTitle>
<DialogTitle>{t('dialog.confirmDelete')}</DialogTitle>
<DialogContent>
<DialogContentText>
{selectedChunks.length} chunk吗
{t('chunkPage.confirmDeleteChunks', { count: selectedChunks.length })}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteDialogOpen(false)}>
{t('common.cancel')}
</Button>
<Button
onClick={handleDeleteChunks}
color="error"
disabled={operationLoading}
>
{operationLoading ? '删除中...' : '确认删除'}
{operationLoading ? t('chunkPage.deleting') : t('dialog.confirmDelete')}
</Button>
</DialogActions>
</Dialog>

View File

@@ -16,6 +16,7 @@ import {
Download as DownloadIcon,
Visibility as VisibilityIcon,
} from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import knowledgeService from '@/services/knowledge_service';
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs';
@@ -25,6 +26,7 @@ interface DocumentPreviewProps {}
function DocumentPreview(props: DocumentPreviewProps) {
const { kb_id, doc_id } = useParams<{ kb_id: string; doc_id: string }>();
const navigate = useNavigate();
const { t } = useTranslation();
const [kb, setKb] = useState<IKnowledge | null>(null);
const [documentObj, setDocumentObj] = useState<IKnowledgeFile | null>(null);
@@ -41,7 +43,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
useEffect(() => {
const fetchData = async () => {
if (!kb_id || !doc_id) {
setError('缺少必要的参数');
setError(t('chunkPage.missingParams'));
setLoading(false);
return;
}
@@ -67,7 +69,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
loadDocumentFile();
} catch (err) {
console.error('获取数据失败:', err);
setError('获取数据失败,请稍后重试');
setError(t('chunkPage.fetchDataFailed'));
} finally {
setLoading(false);
}
@@ -103,13 +105,13 @@ function DocumentPreview(props: DocumentPreviewProps) {
const url = URL.createObjectURL(fileResponse.data);
setFileUrl(url);
} else {
setError('文件格式不支持预览');
setError(t('chunkPage.fileFormatNotSupported'));
}
} catch (err: any) {
console.log('err', err);
if (err.name !== 'AbortError' && err.name !== 'CanceledError') {
console.error('获取文档文件失败:', err);
setError('获取文档文件失败,请稍后重试');
setError(t('chunkPage.getDocumentFileFailed'));
}
} finally {
setFileLoading(false);
@@ -168,7 +170,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
<CardMedia
component="img"
image={fileUrl}
alt={documentObj?.name || '文档预览'}
alt={documentObj?.name || t('chunkPage.documentPreview')}
sx={{
maxWidth: '100%',
maxHeight: '80vh',
@@ -207,7 +209,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
} else {
return (
<Alert severity="info" sx={{ mt: 2 }}>
线
{t('chunkPage.fileTypeNotSupportedPreview')}
</Alert>
);
}
@@ -230,7 +232,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
onClick={handleGoBack}
sx={{ mt: 2 }}
>
{t('common.back')}
</Button>
</Box>
);
@@ -242,21 +244,21 @@ function DocumentPreview(props: DocumentPreviewProps) {
<KnowledgeBreadcrumbs
kbItems={[
{
label: '知识库',
label: t('chunkPage.knowledgeBase'),
path: '/knowledge'
},
{
label: kb?.name || '知识库详情',
label: kb?.name || t('chunkPage.knowledgeBaseDetail'),
path: `/knowledge/${kb_id}`
}
]}
extraItems={[
{
label: documentObj?.name || '文档详情',
label: documentObj?.name || t('chunkPage.documentDetail'),
path: `/chunk/parsed-result?kb_id=${kb_id}&doc_id=${doc_id}`
},
{
label: '文件预览'
label: t('chunkPage.filePreview')
}
]}
/>
@@ -265,7 +267,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Box>
<Typography variant="h4" gutterBottom>
{t('chunkPage.filePreview')}
</Typography>
{documentObj && (
<Typography variant="subtitle1" color="text.secondary">
@@ -280,7 +282,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
onClick={handleGoBack}
variant="outlined"
>
{t('common.back')}
</Button>
{fileUrl && (
@@ -289,7 +291,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
onClick={handleDownload}
variant="contained"
>
{t('chunkPage.downloadFile')}
</Button>
)}
</Box>
@@ -306,7 +308,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
{fileLoading && (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
<CircularProgress />
<Typography sx={{ ml: 2 }}>...</Typography>
<Typography sx={{ ml: 2 }}>{t('chunkPage.loadingFile')}</Typography>
</Box>
)}

View File

@@ -12,6 +12,7 @@ import {
CardContent
} from "@mui/material";
import { Search as SearchIcon, Visibility as VisibilityIcon } from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import { useChunkList } from '@/hooks/chunk-hooks';
import ChunkListResult from './components/ChunkListResult';
import knowledgeService from '@/services/knowledge_service';
@@ -19,6 +20,7 @@ import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge
import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs';
function ChunkParsedResult() {
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const kb_id = searchParams.get('kb_id');
@@ -93,7 +95,7 @@ function ChunkParsedResult() {
return (
<Box sx={{ p: 3 }}>
<Alert severity="error">
ID或文档ID
{t('chunkPage.missingParams')}
</Alert>
</Box>
);
@@ -105,17 +107,17 @@ function ChunkParsedResult() {
<KnowledgeBreadcrumbs
kbItems={[
{
label: '知识库',
label: t('chunkPage.knowledgeBase'),
path: '/knowledge'
},
{
label: knowledgeBase?.name || '知识库详情',
label: knowledgeBase?.name || t('chunkPage.knowledgeBaseDetail'),
path: `/knowledge/${kb_id}`
}
]}
extraItems={[
{
label: document?.name || '文档详情'
label: document?.name || t('chunkPage.documentDetail')
},
]}
/>
@@ -124,10 +126,10 @@ function ChunkParsedResult() {
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box>
<Typography variant="h4" gutterBottom>
Chunk解析结果
{t('chunkPage.documentChunkResult')}
</Typography>
<Typography variant="body1" color="text.secondary">
"{document?.name}" chunk数据
{t('chunkPage.viewDocument')} "{document?.name}" {t('chunkPage.allChunkData')}
</Typography>
</Box>
<Card>
@@ -136,7 +138,7 @@ function ChunkParsedResult() {
{total}
</Typography>
<Typography variant="body2" color="text.secondary">
Chunk数量
{t('chunkPage.totalChunkCount')}
</Typography>
</CardContent>
</Card>
@@ -146,7 +148,7 @@ function ChunkParsedResult() {
onClick={handleViewFile}
sx={{ ml: 2 }}
>
{t('chunkPage.viewFile')}
</Button>
</Box>
</Paper>
@@ -155,7 +157,7 @@ function ChunkParsedResult() {
<Paper sx={{ p: 3, mb: 3 }}>
<TextField
fullWidth
placeholder="搜索chunk内容..."
placeholder={t('chunkPage.searchChunkPlaceholder')}
value={searchKeyword}
onChange={(e) => handleSearch(e.target.value)}
InputProps={{