refactor: 统一中文文案并简化组件结构,提升代码可读性

移除 chrome.i18n 后,将各功能页面、布局与工具模块的 UI 文案改为内联中文;
删除冗余注释与过度抽象(如 ToolCard、StatCard),精简 props 与状态管理;
同步优化 JsonTools、StorageCleaner、Timestamp、TestDataGenerator、Dashboard、
Base64Converter、RightClickRestorer、TextStatistics、TopBar、RouterProvider、
ThemeModeProvider 及生成器库与 utils 工具文件。
This commit is contained in:
雨霖铃
2026-06-27 10:49:36 +08:00
parent f3a9838017
commit d38bafe237
85 changed files with 324 additions and 864 deletions
@@ -40,12 +40,12 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
decodedFileName,
setCustomFileName,
resetAll,
safeFileSelect,
handleFileSelect,
maxFileSizeStr,
} = useBase64Converter({ mode });
const handleDirectionChange = (next: Base64ConvertDirection) => {
if (!next || next === direction) return;
if (next === direction) return;
resetAll();
setDirection(next);
};
@@ -80,7 +80,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file) safeFileSelect(file);
if (file) void handleFileSelect(file);
}}
onClick={() => fileInputRef.current?.click()}
className={cn(
@@ -99,7 +99,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
hidden
onChange={(e) => {
const file = e.target.files?.[0];
if (file) safeFileSelect(file);
if (file) void handleFileSelect(file);
}}
/>
{isLoading ? (
@@ -123,7 +123,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
{formatBytes(info.size)} · {info.type}
</span>
<span className="text-[11px] font-medium text-primary/80 mt-1">
{'点击或拖拽以替换文件'}
</span>
</div>
) : (
@@ -158,17 +158,17 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
<div className="p-4 rounded-2xl bg-card border border-border shadow-sm flex flex-col space-y-3">
<div className="flex justify-between items-center select-none">
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/90">
{'Base64 编码结果'}
Base64
</span>
<div className="flex gap-2">
<CopyButton
text={result.rawBase64}
tooltip={'复制纯 Base64'}
tooltip="复制纯 Base64"
className="h-6 px-2 rounded-md border text-[10px] font-bold"
/>
<CopyButton
text={result.output}
tooltip={'复制 Data URI'}
tooltip="复制 Data URI"
className="h-6 px-2 rounded-md border text-[10px] font-bold"
/>
</div>
@@ -186,14 +186,14 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
<div className="flex items-center justify-between font-mono text-[10px] text-muted-foreground/70 select-none pt-1">
<div className="flex gap-4 items-center tabular-nums">
<span>
{'原始大小'}:{' '}
:{' '}
<span className="font-semibold text-foreground/80">
{formatBytes(result.originalBytes)}
</span>
</span>
<span className="text-border/60">|</span>
<span>
{'编码大小'}:{' '}
:{' '}
<span className="font-semibold text-foreground/80">
{formatBytes(result.outputBytes)}
</span>
@@ -206,7 +206,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
className="h-7 rounded-md text-muted-foreground hover:text-destructive text-[11px] gap-1 px-2"
>
<Trash2 className="w-3.5 h-3.5" />
{'清空'}
</Button>
</div>
</div>
@@ -215,7 +215,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
) : (
<div className="flex flex-col space-y-4">
<TextInputArea
placeholder={'输入需要解码的 Base64 或 data URI...'}
placeholder="输入需要解码的 Base64 或 data URI..."
value={decodeInput}
onChange={setDecodeInput}
externalError={decodeError || undefined}
@@ -1,12 +1,3 @@
/**
* DecodeResultPaper
*
* FileMode 与 ImageMode 通用的 decode 结果展示组件。
* 提取了二者 decode 输出区完全一致的结构:
* 标题 → 可选预览(children)→ 文件信息 → 文件名输入 → 下载按钮
*
* FileMode 直接使用,ImageMode 通过 children 传入图片预览。
*/
import { Download } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -14,19 +5,12 @@ import { Label } from '@/components/ui/label';
import { formatBytes } from '@/utils/format';
interface DecodeResultPaperProps {
/** 标题文案 */
title: string;
/** 解码后推断的 MIME 类型 */
mimeType: string;
/** 解码后 Blob 的大小(字节) */
blobSize: number;
/** 当前文件名 */
fileName: string;
/** 文件名变更回调 */
onFileNameChange: (name: string) => void;
/** 下载按钮点击回调 */
onDownload: () => void;
/** 可选的预览内容,ImageMode 用于渲染图片预览 */
children?: React.ReactNode;
}
@@ -41,31 +25,20 @@ export default function DecodeResultPaper({
}: DecodeResultPaperProps) {
return (
<div className="p-4 rounded-xl bg-primary/10 border border-primary/30">
{/* 标题 */}
<span className="block mb-2 text-xs font-bold text-muted-foreground">{title}</span>
{/* 可选预览内容(ImageMode 的图片) */}
{children}
{/* 文件信息 */}
<div className="flex gap-4 mb-3">
<span className="text-xs text-muted-foreground">
{'推断的 MIME 类型'}: {mimeType}
</span>
<span className="text-xs text-muted-foreground">
{'解码大小'}: {formatBytes(blobSize)}
</span>
<span className="text-xs text-muted-foreground"> MIME : {mimeType}</span>
<span className="text-xs text-muted-foreground">: {formatBytes(blobSize)}</span>
</div>
{/* 文件名输入 */}
<div className="mb-3">
<Label className="block text-xs font-medium text-muted-foreground mb-1">
{'解码后文件名'}
</Label>
<Label className="block text-xs font-medium text-muted-foreground mb-1"></Label>
<Input value={fileName} onChange={(e) => onFileNameChange(e.target.value)} />
</div>
{/* 下载按钮 */}
<Button
variant="default"
onClick={onDownload}
@@ -73,7 +46,7 @@ export default function DecodeResultPaper({
className="w-full rounded-lg font-bold"
>
<Download className="mr-2 h-4 w-4" />
{'下载'}
</Button>
</div>
);
@@ -8,7 +8,7 @@ interface TextModeProps {
onSwitchToImageMode?: () => void;
}
export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
export default function TextMode({ onSwitchToImageMode }: TextModeProps) {
const {
input,
setInput,
@@ -93,16 +93,7 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
[mode],
);
const safeFileSelect = useCallback(
(file: File) => {
handleFileSelect(file).catch((err) => {
console.error(`Base64 [${mode}] pipeline crash:`, err);
});
},
[handleFileSelect, mode],
);
const decodePipeline = useMemo(() => {
const decodeResult = useMemo(() => {
const cleanedInput = debouncedDecodeInput.replace(/^data:image\/[a-z+]+;base64,/i, '').trim();
if (!cleanedInput) return { decoded: null, error: null };
@@ -118,8 +109,8 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
}
}, [debouncedDecodeInput]);
const decoded = decodePipeline.decoded;
const decodeError = decodePipeline.error;
const decoded = decodeResult.decoded;
const decodeError = decodeResult.error;
const decodedFileName = useMemo(() => {
if (customFileName) return customFileName;
@@ -144,7 +135,7 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
decodedFileName,
setCustomFileName,
resetAll,
safeFileSelect,
handleFileSelect,
maxFileSizeStr,
};
}
+8 -13
View File
@@ -4,7 +4,7 @@ import { useContextMenuData } from '@/utils/useContextMenuData';
const IMAGE_DATA_URI_PATTERN = /^\s*data:image\//i;
const ERROR_MESSAGE_TO_I18N: Record<string, string> = {
const ERROR_MESSAGES: Record<string, string> = {
'Invalid Base64 string': '无效的 Base64 字符串',
'Input appears to be binary data (e.g. an image). Please use the Image tab instead.':
'检测到二进制数据(如图像),请使用图像选项卡',
@@ -30,7 +30,7 @@ export function useTextMode() {
useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData });
const conversionPipeline = useMemo(() => {
const conversion = useMemo(() => {
const trimmed = debouncedInput.trim();
if (!trimmed) return { output: '', error: null };
@@ -38,31 +38,26 @@ export function useTextMode() {
if (direction === 'encode') {
const result = textToBase64(debouncedInput);
return { output: result.output, error: null };
} else {
const decoded = base64ToText(trimmed);
return { output: decoded, error: null };
}
const decoded = base64ToText(trimmed);
return { output: decoded, error: null };
} catch (e) {
const message = e instanceof Error ? e.message : '';
const i18nKey = ERROR_MESSAGE_TO_I18N[message];
return {
output: '',
error: i18nKey || message || '转换失败',
error: ERROR_MESSAGES[message] || message || '转换失败',
};
}
}, [debouncedInput, direction]);
const output = conversionPipeline.output;
const error = conversionPipeline.error;
const output = conversion.output;
const error = conversion.error;
const placeholder =
direction === 'encode' ? '输入需要编码为 Base64 的文本...' : '输入需要解码的 Base64 字符串...';
const outputLabel = direction === 'encode' ? 'Base64 编码结果' : '解码文本结果';
const showImageHint = useMemo(
() => direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input),
[direction, input],
);
const showImageHint = direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input);
const handleDirectionChange = (value: 'encode' | 'decode') => {
if (value === direction) return;