aa23a263fe
- 移除 react-i18next、i18next 及相关依赖
- 删除旧的 i18n/ 目录和 useLazyTranslation 工具
- 新增 utils/chromeI18n.ts 类型安全 wrapper(useI18n Hook + getMessage)
- 生成 public/_locales/{zh,en}/messages.json(298 个翻译 key)
- 批量更新 39+ 组件文件的导入和翻译调用
- 转换翻译键格式:namespace:key → namespace_key
- 修复 ErrorBoundary/PageErrorBoundary 从 withTranslation HOC 改为直接调用 getMessage
- 更新 vitest.setup.ts mock 加载实际翻译文本
- 修复 11 个测试文件的断言以匹配中文翻译
- TypeScript、ESLint、547 项测试全部通过
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
74 lines
1.9 KiB
TypeScript
74 lines
1.9 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { Check, Copy } from 'lucide-react';
|
|
import { copyTextToClipboard } from '@/utils/clipboard';
|
|
import { cn } from '@/lib/utils';
|
|
import { buttonVariants, type ButtonProps } from '@/components/ui/button';
|
|
import { toast } from 'sonner';
|
|
import { useI18n } from '@/utils/chromeI18n';
|
|
|
|
interface CopyButtonProps extends Omit<ButtonProps, 'children' | 'onClick'> {
|
|
text: string;
|
|
tooltip?: string;
|
|
}
|
|
|
|
export const CopyButton: React.FC<CopyButtonProps> = ({
|
|
text,
|
|
tooltip,
|
|
variant = 'ghost',
|
|
size = 'icon',
|
|
className,
|
|
...props
|
|
}) => {
|
|
const { t } = useI18n('common');
|
|
const [copied, setCopied] = useState(false);
|
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
};
|
|
}, []);
|
|
|
|
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.stopPropagation();
|
|
|
|
if (!text) {
|
|
toast.error(t('messages.copyEmpty'));
|
|
return;
|
|
}
|
|
|
|
const success = await copyTextToClipboard(text);
|
|
if (success) {
|
|
toast.success(t('messages.copySuccess'));
|
|
setCopied(true);
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
|
} else {
|
|
toast.error(t('messages.copyError'));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={handleCopy}
|
|
title={tooltip ?? t('buttons.copy')}
|
|
className={cn(
|
|
buttonVariants({ variant, size }),
|
|
copied &&
|
|
'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/10',
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{copied ? (
|
|
<Check className="h-[1.2em] w-[1.2em] animate-in fade-in zoom-in-75 duration-200" />
|
|
) : (
|
|
<Copy className="h-[1.2em] w-[1.2em]" />
|
|
)}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
export default CopyButton;
|