refactor(i18n): 移除 chrome.i18n 国际化,统一使用中文硬编码

移除 chromeI18n 工具、_locales 翻译文件和 manifest default_locale 配置,
将所有 UI 文案改为直接硬编码中文,并更新相关测试与文档。
This commit is contained in:
雨霖铃
2026-06-18 14:22:14 +00:00
parent 618f3c36f2
commit ec92afa294
68 changed files with 531 additions and 2148 deletions
-68
View File
@@ -1,68 +0,0 @@
/**
* chrome.i18n 类型安全 wrapper
* 提供与 react-i18next 兼容的接口
*/
/**
* 获取翻译文本
* @param msgId 翻译 key(如 'timestamp_pageTitle'
* @param substitutions 占位符替换值(可选)
* @returns 翻译后的文本
*/
export function getMessage(msgId: string, substitutions?: string[]): string {
try {
return chrome.i18n.getMessage(msgId, substitutions);
} catch (error) {
console.warn(`[chrome.i18n] 无法获取翻译: ${msgId}`, error);
return msgId; // 回退到 key 本身
}
}
/**
* react-i18next 兼容的 Hook
* 返回 t 函数和相关信息
*/
export function useI18n(namespace?: string | string[]) {
const namespaces = Array.isArray(namespace) ? namespace : namespace ? [namespace] : [];
const t = (key: string, options?: Record<string, unknown>): string => {
// 统一将分隔符转换为下划线,兼容 'namespace:key.path' 和 'key.path' 两种写法
const msgId = key.replace(':', '_').replace(/\./g, '_');
// 先尝试直接查找 key
let message = getMessage(msgId);
// 如果直接查找未命中(空字符串或返回 key 本身),尝试命名空间前缀(使用转换后的 msgId)
if ((!message || message === msgId) && namespaces.length > 0) {
for (const ns of namespaces) {
const candidate = `${ns}_${msgId}`;
const result = getMessage(candidate);
if (result !== candidate) {
message = result;
break;
}
}
}
if (options) {
for (const [placeholder, value] of Object.entries(options)) {
message = message.replace(`{{${placeholder}}}`, String(value));
}
}
return message;
};
return {
t,
i18n: {
language: 'zh',
changeLanguage: (_lng?: string) => {
// chrome.i18n 无法动态切换语言,需要刷新页面
console.warn('[chrome.i18n] 无法动态切换语言,需要刷新页面');
return Promise.resolve();
},
},
isLoaded: true,
};
}
+7 -12
View File
@@ -2,8 +2,6 @@
* JWT 解析工具
*/
import { getMessage } from '@/utils/chromeI18n';
interface JwtHeader {
alg: string;
typ?: string;
@@ -43,7 +41,7 @@ export function decodeBase64Url(str: string): string {
const pad = base64.length % 4;
if (pad) {
if (pad === 1) {
throw new Error(getMessage('jwt_errors_invalidBase64String'));
throw new Error('无效的 Base64URL 字符串');
}
base64 += new Array(5 - pad).join('=');
}
@@ -58,10 +56,9 @@ export function decodeBase64Url(str: string): string {
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
} catch (e) {
throw new Error(
getMessage('jwt_errors_failedToDecode') + (e instanceof Error ? e.message : String(e)),
{ cause: e },
);
throw new Error('Base64 解码失败: ' + (e instanceof Error ? e.message : String(e)), {
cause: e,
});
}
}
@@ -78,7 +75,7 @@ export function parseJwt(token: string): JwtResult {
payload: null,
signature: '',
raw: { header: '', payload: '', signature: '' },
error: getMessage('jwt_errors_invalidFormat'),
error: 'JWT 格式无效:应包含 3 个部分(header.payload.signature',
};
}
@@ -98,8 +95,7 @@ export function parseJwt(token: string): JwtResult {
const headerJson = decodeBase64Url(headerB64);
result.header = JSON.parse(headerJson);
} catch (e) {
result.error =
getMessage('jwt_errors_parseHeaderFailed') + (e instanceof Error ? e.message : String(e));
result.error = 'JWT Header 解析失败: ' + (e instanceof Error ? e.message : String(e));
return result;
}
@@ -107,8 +103,7 @@ export function parseJwt(token: string): JwtResult {
const payloadJson = decodeBase64Url(payloadB64);
result.payload = JSON.parse(payloadJson);
} catch (e) {
result.error =
getMessage('jwt_errors_parsePayloadFailed') + (e instanceof Error ? e.message : String(e));
result.error = 'JWT Payload 解析失败: ' + (e instanceof Error ? e.message : String(e));
return result;
}
+1 -1
View File
@@ -9,7 +9,7 @@ import type { DataRule, FieldConfig } from '@/types/testDataGenerator';
const STORAGE_KEY = 'testDataGenerator_rules';
/** 最大规则数量 */
const MAX_RULES = 20;
export const MAX_RULES = 20;
/**
* 获取所有规则
+13 -7
View File
@@ -20,6 +20,15 @@ const CLEAN_OPTION_KEYS: (keyof StorageCleanerOptions)[] = [
'serviceWorkers',
];
const OPTION_LABELS: Record<string, string> = {
localStorage: 'Local Storage',
sessionStorage: 'Session Storage',
indexedDB: '站点存储',
cookies: 'Cookies',
cacheStorage: 'Cache Storage',
serviceWorkers: 'Service Workers',
};
export async function getCurrentTab() {
// For popup pages, we need to get the active tab from the browser window that triggered the popup.
// We should ONLY care about the currently active tab in the last focused window.
@@ -362,22 +371,19 @@ export async function clearStorage(
return result;
}
export function formatCleaningResult(
result: CleaningResult,
t: (key: string, options?: Record<string, unknown>) => string,
): string {
export function formatCleaningResult(result: CleaningResult): string {
const parts: string[] = [];
for (const key of CLEAN_OPTION_KEYS) {
const r = result[key];
if (r?.success && r.count > 0) {
parts.push(`${r.count} ${t(`storageCleaner:options.${key}`)}`);
parts.push(`${r.count} ${OPTION_LABELS[key] || key}`);
}
}
if (parts.length === 0) {
return t('storageCleaner:noDataToClean');
return '该页面没有可清理的存储数据';
}
return t('storageCleaner:cleanedSummary', { items: parts.join(', ') });
return `清理了 ${parts.join(', ')}`;
}