diff --git a/components/ImageUploader.tsx b/components/ImageUploader.tsx index a760f29..c1144af 100644 --- a/components/ImageUploader.tsx +++ b/components/ImageUploader.tsx @@ -4,7 +4,7 @@ import ImageIcon from '@mui/icons-material/Image'; import ClearIcon from '@mui/icons-material/Clear'; import { qrCodePageStyles } from '@/config/pageTheme'; import { useSnackbar } from '@/components/GlobalSnackbar'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface ImageUploaderProps { /** 选中的文件 */ @@ -32,7 +32,7 @@ const ImageUploader = ({ dragging, onDraggingChange, }: ImageUploaderProps) => { - const { t } = useTranslation(['qrCode']); + const { t } = useLazyTranslation('qrCode'); const { showMessage } = useSnackbar(); const fileInputRef = useRef(null); diff --git a/components/QrCodePreview.tsx b/components/QrCodePreview.tsx index 109ecd8..424397e 100644 --- a/components/QrCodePreview.tsx +++ b/components/QrCodePreview.tsx @@ -2,7 +2,7 @@ import { Box, Button, Typography } from '@mui/material'; import DownloadIcon from '@mui/icons-material/Download'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { qrCodePageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface QrCodePreviewProps { /** 二维码 Data URL */ @@ -14,7 +14,7 @@ interface QrCodePreviewProps { } const QrCodePreview = ({ qrCodeDataUrl, onDownload, onCopy }: QrCodePreviewProps) => { - const { t } = useTranslation(['qrCode']); + const { t } = useLazyTranslation('qrCode'); if (!qrCodeDataUrl) { return ( diff --git a/pages/Base64Converter/__tests__/index.test.tsx b/pages/Base64Converter/__tests__/index.test.tsx index fd17319..c099382 100644 --- a/pages/Base64Converter/__tests__/index.test.tsx +++ b/pages/Base64Converter/__tests__/index.test.tsx @@ -2,6 +2,24 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import Base64ConverterPage from '../index'; +// Mock useLazyTranslation +vi.mock('@/utils/useLazyTranslation', () => ({ + useLazyTranslation: () => ({ + t: (key: string) => key, + i18n: { changeLanguage: vi.fn(), language: 'zh-CN' }, + isLoaded: true, + }), +})); + +// Mock getEntryPointType +vi.mock('@/config/features', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getEntryPointType: () => 'sidepanel', + }; +}); + // Mock 子组件 vi.mock('../TextMode', () => ({ default: () =>
TextMode
, diff --git a/pages/JsonTools/DiffNavigator.tsx b/pages/JsonTools/DiffNavigator.tsx index 2573e4e..96004c1 100644 --- a/pages/JsonTools/DiffNavigator.tsx +++ b/pages/JsonTools/DiffNavigator.tsx @@ -1,7 +1,7 @@ import { Box, IconButton, Typography } from '@mui/material'; import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; import NavigateNextIcon from '@mui/icons-material/NavigateNext'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; import { jsonDiffPageStyles } from '@/config/pageTheme'; interface DiffNavigatorProps { @@ -13,7 +13,7 @@ interface DiffNavigatorProps { } export default function DiffNavigator({ total, currentIndex, onPrev, onNext }: DiffNavigatorProps) { - const { t } = useTranslation(['jsonDiff']); + const { t } = useLazyTranslation('jsonDiff'); if (total === 0) { return ( diff --git a/pages/JsonTools/DiffResult.tsx b/pages/JsonTools/DiffResult.tsx index 1967f90..ef043e3 100644 --- a/pages/JsonTools/DiffResult.tsx +++ b/pages/JsonTools/DiffResult.tsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography, useTheme } from '@mui/material'; import type { Theme } from '@mui/material/styles'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; import { jsonDiffPageStyles, surfaceTint } from '@/config/pageTheme'; import JsonTree from './JsonTree'; import type { DiffNode, DiffResult as DiffResultType, DiffType, ViewMode } from './types'; @@ -12,7 +12,7 @@ interface DiffResultProps { } export default function DiffResult({ result, viewMode, activePath }: DiffResultProps) { - const { t } = useTranslation(['jsonDiff']); + const { t } = useLazyTranslation('jsonDiff'); if (viewMode === 'sideBySide') { return ( diff --git a/pages/JsonTools/JsonConvertSection.tsx b/pages/JsonTools/JsonConvertSection.tsx index f7a1cd5..c99beb8 100644 --- a/pages/JsonTools/JsonConvertSection.tsx +++ b/pages/JsonTools/JsonConvertSection.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { Box, Button, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; import { formatByteSize } from '@/utils/textStatistics'; import { useSnackbar } from '@/components/GlobalSnackbar'; import CopyButton from '@/components/CopyButton'; @@ -43,7 +43,7 @@ export default function JsonConvertSection({ convertFunction, convertButtonKey = 'convertButton', }: JsonConvertSectionProps) { - const { t } = useTranslation(['jsonFormat']); + const { t } = useLazyTranslation('jsonFormat'); const { showMessage } = useSnackbar(); const [input, setInput] = useState(''); diff --git a/pages/JsonTools/JsonFormatSection.tsx b/pages/JsonTools/JsonFormatSection.tsx index 6de69d6..966d797 100644 --- a/pages/JsonTools/JsonFormatSection.tsx +++ b/pages/JsonTools/JsonFormatSection.tsx @@ -9,7 +9,7 @@ import { TextField, Typography, } from '@mui/material'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; import { formatJson, validateJson, @@ -32,7 +32,7 @@ const INDENT_OPTIONS = [2, 4, 6, 8] as const; * 支持一键复制格式化后的 JSON。 */ export default function JsonFormatSection() { - const { t } = useTranslation(['jsonFormat']); + const { t } = useLazyTranslation('jsonFormat'); const { showMessage } = useSnackbar(); const [input, setInput] = useState(''); diff --git a/pages/QrCode/__tests__/index.test.tsx b/pages/QrCode/__tests__/index.test.tsx index de06001..f8b9adf 100644 --- a/pages/QrCode/__tests__/index.test.tsx +++ b/pages/QrCode/__tests__/index.test.tsx @@ -2,6 +2,24 @@ import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import QrCodePage from '../index'; +// Mock useLazyTranslation +vi.mock('@/utils/useLazyTranslation', () => ({ + useLazyTranslation: () => ({ + t: (key: string) => key, + i18n: { changeLanguage: vi.fn(), language: 'zh-CN' }, + isLoaded: true, + }), +})); + +// Mock getEntryPointType +vi.mock('@/config/features', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getEntryPointType: () => 'sidepanel', + }; +}); + // Mock 子组件 vi.mock('@/components/QrCodePreview', () => ({ default: () =>
QrCodePreview
, diff --git a/pages/StorageCleaner/AutoRefreshToggle.tsx b/pages/StorageCleaner/AutoRefreshToggle.tsx index 0b3277e..a261cc3 100644 --- a/pages/StorageCleaner/AutoRefreshToggle.tsx +++ b/pages/StorageCleaner/AutoRefreshToggle.tsx @@ -1,6 +1,6 @@ import { Box, Switch, Typography } from '@mui/material'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface AutoRefreshToggleProps { autoRefresh: boolean; @@ -8,7 +8,7 @@ interface AutoRefreshToggleProps { } export default function AutoRefreshToggle({ autoRefresh, onChange }: AutoRefreshToggleProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); return ( diff --git a/pages/StorageCleaner/CleaningResult.tsx b/pages/StorageCleaner/CleaningResult.tsx index 384ce1c..e166bf0 100644 --- a/pages/StorageCleaner/CleaningResult.tsx +++ b/pages/StorageCleaner/CleaningResult.tsx @@ -2,14 +2,14 @@ import { Alert, Box } from '@mui/material'; import type { CleaningResult } from '@/types/storage'; import { formatCleaningResult } from '@/utils/storageCleaner'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface CleaningResultProps { result: CleaningResult | null; } export default function CleaningResult({ result }: CleaningResultProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); if (!result) return null; return ( diff --git a/pages/StorageCleaner/DomainHeader.tsx b/pages/StorageCleaner/DomainHeader.tsx index b31ee83..999b2b8 100644 --- a/pages/StorageCleaner/DomainHeader.tsx +++ b/pages/StorageCleaner/DomainHeader.tsx @@ -3,7 +3,7 @@ import StorageIcon from '@mui/icons-material/Storage'; import PageHeader from '@/components/PageHeader'; import { formatSize } from '@/utils/storageCleaner'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; /** * DomainHeader 组件属性接口 @@ -29,7 +29,7 @@ interface DomainHeaderProps { * ``` */ export default function DomainHeader({ domain, totalSize }: DomainHeaderProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); return ( } diff --git a/pages/StorageCleaner/ErrorDisplay.tsx b/pages/StorageCleaner/ErrorDisplay.tsx index 8205145..6398070 100644 --- a/pages/StorageCleaner/ErrorDisplay.tsx +++ b/pages/StorageCleaner/ErrorDisplay.tsx @@ -1,14 +1,14 @@ import { Box, Container, Typography } from '@mui/material'; import WarningIcon from '@mui/icons-material/Warning'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface ErrorDisplayProps { error: string; } export default function ErrorDisplay({ error }: ErrorDisplayProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); return ( diff --git a/pages/StorageCleaner/OptionItem.tsx b/pages/StorageCleaner/OptionItem.tsx index 4b03b73..f4f8bfb 100644 --- a/pages/StorageCleaner/OptionItem.tsx +++ b/pages/StorageCleaner/OptionItem.tsx @@ -1,7 +1,7 @@ import { Box, Checkbox, Typography } from '@mui/material'; import { formatSize } from '@/utils/storageCleaner'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface OptionItemProps { labelKey: string; @@ -18,7 +18,7 @@ export default function OptionItem({ isCount = false, onChange, }: OptionItemProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); return ( diff --git a/pages/StorageCleaner/StorageCleanerConfirm.tsx b/pages/StorageCleaner/StorageCleanerConfirm.tsx index 7bd92d5..0a7c3de 100644 --- a/pages/StorageCleaner/StorageCleanerConfirm.tsx +++ b/pages/StorageCleaner/StorageCleanerConfirm.tsx @@ -10,7 +10,7 @@ import { import type { StorageCleanerOptions } from '@/types/storage'; import Button from '@/components/Button'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; export interface StorageCleanerConfirmProps { open: boolean; @@ -25,7 +25,7 @@ export function StorageCleanerConfirm({ onConfirm, options, }: StorageCleanerConfirmProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); const selectedOptions = Object.entries(options) .filter(([_, value]) => value) diff --git a/pages/StorageCleaner/StorageOptionsGrid.tsx b/pages/StorageCleaner/StorageOptionsGrid.tsx index f4d2d37..a631d61 100644 --- a/pages/StorageCleaner/StorageOptionsGrid.tsx +++ b/pages/StorageCleaner/StorageOptionsGrid.tsx @@ -2,7 +2,7 @@ import { Box, Checkbox, Divider, Grid, Typography } from '@mui/material'; import type { StorageCleanerOptions } from '@/types/storage'; import OptionItem from './OptionItem'; import { storageCleanerPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface StorageOptionsGridProps { options: StorageCleanerOptions; @@ -21,7 +21,7 @@ export default function StorageOptionsGrid({ onOptionChange, onSelectAll, }: StorageOptionsGridProps) { - const { t } = useTranslation(['storageCleaner']); + const { t } = useLazyTranslation('storageCleaner'); const optionKeys: { key: keyof StorageCleanerOptions; isCount?: boolean }[] = [ { key: 'localStorage' }, diff --git a/pages/StorageCleaner/useStorageCleaner.ts b/pages/StorageCleaner/useStorageCleaner.ts index ddd5020..89f0f20 100644 --- a/pages/StorageCleaner/useStorageCleaner.ts +++ b/pages/StorageCleaner/useStorageCleaner.ts @@ -18,7 +18,7 @@ import { isRestrictedUrl, } from '@/utils/storageCleaner'; import { MessageAction, sendMessage } from '@/utils/messages'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; const DEFAULT_OPTIONS: StorageCleanerOptions = { localStorage: true, @@ -66,7 +66,7 @@ export interface UseStorageCleanerOptions { export function useStorageCleaner({ showMessage, }: UseStorageCleanerOptions): UseStorageCleanerReturn { - const { t } = useTranslation(['storageCleaner', 'common']); + const { t } = useLazyTranslation(['storageCleaner', 'common']); const [domain, setDomain] = useState(''); const [error, setError] = useState(''); const [isInitializing, setIsInitializing] = useState(true); diff --git a/pages/Timestamp/LiveClock.tsx b/pages/Timestamp/LiveClock.tsx index 4b0ce7c..38f4aff 100644 --- a/pages/Timestamp/LiveClock.tsx +++ b/pages/Timestamp/LiveClock.tsx @@ -5,7 +5,7 @@ import CopyButton from '@/components/CopyButton'; import { useSnackbar } from '@/components/GlobalSnackbar'; import type { UnitType } from '@/config/pageTheme'; import { timestampPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface LiveClockProps { unit: UnitType; @@ -14,7 +14,7 @@ interface LiveClockProps { const LiveClock = React.memo(({ unit, onUseNow }: LiveClockProps) => { const [now, setNow] = useState(() => Date.now()); - const { t } = useTranslation(['timestamp']); + const { t } = useLazyTranslation('timestamp'); const { showMessage } = useSnackbar(); const onUseNowRef = useRef(onUseNow); diff --git a/pages/Timestamp/ResultView.tsx b/pages/Timestamp/ResultView.tsx index 3bc5594..9faa5b6 100644 --- a/pages/Timestamp/ResultView.tsx +++ b/pages/Timestamp/ResultView.tsx @@ -4,7 +4,7 @@ import dayjs from '@/utils/dayjs'; import CopyButton from '@/components/CopyButton'; import type { UnitType } from '@/config/pageTheme'; import { DATE_FORMAT, timestampPageStyles } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; interface ResultViewProps { result: string; @@ -17,7 +17,7 @@ interface ResultViewProps { const ResultView = React.memo( ({ result, mode, unit, zone, showEmptyPlaceholder = false }: ResultViewProps) => { - const { t } = useTranslation(['timestamp']); + const { t } = useLazyTranslation('timestamp'); const extraInfo = useMemo(() => { if (!result) return null; diff --git a/pages/Timestamp/useTimestampConverter.ts b/pages/Timestamp/useTimestampConverter.ts index 4b378a1..3fac4a1 100644 --- a/pages/Timestamp/useTimestampConverter.ts +++ b/pages/Timestamp/useTimestampConverter.ts @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'; import dayjs from '@/utils/dayjs'; import type { UnitType, ZoneType } from '@/config/pageTheme'; import { DATE_FORMAT } from '@/config/pageTheme'; -import { useTranslation } from 'react-i18next'; +import { useLazyTranslation } from '@/utils/useLazyTranslation'; import { useContextMenuData } from '@/utils/useContextMenuData'; export interface UseTimestampConverterReturn { @@ -34,7 +34,7 @@ function isTimestampLike(input: string): boolean { } export function useTimestampConverter(): UseTimestampConverterReturn { - const { t } = useTranslation(['timestamp']); + const { t } = useLazyTranslation('timestamp'); const [mode, setMode] = useState<'ts2dt' | 'dt2ts'>('ts2dt'); const [tsInput, setTsInput] = useState(() => String(Date.now())); const [dtInput, setDtInput] = useState(() => dayjs().format(DATE_FORMAT));