mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-10 23:12:05 +08:00
feat(i18n): full i18n for all pages + sidebar lang switcher + zh-TW locale
- All pages now use t() for internationalization - Sidebar footer: searchable upward dropdown language switcher - Generated zh-TW.json (Traditional Chinese) via gen-locales.cjs - CSS for lang switcher with mobile/collapsed sidebar support - Removed language toggle from settings page
This commit is contained in:
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
import { api } from '../lib/tauri-api.js'
|
||||
import { toast } from '../components/toast.js'
|
||||
import { t } from '../lib/i18n.js'
|
||||
|
||||
const LOG_TABS = [
|
||||
{ key: 'gateway', label: 'Gateway 日志' },
|
||||
{ key: 'gateway-err', label: 'Gateway 错误' },
|
||||
{ key: 'guardian', label: '守护进程' },
|
||||
{ key: 'guardian-backup', label: '备份日志' },
|
||||
{ key: 'config-audit', label: '审计日志' },
|
||||
{ key: 'gateway', label: () => t('logs.tabGateway') },
|
||||
{ key: 'gateway-err', label: () => t('logs.tabGatewayErr') },
|
||||
{ key: 'guardian', label: () => t('logs.tabGuardian') },
|
||||
{ key: 'guardian-backup', label: () => t('logs.tabBackup') },
|
||||
{ key: 'config-audit', label: () => t('logs.tabAudit') },
|
||||
]
|
||||
|
||||
let _searchTimer = null
|
||||
@@ -20,17 +21,17 @@ export async function render() {
|
||||
|
||||
page.innerHTML = `
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">日志查看</h1>
|
||||
<p class="page-desc">查看 OpenClaw 各服务日志</p>
|
||||
<h1 class="page-title">${t('logs.title')}</h1>
|
||||
<p class="page-desc">${t('logs.desc')}</p>
|
||||
</div>
|
||||
<div class="tab-bar">
|
||||
${LOG_TABS.map((t, i) => `<div class="tab${i === 0 ? ' active' : ''}" data-tab="${t.key}">${t.label}</div>`).join('')}
|
||||
${LOG_TABS.map((item, i) => `<div class="tab${i === 0 ? ' active' : ''}" data-tab="${item.key}">${item.label()}</div>`).join('')}
|
||||
</div>
|
||||
<div class="log-toolbar">
|
||||
<input type="text" class="form-input" id="log-search" placeholder="搜索日志..." style="max-width:300px">
|
||||
<button class="btn btn-secondary btn-sm" id="btn-refresh">刷新</button>
|
||||
<input type="text" class="form-input" id="log-search" placeholder="${t('logs.searchPlaceholder')}" style="max-width:300px">
|
||||
<button class="btn btn-secondary btn-sm" id="btn-refresh">${t('logs.refresh')}</button>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:var(--font-size-sm);color:var(--text-secondary)">
|
||||
<input type="checkbox" id="log-autoscroll" checked> 自动滚动
|
||||
<input type="checkbox" id="log-autoscroll" checked> ${t('logs.autoScroll')}
|
||||
</label>
|
||||
</div>
|
||||
<div class="log-viewer" id="log-content" style="height:calc(100vh - 280px)"><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div></div>
|
||||
@@ -41,7 +42,7 @@ export async function render() {
|
||||
// Tab 切换
|
||||
page.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.onclick = () => {
|
||||
page.querySelectorAll('.tab').forEach(t => t.classList.remove('active'))
|
||||
page.querySelectorAll('.tab').forEach(el => el.classList.remove('active'))
|
||||
tab.classList.add('active')
|
||||
currentTab = tab.dataset.tab
|
||||
page.querySelector('#log-search').value = ''
|
||||
@@ -77,12 +78,12 @@ async function loadLog(page, logName) {
|
||||
const el = page.querySelector('#log-content')
|
||||
const refreshBtn = page.querySelector('#btn-refresh')
|
||||
// 显示加载状态
|
||||
el.innerHTML = '<div class="log-loading"><div class="service-spinner"></div><span style="color:var(--text-tertiary);margin-left:8px">加载日志中...</span></div>'
|
||||
el.innerHTML = '<div class="log-loading"><div class="service-spinner"></div><span style="color:var(--text-tertiary);margin-left:8px">' + t('logs.loading') + '</span></div>'
|
||||
if (refreshBtn) { refreshBtn.classList.add('btn-loading'); refreshBtn.disabled = true }
|
||||
try {
|
||||
const content = await api.readLogTail(logName, 200)
|
||||
if (!content || !content.trim()) {
|
||||
el.innerHTML = '<div style="color:var(--text-tertiary)">暂无日志</div>'
|
||||
el.innerHTML = '<div style="color:var(--text-tertiary)">' + t('logs.empty') + '</div>'
|
||||
return
|
||||
}
|
||||
const lines = content.trim().split('\n')
|
||||
@@ -91,8 +92,8 @@ async function loadLog(page, logName) {
|
||||
el.scrollTop = el.scrollHeight
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerHTML = '<div style="color:var(--error);padding:12px">加载日志失败: ' + e + '</div>'
|
||||
toast('加载日志失败: ' + e, 'error')
|
||||
el.innerHTML = '<div style="color:var(--error);padding:12px">' + t('logs.loadFailed') + ': ' + e + '</div>'
|
||||
toast(t('logs.loadFailed') + ': ' + e, 'error')
|
||||
} finally {
|
||||
if (refreshBtn) { refreshBtn.classList.remove('btn-loading'); refreshBtn.disabled = false }
|
||||
}
|
||||
@@ -103,13 +104,13 @@ async function searchLog(page, logName, query) {
|
||||
try {
|
||||
const results = await api.searchLog(logName, query)
|
||||
if (!results || !results.length) {
|
||||
el.innerHTML = '<div style="color:var(--text-tertiary)">未找到匹配结果</div>'
|
||||
el.innerHTML = '<div style="color:var(--text-tertiary)">' + t('logs.noResults') + '</div>'
|
||||
return
|
||||
}
|
||||
el.innerHTML = results.map(l => `<div class="log-line">${highlightMatch(escapeHtml(l), query)}</div>`).join('')
|
||||
} catch (e) {
|
||||
el.innerHTML = '<div style="color:var(--error);padding:12px">搜索失败: ' + e + '</div>'
|
||||
toast('搜索失败: ' + e, 'error')
|
||||
el.innerHTML = '<div style="color:var(--error);padding:12px">' + t('logs.searchFailed') + ': ' + e + '</div>'
|
||||
toast(t('logs.searchFailed') + ': ' + e, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user