镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-22 14:25:41 +08:00
feat(i18n): 实现多语言支持并优化脚本注入
为工具栏、配置面板、预览面板等组件添加多语言支持 优化页面脚本注入逻辑,添加超时处理和清理机制 添加语言变更时强制重新渲染Lit组件的功能 更新中英文翻译文件
这个提交包含在:
父节点
104407fe6b
当前提交
4c27a3e18c
@ -208,7 +208,7 @@ export class ConfigPanel extends LitElement {
|
|||||||
${!showExecutionView && hasConflicts
|
${!showExecutionView && hasConflicts
|
||||||
? html`
|
? html`
|
||||||
<div class="warning-message">
|
<div class="warning-message">
|
||||||
⚠️ 检测到 ${this.conflictCount} 个冲突
|
⚠️ ${I18nService.t('conflicts_detected', [String(this.conflictCount)])}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
|
|||||||
@ -446,7 +446,7 @@ export class FileSelectorPanel extends LitElement {
|
|||||||
|
|
||||||
// Confirm if there are conflicts
|
// Confirm if there are conflicts
|
||||||
if (this.conflictIds.size > 0) {
|
if (this.conflictIds.size > 0) {
|
||||||
const conflictMessage = `检测到 ${this.conflictIds.size} 个冲突,是否继续?`;
|
const conflictMessage = I18nService.t('conflicts_detected_confirm', [String(this.conflictIds.size)]);
|
||||||
const confirmed = confirm(conflictMessage);
|
const confirmed = confirm(conflictMessage);
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
|
|||||||
@ -60,9 +60,7 @@ export class PreviewPanel extends LitElement {
|
|||||||
<line x1="12" y1="9" x2="12" y2="13"></line>
|
<line x1="12" y1="9" x2="12" y2="13"></line>
|
||||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||||
</svg>
|
</svg>
|
||||||
<span>
|
<span>${I18nService.t('conflicts_detected', [String(this.conflictCount)])}</span>
|
||||||
检测到 ${this.conflictCount} 个冲突
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
@ -70,7 +68,7 @@ export class PreviewPanel extends LitElement {
|
|||||||
|
|
||||||
<div class="panel-stats">
|
<div class="panel-stats">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">预览项:</span>
|
<span class="stat-label">${I18nService.t('preview_items')}</span>
|
||||||
<span class="stat-value">${this.items.length}</span>
|
<span class="stat-value">${this.items.length}</span>
|
||||||
</div>
|
</div>
|
||||||
${this.showStatus
|
${this.showStatus
|
||||||
@ -92,7 +90,7 @@ export class PreviewPanel extends LitElement {
|
|||||||
${hasConflicts
|
${hasConflicts
|
||||||
? html`
|
? html`
|
||||||
<div class="stat-item conflict">
|
<div class="stat-item conflict">
|
||||||
<span class="stat-label">冲突:</span>
|
<span class="stat-label">${I18nService.t('preview_summary_conflict')}</span>
|
||||||
<span class="stat-value">${this.conflictCount}</span>
|
<span class="stat-value">${this.conflictCount}</span>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -104,7 +102,7 @@ export class PreviewPanel extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="loading-state">
|
<div class="loading-state">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>正在生成预览...</p>
|
<p>${I18nService.t('preview_loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
|
|||||||
@ -152,7 +152,7 @@ export class Toolbar extends LitElement {
|
|||||||
|
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<span class="selected-count">
|
<span class="selected-count">
|
||||||
已选择 ${this.selectedCount} / ${this.totalCount} 个文件
|
${I18nService.t('selected_files_count', [String(this.selectedCount), String(this.totalCount)])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { LitElement, html, css, type PropertyValues } from 'lit';
|
|||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { virtualize, virtualizerRef } from '@lit-labs/virtualizer/virtualize.js';
|
import { virtualize, virtualizerRef } from '@lit-labs/virtualizer/virtualize.js';
|
||||||
import { FileItem } from '../../types/platform';
|
import { FileItem } from '../../types/platform';
|
||||||
|
import { I18nService } from '../../utils/i18n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual File List Component
|
* Virtual File List Component
|
||||||
@ -201,7 +202,7 @@ export class VirtualFileList extends LitElement {
|
|||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>没有找到文件</p>
|
<p>${I18nService.t('no_files_found')}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
||||||
<polyline points="14 2 14 8 20 8"></polyline>
|
<polyline points="14 2 14 8 20 8"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
<p>选择文件并配置规则以查看预览</p>
|
<p>${I18nService.t('preview_empty_hint')}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,36 +169,60 @@ function cleanupOldInstances(): void {
|
|||||||
* @param platform - 平台名称 (aliyun/baidu/quark)
|
* @param platform - 平台名称 (aliyun/baidu/quark)
|
||||||
*/
|
*/
|
||||||
async function injectPageScriptToMainWorld(platform: 'aliyun' | 'baidu' | 'quark'): Promise<void> {
|
async function injectPageScriptToMainWorld(platform: 'aliyun' | 'baidu' | 'quark'): Promise<void> {
|
||||||
try {
|
|
||||||
// ✅ 引用编译后的 .js 文件(Vite 会将 TypeScript 编译为 JavaScript)
|
// ✅ 引用编译后的 .js 文件(Vite 会将 TypeScript 编译为 JavaScript)
|
||||||
const scriptPath = `src/adapters/${platform}/page-script.js`;
|
const scriptPath = `src/adapters/${platform}/page-script.js`;
|
||||||
const scriptURL = chrome.runtime.getURL(scriptPath);
|
const scriptURL = chrome.runtime.getURL(scriptPath);
|
||||||
|
let script: HTMLScriptElement | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
// 创建 script 标签并注入到页面 (MAIN world)
|
// 创建 script 标签并注入到页面 (MAIN world)
|
||||||
const script = document.createElement('script');
|
const scriptEl = document.createElement('script');
|
||||||
script.src = scriptURL;
|
script = scriptEl;
|
||||||
script.type = 'module'; // 支持 ES6 模块
|
scriptEl.src = scriptURL;
|
||||||
|
scriptEl.type = 'module'; // 支持 ES6 模块
|
||||||
|
|
||||||
// 等待脚本加载完成
|
// 等待脚本加载完成(加超时保护,避免 CSP/站点脚本拦截导致 onload/onerror 永不触发,从而阻塞整个悬浮按钮注入)
|
||||||
|
const timeoutMs = 1500;
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
script.onload = () => {
|
const cleanup = () => {
|
||||||
|
scriptEl.onload = null;
|
||||||
|
scriptEl.onerror = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error(`Timeout loading page-script for ${platform}`));
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
scriptEl.onload = () => {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
cleanup();
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
script.onerror = () => {
|
scriptEl.onerror = () => {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
cleanup();
|
||||||
reject(new Error(`Failed to load page-script for ${platform}`));
|
reject(new Error(`Failed to load page-script for ${platform}`));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 注入到页面
|
// 注入到页面
|
||||||
(document.head || document.documentElement).appendChild(script);
|
(document.head || document.documentElement).appendChild(scriptEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 脚本加载后可以移除标签 (代码已执行)
|
// 脚本加载后可以移除标签 (代码已执行)
|
||||||
script.remove();
|
scriptEl.remove();
|
||||||
|
|
||||||
logger.info(`${platform} page-script injected to MAIN world successfully`);
|
logger.info(`${platform} page-script injected to MAIN world successfully`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to inject ${platform} page-script:`, error instanceof Error ? error : new Error(String(error)));
|
// 不要阻塞悬浮按钮注入:即使 page-script 注入失败,也应该让 UI 先显示出来
|
||||||
throw error;
|
// 相关平台的 API 调用可能会退化/失败,但用户至少能看到按钮和面板,且能在控制台看到诊断信息
|
||||||
|
logger.warn(
|
||||||
|
`[DIAG] Failed to inject ${platform} page-script (continue without it):`,
|
||||||
|
error instanceof Error ? error : new Error(String(error))
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// Best-effort cleanup: 避免遗留无用的 <script>
|
||||||
|
script?.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +419,40 @@ function setupStorageListener(platform: PlatformName): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force Lit-based components to re-render when language changes.
|
||||||
|
*
|
||||||
|
* Reason: I18nService stores language in a static field; updating it won't
|
||||||
|
* automatically trigger LitElement re-rendering unless some reactive state changes.
|
||||||
|
* We traverse the panel subtree (including nested shadow roots) and call
|
||||||
|
* requestUpdate() for any element that exposes it.
|
||||||
|
*/
|
||||||
|
function requestLitRenderDeep(root: Element): void {
|
||||||
|
const queue: Array<Element | ShadowRoot> = [root];
|
||||||
|
const visited = new Set<Node>();
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
const node = queue.shift();
|
||||||
|
if (!node || visited.has(node)) continue;
|
||||||
|
visited.add(node);
|
||||||
|
|
||||||
|
if (node instanceof Element) {
|
||||||
|
const maybeLit = node as unknown as { requestUpdate?: () => void; shadowRoot?: ShadowRoot | null };
|
||||||
|
maybeLit.requestUpdate?.();
|
||||||
|
|
||||||
|
if (maybeLit.shadowRoot) {
|
||||||
|
queue.push(maybeLit.shadowRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.push(...Array.from(node.children));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShadowRoot (DocumentFragment)
|
||||||
|
queue.push(...Array.from(node.children));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置语言变更监听器
|
* 设置语言变更监听器
|
||||||
* 同步 popup 的语言切换到 content script 上下文
|
* 同步 popup 的语言切换到 content script 上下文
|
||||||
@ -411,6 +469,11 @@ function setupLanguageChangeListener(): void {
|
|||||||
// 通知 FloatingButton 更新 UI
|
// 通知 FloatingButton 更新 UI
|
||||||
floatingButton?.updateLanguage();
|
floatingButton?.updateLanguage();
|
||||||
|
|
||||||
|
// ✅ 同步更新已打开的面板(以及其内部的所有 Lit 组件)
|
||||||
|
if (fileSelectorPanel?.open) {
|
||||||
|
requestLitRenderDeep(fileSelectorPanel);
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Language synced successfully to: ${newLanguage}`);
|
logger.info(`Language synced successfully to: ${newLanguage}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
"config_panel_title": "Configuration",
|
"config_panel_title": "Configuration",
|
||||||
"file_list_panel_title": "File List",
|
"file_list_panel_title": "File List",
|
||||||
"preview_panel_title": "Preview",
|
"preview_panel_title": "Preview",
|
||||||
|
"preview_items": "Preview items:",
|
||||||
"execute_rename": "Execute Rename",
|
"execute_rename": "Execute Rename",
|
||||||
"executing": "Executing...",
|
"executing": "Executing...",
|
||||||
"loading_files": "Loading files...",
|
"loading_files": "Loading files...",
|
||||||
@ -24,6 +25,8 @@
|
|||||||
"type_image": "Image",
|
"type_image": "Image",
|
||||||
"type_document": "Document",
|
"type_document": "Document",
|
||||||
"type_other": "Other",
|
"type_other": "Other",
|
||||||
|
"selected_files_count": "Selected $1 / $2 files",
|
||||||
|
"no_files_found": "No files found",
|
||||||
"rename_dialog_file_count": "files",
|
"rename_dialog_file_count": "files",
|
||||||
"rename_dialog_close": "×",
|
"rename_dialog_close": "×",
|
||||||
"rule_selector_title": "Select Rename Rule",
|
"rule_selector_title": "Select Rename Rule",
|
||||||
@ -90,7 +93,10 @@
|
|||||||
"sync_failed": "Sync failed, please refresh the page",
|
"sync_failed": "Sync failed, please refresh the page",
|
||||||
"sync_retry": "Resync",
|
"sync_retry": "Resync",
|
||||||
"sync_not_supported": "Auto sync is not supported on this platform. Please refresh the page.",
|
"sync_not_supported": "Auto sync is not supported on this platform. Please refresh the page.",
|
||||||
|
"conflicts_detected": "Conflicts detected: $1",
|
||||||
|
"conflicts_detected_confirm": "Conflicts detected: $1. Continue?",
|
||||||
"preview_empty": "No files to preview",
|
"preview_empty": "No files to preview",
|
||||||
|
"preview_empty_hint": "Select files and configure rules to see the preview",
|
||||||
"preview_loading": "Generating preview...",
|
"preview_loading": "Generating preview...",
|
||||||
"preview_summary_total": "Total:",
|
"preview_summary_total": "Total:",
|
||||||
"preview_summary_changed": "To Change:",
|
"preview_summary_changed": "To Change:",
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
"config_panel_title": "配置",
|
"config_panel_title": "配置",
|
||||||
"file_list_panel_title": "文件列表",
|
"file_list_panel_title": "文件列表",
|
||||||
"preview_panel_title": "预览",
|
"preview_panel_title": "预览",
|
||||||
|
"preview_items": "预览项:",
|
||||||
"execute_rename": "执行重命名",
|
"execute_rename": "执行重命名",
|
||||||
"executing": "执行中...",
|
"executing": "执行中...",
|
||||||
"loading_files": "正在加载文件...",
|
"loading_files": "正在加载文件...",
|
||||||
@ -24,6 +25,8 @@
|
|||||||
"type_image": "图片",
|
"type_image": "图片",
|
||||||
"type_document": "文档",
|
"type_document": "文档",
|
||||||
"type_other": "其他",
|
"type_other": "其他",
|
||||||
|
"selected_files_count": "已选择 $1 / $2 个文件",
|
||||||
|
"no_files_found": "没有找到文件",
|
||||||
"rename_dialog_file_count": "个文件",
|
"rename_dialog_file_count": "个文件",
|
||||||
"rename_dialog_close": "×",
|
"rename_dialog_close": "×",
|
||||||
"rule_selector_title": "选择重命名规则",
|
"rule_selector_title": "选择重命名规则",
|
||||||
@ -90,7 +93,10 @@
|
|||||||
"sync_failed": "同步失败,可能需要手动刷新页面",
|
"sync_failed": "同步失败,可能需要手动刷新页面",
|
||||||
"sync_retry": "重新同步",
|
"sync_retry": "重新同步",
|
||||||
"sync_not_supported": "当前平台暂不支持自动同步,请手动刷新页面",
|
"sync_not_supported": "当前平台暂不支持自动同步,请手动刷新页面",
|
||||||
|
"conflicts_detected": "检测到 $1 个冲突",
|
||||||
|
"conflicts_detected_confirm": "检测到 $1 个冲突,是否继续?",
|
||||||
"preview_empty": "暂无文件可预览",
|
"preview_empty": "暂无文件可预览",
|
||||||
|
"preview_empty_hint": "选择文件并配置规则以查看预览",
|
||||||
"preview_loading": "生成预览中...",
|
"preview_loading": "生成预览中...",
|
||||||
"preview_summary_total": "总计:",
|
"preview_summary_total": "总计:",
|
||||||
"preview_summary_changed": "将改变:",
|
"preview_summary_changed": "将改变:",
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
"config_panel_title": "設定",
|
"config_panel_title": "設定",
|
||||||
"file_list_panel_title": "檔案列表",
|
"file_list_panel_title": "檔案列表",
|
||||||
"preview_panel_title": "預覽",
|
"preview_panel_title": "預覽",
|
||||||
|
"preview_items": "預覽項:",
|
||||||
"execute_rename": "執行重新命名",
|
"execute_rename": "執行重新命名",
|
||||||
"executing": "執行中...",
|
"executing": "執行中...",
|
||||||
"loading_files": "正在載入檔案...",
|
"loading_files": "正在載入檔案...",
|
||||||
@ -24,6 +25,8 @@
|
|||||||
"type_image": "圖片",
|
"type_image": "圖片",
|
||||||
"type_document": "文件",
|
"type_document": "文件",
|
||||||
"type_other": "其他",
|
"type_other": "其他",
|
||||||
|
"selected_files_count": "已選擇 $1 / $2 個檔案",
|
||||||
|
"no_files_found": "沒有找到檔案",
|
||||||
"rename_dialog_file_count": "個檔案",
|
"rename_dialog_file_count": "個檔案",
|
||||||
"rename_dialog_close": "×",
|
"rename_dialog_close": "×",
|
||||||
"rule_selector_title": "選擇重新命名規則",
|
"rule_selector_title": "選擇重新命名規則",
|
||||||
@ -90,7 +93,10 @@
|
|||||||
"sync_failed": "同步失敗,可能需要手動重新整理頁面",
|
"sync_failed": "同步失敗,可能需要手動重新整理頁面",
|
||||||
"sync_retry": "重新同步",
|
"sync_retry": "重新同步",
|
||||||
"sync_not_supported": "目前平台暫不支援自動同步,請手動重新整理頁面",
|
"sync_not_supported": "目前平台暫不支援自動同步,請手動重新整理頁面",
|
||||||
|
"conflicts_detected": "偵測到 $1 個衝突",
|
||||||
|
"conflicts_detected_confirm": "偵測到 $1 個衝突,是否繼續?",
|
||||||
"preview_empty": "暫無檔案可預覽",
|
"preview_empty": "暫無檔案可預覽",
|
||||||
|
"preview_empty_hint": "選擇檔案並設定規則以查看預覽",
|
||||||
"preview_loading": "產生預覽中...",
|
"preview_loading": "產生預覽中...",
|
||||||
"preview_summary_total": "總計:",
|
"preview_summary_total": "總計:",
|
||||||
"preview_summary_changed": "將變更:",
|
"preview_summary_changed": "將變更:",
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
* 1. Edit version in package.json
|
* 1. Edit version in package.json
|
||||||
* 2. Run: npm run sync:version (or automatically via prebuild/predev)
|
* 2. Run: npm run sync:version (or automatically via prebuild/predev)
|
||||||
*
|
*
|
||||||
* Generated at: 2025-12-29T11:20:31.860Z
|
* Generated at: 2025-12-30T04:26:21.804Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户