refactor(i18n): 移除 chrome.i18n 国际化,统一使用中文硬编码
移除 chromeI18n 工具、_locales 翻译文件和 manifest default_locale 配置, 将所有 UI 文案改为直接硬编码中文,并更新相关测试与文档。
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -43,8 +42,6 @@ export default function AutoRefreshToggle({
|
||||
className,
|
||||
...props
|
||||
}: AutoRefreshToggleProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -57,7 +54,7 @@ export default function AutoRefreshToggle({
|
||||
htmlFor="auto-refresh-switch"
|
||||
className="text-xs font-bold text-muted-foreground/90 cursor-pointer select-none tracking-wide uppercase"
|
||||
>
|
||||
{t('storageCleaner:autoRefresh')}
|
||||
{'清理后自动刷新页面'}
|
||||
</Label>
|
||||
|
||||
<Switch id="auto-refresh-switch" checked={reloadAfterClean} onCheckedChange={onChange} />
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { CheckCircle, XCircle } from 'lucide-react';
|
||||
import type { CleaningResult as CleaningResultType } from '@/types/storage';
|
||||
import { formatCleaningResult } from '@/utils/storageCleaner';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface CleaningResultProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -10,8 +9,6 @@ interface CleaningResultProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
export default function CleaningResult({ result, className, ...props }: CleaningResultProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const isSuccess = result.overallSuccess;
|
||||
@@ -33,9 +30,7 @@ export default function CleaningResult({ result, className, ...props }: Cleaning
|
||||
)}
|
||||
|
||||
<span className="text-xs sm:text-sm font-semibold leading-relaxed break-all">
|
||||
{isSuccess
|
||||
? formatCleaningResult(result, t)
|
||||
: result.error || t('storageCleaner:partialFailure')}
|
||||
{isSuccess ? formatCleaningResult(result) : result.error || '部分清理失败'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface ErrorDisplayProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -7,8 +6,6 @@ interface ErrorDisplayProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
export default function ErrorDisplay({ error, className, ...props }: ErrorDisplayProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -27,7 +24,7 @@ export default function ErrorDisplay({ error, className, ...props }: ErrorDispla
|
||||
</p>
|
||||
|
||||
<p className="text-xs font-medium leading-relaxed text-muted-foreground/90 px-2">
|
||||
{t('storageCleaner:errorStandardOnly')}
|
||||
{'存储清理功能仅适用于标准网页'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import React from 'react';
|
||||
import { formatBytes } from '@/utils/format';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { StorageSizeInfo } from '../useStorageCleaner';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
const OPTION_LABELS: Record<string, string> = {
|
||||
localStorage: 'Local Storage',
|
||||
sessionStorage: 'Session Storage',
|
||||
indexedDB: '站点存储',
|
||||
cookies: 'Cookies',
|
||||
cacheStorage: 'Cache Storage',
|
||||
serviceWorkers: 'Service Workers',
|
||||
};
|
||||
|
||||
interface OptionItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
labelKey: string;
|
||||
checked: boolean;
|
||||
@@ -20,10 +28,9 @@ export default function OptionItem({
|
||||
className,
|
||||
...props
|
||||
}: OptionItemProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
const sizeValue = sizeInfo?.value;
|
||||
const isCount = sizeInfo?.displayType === 'count';
|
||||
const label = OPTION_LABELS[labelKey] || labelKey;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -44,7 +51,7 @@ export default function OptionItem({
|
||||
checked ? 'text-foreground' : 'text-foreground/75',
|
||||
)}
|
||||
>
|
||||
{t(labelKey)}
|
||||
{label}
|
||||
</span>
|
||||
|
||||
{sizeValue !== undefined && sizeValue > 0 ? (
|
||||
@@ -54,11 +61,11 @@ export default function OptionItem({
|
||||
checked ? 'text-primary/70' : 'text-muted-foreground/70',
|
||||
)}
|
||||
>
|
||||
{isCount ? `${sizeValue} ${t('storageCleaner:countUnit')}` : formatBytes(sizeValue)}
|
||||
{isCount ? `${sizeValue} 项` : formatBytes(sizeValue)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="block text-[10px] font-medium text-muted-foreground/50 mt-0.5 italic">
|
||||
{t('storageCleaner:noData')}
|
||||
{'无数据'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import type { StorageCleanerOptions } from '@/types/storage';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface StorageCleanerConfirmProps {
|
||||
@@ -20,17 +19,24 @@ export interface StorageCleanerConfirmProps {
|
||||
options: StorageCleanerOptions;
|
||||
}
|
||||
|
||||
const OPTION_LABELS: Record<string, string> = {
|
||||
localStorage: 'Local Storage',
|
||||
sessionStorage: 'Session Storage',
|
||||
indexedDB: '站点存储',
|
||||
cookies: 'Cookies',
|
||||
cacheStorage: 'Cache Storage',
|
||||
serviceWorkers: 'Service Workers',
|
||||
};
|
||||
|
||||
export function StorageCleanerConfirm({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
options,
|
||||
}: StorageCleanerConfirmProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
const selectedOptions = Object.entries(options)
|
||||
.filter(([_, value]) => value)
|
||||
.map(([key, _]) => t(`storageCleaner:options.${key as keyof StorageCleanerOptions}`));
|
||||
.map(([key, _]) => OPTION_LABELS[key] || key);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||
@@ -42,14 +48,14 @@ export function StorageCleanerConfirm({
|
||||
{/* 头部标题区域 */}
|
||||
<DialogHeader className="pt-1">
|
||||
<DialogTitle className="text-center text-lg font-bold tracking-tight text-foreground">
|
||||
{t('storageCleaner:confirmTitle')}
|
||||
{'确认清理数据?'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 内容主体:限制最大宽度,防止内部元素在大分辨率下被横向拉得太松散 */}
|
||||
<div className="text-center py-4 flex flex-col items-center w-full max-w-[280px] mx-auto">
|
||||
<DialogDescription className="mb-4 text-xs font-medium text-muted-foreground/90 leading-relaxed">
|
||||
{t('storageCleaner:confirmDesc')}
|
||||
{'您将永久删除当前页面的以下选定存储项。'}
|
||||
</DialogDescription>
|
||||
|
||||
{/* 待清理项目徽章群 */}
|
||||
@@ -69,7 +75,7 @@ export function StorageCleanerConfirm({
|
||||
<div className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 rounded-lg bg-destructive/5 border border-dashed border-destructive/20 w-full max-w-[240px]">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-destructive shrink-0" />
|
||||
<span className="text-[11px] font-bold text-destructive leading-none tracking-tight">
|
||||
{t('storageCleaner:irreversible')}
|
||||
{'此操作不可撤销'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,7 +87,7 @@ export function StorageCleanerConfirm({
|
||||
onClick={onConfirm}
|
||||
className="w-full text-xs font-bold shadow-sm h-9"
|
||||
>
|
||||
{t('storageCleaner:confirmAction')}
|
||||
{'确认清理'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -89,7 +95,7 @@ export function StorageCleanerConfirm({
|
||||
onClick={onClose}
|
||||
className="w-full text-xs font-semibold shadow-sm h-9 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{t('common_buttons_cancel')}
|
||||
{'取消'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import type { StorageCleanerOptions } from '@/types/storage';
|
||||
import type { StorageSizeInfo } from '../useStorageCleaner';
|
||||
import OptionItem from './OptionItem';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -26,8 +25,6 @@ export default function StorageOptionsGrid({
|
||||
className,
|
||||
...props
|
||||
}: StorageOptionsGridProps) {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
const optionKeys: (keyof StorageCleanerOptions)[] = [
|
||||
'localStorage',
|
||||
'sessionStorage',
|
||||
@@ -48,7 +45,7 @@ export default function StorageOptionsGrid({
|
||||
{optionKeys.map((key) => (
|
||||
<OptionItem
|
||||
key={key}
|
||||
labelKey={`storageCleaner:options.${key}`}
|
||||
labelKey={key}
|
||||
checked={options[key]}
|
||||
sizeInfo={sizes[key]}
|
||||
onChange={() => onOptionChange(key)}
|
||||
@@ -62,7 +59,7 @@ export default function StorageOptionsGrid({
|
||||
className="border-t border-border flex justify-between items-center pl-3.5 pr-7 py-2.5 bg-muted/20 hover:bg-muted/40 cursor-pointer select-none transition-colors"
|
||||
>
|
||||
<Label className="text-xs font-bold text-muted-foreground/90 cursor-pointer tracking-wide uppercase">
|
||||
{t('storageCleaner:selectAll')}
|
||||
{'全选所有项'}
|
||||
</Label>
|
||||
|
||||
<Checkbox
|
||||
|
||||
@@ -6,11 +6,8 @@ import StorageOptionsGrid from './components/StorageOptionsGrid';
|
||||
import AutoRefreshToggle from './components/AutoRefreshToggle';
|
||||
import ErrorDisplay from './components/ErrorDisplay';
|
||||
import CleaningResult from './components/CleaningResult';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
|
||||
export default function Index() {
|
||||
const { t } = useI18n('storageCleaner');
|
||||
|
||||
const {
|
||||
error,
|
||||
isInitializing,
|
||||
@@ -36,7 +33,7 @@ export default function Index() {
|
||||
<div className="flex flex-col items-center justify-center py-12 min-h-[280px] w-full">
|
||||
<Loader2 className="h-6 w-6 text-muted-foreground/80" />
|
||||
<span className="text-xs text-muted-foreground mt-2 font-medium tracking-wide">
|
||||
{t('storageCleaner:initializing')}
|
||||
{'正在读取站点数据...'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -75,10 +72,10 @@ export default function Index() {
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
{t('storageCleaner:cleaning')}
|
||||
{'正在清理...'}
|
||||
</>
|
||||
) : (
|
||||
t('storageCleaner:cleanNow')
|
||||
'立即清理'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
getSessionStorageSize,
|
||||
isRestrictedUrl,
|
||||
} from '@/utils/storageCleaner';
|
||||
import { useI18n } from '@/utils/chromeI18n';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const DEFAULT_OPTIONS: StorageCleanerOptions = {
|
||||
@@ -59,7 +58,6 @@ export interface UseStorageCleanerReturn {
|
||||
}
|
||||
|
||||
export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
const { t } = useI18n(['storageCleaner', 'common']);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isInitializing, setIsInitializing] = useState<boolean>(true);
|
||||
const [options, setOptions] = useState<StorageCleanerOptions>(DEFAULT_OPTIONS);
|
||||
@@ -93,11 +91,11 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
if (currentRequestId !== requestIdRef.current) return;
|
||||
|
||||
if (!tab || !tab.url) {
|
||||
setError(t('storageCleaner:errorNoTab'));
|
||||
setError('无法获取当前标签页');
|
||||
return;
|
||||
}
|
||||
if (isRestrictedUrl(tab.url)) {
|
||||
setError(t('storageCleaner:errorRestricted'));
|
||||
setError('存储清理功能不支持此页面');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +133,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
setIsInitializing(false);
|
||||
}
|
||||
}
|
||||
}, [t]);
|
||||
}, []);
|
||||
|
||||
const loadInfoRef = useRef(loadInfo);
|
||||
useEffect(() => {
|
||||
@@ -177,10 +175,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
if (storageTimerRef.current) clearTimeout(storageTimerRef.current);
|
||||
storageTimerRef.current = setTimeout(async () => {
|
||||
await storageUtil
|
||||
.set('storageCleaner/preferences', {
|
||||
reloadAfterClean,
|
||||
selectedTypes: options,
|
||||
})
|
||||
.set('storageCleaner/preferences', { reloadAfterClean, selectedTypes: options })
|
||||
.catch(console.error);
|
||||
}, 500);
|
||||
}, [options, reloadAfterClean, isInitializing]);
|
||||
@@ -209,7 +204,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
|
||||
const tab = await getCurrentTab();
|
||||
if (!tab || !tab.id || !tab.url) {
|
||||
toast.warning(t('storageCleaner:errorNoTab'));
|
||||
toast.warning('无法获取当前标签页');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,18 +214,18 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
||||
setResult(cleaningResult);
|
||||
|
||||
if (reloadAfterClean && cleaningResult.overallSuccess) {
|
||||
toast.success(t('storageCleaner:cleanSuccessReload'));
|
||||
toast.success('清理成功,即将刷新页面');
|
||||
await chrome.tabs.reload(tab.id);
|
||||
} else {
|
||||
await loadInfo();
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(`${t('storageCleaner:cleanError')}: ${String(err)}`);
|
||||
toast.error(`清理失败: ${String(err)}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setShowConfirm(false);
|
||||
}
|
||||
}, [options, reloadAfterClean, loadInfo, t]);
|
||||
}, [options, reloadAfterClean, loadInfo]);
|
||||
|
||||
const totalBytes = useMemo(() => {
|
||||
return Object.values(sizes).reduce((acc, s) => {
|
||||
|
||||
Reference in New Issue
Block a user