chore: add workflow to check unused i18n

This commit is contained in:
Kuingsmile
2025-07-03 16:11:31 +08:00
parent 5c6361662f
commit 1c868c05fb
5 changed files with 518 additions and 297 deletions

213
.github/workflows/i18n-check.yml vendored Normal file
View File

@@ -0,0 +1,213 @@
name: 'I18n Check - Find Unused Translation Keys'
on:
workflow_dispatch:
pull_request:
branches: [ main, dev ]
paths:
- 'src/**/*.vue'
- 'src/**/*.ts'
- 'src/**/*.js'
- 'src/i18n/**/*.json'
- 'scripts/find-unused-i18n.js'
- '.github/workflows/i18n-check.yml'
push:
branches: [ main, dev ]
paths:
- 'src/i18n/**/*.json'
env:
NODE_OPTIONS: '--max-old-space-size=4096'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
i18n-check:
name: Check for Unused I18n Keys
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run i18n unused keys check
id: i18n-check
run: |
echo "Running i18n unused keys analysis..."
# Run the script and capture output
OUTPUT=$(node scripts/find-unused-i18n.js 2>&1)
EXIT_CODE=$?
# Save the output to a file for the job summary
echo "$OUTPUT" > i18n-check-output.txt
# Also output to console
echo "$OUTPUT"
# Check if there are unused keys by looking for the specific pattern in output
if echo "$OUTPUT" | grep -q "🗑️ Unused I18n Keys:"; then
echo "unused_keys_found=true" >> $GITHUB_OUTPUT
echo "❌ Found unused i18n keys!"
exit 1
else
echo "unused_keys_found=false" >> $GITHUB_OUTPUT
echo "✅ No unused i18n keys found!"
exit 0
fi
- name: Generate Job Summary
if: always()
run: |
echo "## 🌐 I18n Keys Analysis Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f i18n-check-output.txt ]; then
echo "### 📊 Analysis Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat i18n-check-output.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ steps.i18n-check.outputs.unused_keys_found }}" = "true" ]; then
echo "### ❌ Action Required" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Unused i18n keys were found. Consider:" >> $GITHUB_STEP_SUMMARY
echo "- Removing unused keys from locale files" >> $GITHUB_STEP_SUMMARY
echo "- Verifying that the keys are actually unused" >> $GITHUB_STEP_SUMMARY
echo "- Adding usage for keys that should be kept" >> $GITHUB_STEP_SUMMARY
else
echo "### ✅ All Clear" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No unused i18n keys found. Great job maintaining clean translations! 🎉" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 💡 Tips" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Run \`yarn i18n:check\` locally to check for unused keys" >> $GITHUB_STEP_SUMMARY
echo "- Run \`yarn i18n:check:verbose\` for detailed usage information" >> $GITHUB_STEP_SUMMARY
echo "- This check runs automatically on PRs that modify Vue, TS, JS, or i18n files" >> $GITHUB_STEP_SUMMARY
- name: Upload analysis results
if: always()
uses: actions/upload-artifact@v4
with:
name: i18n-analysis-results
path: i18n-check-output.txt
retention-days: 7
- name: Comment on PR (if unused keys found)
if: github.event_name == 'pull_request' && steps.i18n-check.outputs.unused_keys_found == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let output = '';
try {
output = fs.readFileSync('i18n-check-output.txt', 'utf8');
} catch (error) {
output = 'Unable to read analysis output';
}
const body = `## 🌐 I18n Analysis Report
❌ **Unused i18n keys were found in this PR**
<details>
<summary>📊 Click to view detailed analysis</summary>
\`\`\`
${output}
\`\`\`
</details>
### 🔧 Action Required
Please review the unused keys listed above and consider:
- **Removing** keys that are truly unused
- **Verifying** that the detection is correct (some dynamic key usage might not be detected)
- **Adding usage** for keys that should be kept
### 💡 Local Testing
You can run this analysis locally using:
\`\`\`bash
yarn i18n:check # Basic check
yarn i18n:check:verbose # Detailed output with usage examples
\`\`\`
---
*This comment was automatically generated by the I18n Check workflow.*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
locale-consistency-check:
name: Check Locale File Consistency
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Check locale files consistency
run: |
echo "Checking if all locale files have the same structure..."
# Run the i18n script in verbose mode to also check for inconsistencies
node scripts/find-unused-i18n.js --verbose > locale-check.txt 2>&1
# Check if there are inconsistencies reported
if grep -q "⚠️ Locale Inconsistencies:" locale-check.txt; then
echo "❌ Found locale inconsistencies!"
cat locale-check.txt
exit 1
else
echo "✅ All locale files are consistent!"
fi
- name: Generate Consistency Report
if: always()
run: |
echo "## 🔄 Locale Consistency Check" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if grep -q "⚠️ Locale Inconsistencies:" locale-check.txt 2>/dev/null; then
echo "### ❌ Inconsistencies Found" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Some keys exist in one locale but not others:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat locale-check.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
else
echo "### ✅ All Locale Files Consistent" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All locale files have matching key structures. Perfect! 🎉" >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -28,6 +28,8 @@
"nowatch": "tauri dev --no-watch",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"i18n:check": "node scripts/find-unused-i18n.js",
"i18n:check:verbose": "node scripts/find-unused-i18n.js --verbose",
"cz": "git-cz",
"release": "bump-version",
"prebuild:dev": "node scripts/prepare.js",

288
scripts/find-unused-i18n.js Normal file
View File

@@ -0,0 +1,288 @@
#!/usr/bin/env node
import { readdirSync, readFileSync } from 'node:fs'
import { basename, dirname, extname, join, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const LOCALE_DIR = join(__dirname, '../src/i18n/locales')
const SRC_DIR = join(__dirname, '../src')
console.log(`\n🔍 Analyzing i18n keys in ${LOCALE_DIR} and source files in ${SRC_DIR}\n`)
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
}
function colorize(text, color) {
return `${colors[color]}${text}${colors.reset}`
}
function flattenKeys(obj, prefix = '') {
const keys = []
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
keys.push(...flattenKeys(value, fullKey))
} else {
keys.push(fullKey)
}
}
return keys
}
function readLocaleFile(filePath) {
try {
const content = readFileSync(filePath, 'utf8')
return JSON.parse(content)
} catch (error) {
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
return {}
}
}
function getAllI18nKeys() {
const localeFiles = readdirSync(LOCALE_DIR).filter(file => file.endsWith('.json'))
const allKeys = new Set()
const localeData = {}
console.log(colorize('\n📁 Found locale files:', 'blue'))
for (const file of localeFiles) {
const filePath = join(LOCALE_DIR, file)
const locale = basename(file, '.json')
const data = readLocaleFile(filePath)
const keys = flattenKeys(data)
localeData[locale] = {
file: filePath,
keys,
data
}
keys.forEach(key => allKeys.add(key))
console.log(` ${colorize('✓', 'green')} ${file} (${keys.length} keys)`)
}
return {
allKeys: Array.from(allKeys).sort(),
localeData
}
}
function findFiles(dir, extensions = ['.vue', '.ts', '.js']) {
const files = []
function walk(currentDir) {
const entries = readdirSync(currentDir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(currentDir, entry.name)
if (entry.isDirectory()) {
if (!['node_modules', '.git', 'dist', 'build', 'target'].includes(entry.name)) {
walk(fullPath)
}
} else if (entry.isFile()) {
const ext = extname(entry.name)
if (extensions.includes(ext)) {
files.push(fullPath)
}
}
}
}
walk(dir)
return files
}
function findKeyUsage(keys) {
const usage = {}
keys.forEach(key => {
usage[key] = {
used: false,
files: [],
patterns: []
}
})
console.log(colorize('\n🔍 Searching for key usage in source files...', 'blue'))
const sourceFiles = findFiles(SRC_DIR)
console.log(` Found ${sourceFiles.length} source files to analyze`)
const searchPatterns = [
/\$?t\s*\(\s*['"`]([^'"`]+)['"`]/g,
/(?:^|[^a-zA-Z])t\s*\(\s*['"`]([^'"`]+)['"`]/g,
/\{\{\s*\$?t\s*\(\s*['"`]([^'"`]+)['"`]/g
]
sourceFiles.forEach(filePath => {
try {
const content = readFileSync(filePath, 'utf8')
const relativePath = relative(join(__dirname, '..'), filePath)
searchPatterns.forEach((pattern, patternIndex) => {
let match
while ((match = pattern.exec(content)) !== null) {
const key = match[1]
if (usage[key]) {
usage[key].used = true
if (!usage[key].files.includes(relativePath)) {
usage[key].files.push(relativePath)
}
if (!usage[key].patterns.includes(patternIndex)) {
usage[key].patterns.push(patternIndex)
}
}
}
})
} catch (error) {
console.error(colorize(`Error reading ${filePath}: ${error.message}`, 'red'))
}
})
return usage
}
function findLocaleInconsistencies(localeData) {
const locales = Object.keys(localeData)
const inconsistencies = {}
if (locales.length < 2) {
return inconsistencies
}
locales.forEach(locale => {
const currentKeys = new Set(localeData[locale].keys)
inconsistencies[locale] = {
missing: [],
extra: []
}
locales.forEach(otherLocale => {
if (locale !== otherLocale) {
localeData[otherLocale].keys.forEach(key => {
if (!currentKeys.has(key) && !inconsistencies[locale].missing.includes(key)) {
inconsistencies[locale].missing.push(key)
}
})
}
})
localeData[locale].keys.forEach(key => {
const existsInOthers = locales.some(
otherLocale => locale !== otherLocale && localeData[otherLocale].keys.includes(key)
)
if (!existsInOthers) {
inconsistencies[locale].extra.push(key)
}
})
})
return inconsistencies
}
function main() {
console.log(colorize('🌐 OpenList Desktop - I18n Usage Analyzer', 'cyan'))
console.log(colorize('==========================================', 'cyan'))
const { allKeys, localeData } = getAllI18nKeys()
console.log(colorize(`\n📊 Total unique keys found: ${allKeys.length}`, 'yellow'))
const usage = findKeyUsage(allKeys)
const usedKeys = allKeys.filter(key => usage[key].used)
const unusedKeys = allKeys.filter(key => !usage[key].used)
const inconsistencies = findLocaleInconsistencies(localeData)
console.log(colorize('\n📈 Usage Summary:', 'blue'))
console.log(` ${colorize('✓', 'green')} Used keys: ${usedKeys.length}`)
console.log(` ${colorize('✗', 'red')} Unused keys: ${unusedKeys.length}`)
console.log(` ${colorize('📊', 'yellow')} Usage rate: ${((usedKeys.length / allKeys.length) * 100).toFixed(1)}%`)
if (unusedKeys.length > 0) {
console.log(colorize('\n🗑 Unused I18n Keys:', 'red'))
console.log(colorize('====================', 'red'))
const groupedUnused = {}
unusedKeys.forEach(key => {
const namespace = key.split('.')[0]
if (!groupedUnused[namespace]) {
groupedUnused[namespace] = []
}
groupedUnused[namespace].push(key)
})
Object.entries(groupedUnused).forEach(([namespace, keys]) => {
console.log(colorize(`\n[${namespace}] - ${keys.length} unused keys:`, 'yellow'))
keys.forEach(key => {
console.log(` ${colorize('✗', 'red')} ${key}`)
})
})
} else {
console.log(colorize('\n🎉 No unused keys found! All i18n keys are being used.', 'green'))
}
const hasInconsistencies = Object.values(inconsistencies).some(inc => inc.missing.length > 0 || inc.extra.length > 0)
if (hasInconsistencies) {
console.log(colorize('\n⚠ Locale Inconsistencies:', 'yellow'))
console.log(colorize('=========================', 'yellow'))
Object.entries(inconsistencies).forEach(([locale, data]) => {
if (data.missing.length > 0 || data.extra.length > 0) {
console.log(colorize(`\n[${locale}.json]:`, 'cyan'))
if (data.missing.length > 0) {
console.log(colorize(` Missing ${data.missing.length} keys:`, 'red'))
data.missing.forEach(key => {
console.log(` ${colorize('✗', 'red')} ${key}`)
})
}
if (data.extra.length > 0) {
console.log(colorize(` Extra ${data.extra.length} keys:`, 'blue'))
data.extra.forEach(key => {
console.log(` ${colorize('!', 'blue')} ${key}`)
})
}
}
})
}
if (process.argv.includes('--verbose') || process.argv.includes('-v')) {
console.log(colorize('\n📋 Sample Used Keys (first 10):', 'blue'))
console.log(colorize('=================================', 'blue'))
usedKeys.slice(0, 10).forEach(key => {
const files = usage[key].files.slice(0, 3) // Show first 3 files
const moreFiles = usage[key].files.length > 3 ? ` (+${usage[key].files.length - 3} more)` : ''
console.log(` ${colorize('✓', 'green')} ${key}`)
console.log(` Used in: ${files.join(', ')}${moreFiles}`)
})
}
console.log(colorize('\n✨ Analysis complete!', 'cyan'))
if (unusedKeys.length > 0) {
console.log(colorize('\n💡 Tip: Run with --verbose (-v) flag to see usage details of used keys', 'blue'))
}
}
main()

View File

@@ -32,17 +32,13 @@
"stopRclone": "Stop RClone",
"manageMounts": "Manage Mounts",
"autoLaunch": "Auto Launch Core(not app)",
"autoMount": "Auto Mount",
"openSettings": "Open Settings",
"processing": "Processing...",
"adminPassword": "Admin Password",
"showAdminPassword": "Show/Copy admin password from logs"
},
"coreMonitor": {
"title": "Core Monitor",
"online": "Online",
"offline": "Offline",
"uptime": "Uptime",
"responseTime": "Response Time",
"healthy": "Healthy",
"unhealthy": "Unhealthy"
@@ -51,7 +47,6 @@
"title": "Version Manager",
"openlist": "OpenList",
"rclone": "Rclone",
"current": "Current",
"selectVersion": "Select Version",
"update": "Update"
},
@@ -72,16 +67,10 @@
"serviceManagement": {
"title": "Service Management",
"serviceStatus": "OpenList Backend Service",
"uptime": "Uptime",
"port": "Port",
"version": "Version",
"install": "Install",
"start": "Start",
"stop": "Stop",
"restart": "Restart",
"uninstall": "Uninstall",
"recentLogs": "Recent Logs",
"noLogs": "No logs available",
"status": {
"running": "Running",
"installed": "Installed",
@@ -101,7 +90,6 @@
"subtitle": "Configure your OpenList Desktop application",
"saveChanges": "Save Changes",
"resetToDefaults": "Reset to defaults",
"unsavedChanges": "You have unsaved changes",
"confirmReset": "Are you sure you want to reset all settings to defaults? This action cannot be undone.",
"saved": "Settings saved successfully!",
"saveFailed": "Failed to save settings. Please try again.",
@@ -126,20 +114,13 @@
"title": "Network Configuration",
"subtitle": "Configure the web interface and network settings"
},
"storage": {
"title": "Storage Configuration",
"subtitle": "Configure data directory and binary locations"
},
"startup": {
"title": "Startup Options",
"subtitle": "Configure automatic startup behavior"
},
"service": {
"title": "Service Configuration",
"subtitle": "Configure OpenList service settings",
"network": {
"title": "Network Configuration",
"subtitle": "Configure the web interface and network settings",
"port": {
"label": "Listen Port",
"placeholder": "5244",
@@ -155,23 +136,7 @@
"description": "Use HTTPS for secure communication (requires SSL certificate configuration)"
}
},
"storage": {
"title": "Storage Configuration",
"subtitle": "Configure data directory and binary locations",
"dataDir": {
"label": "Data Directory",
"placeholder": "./data",
"help": "Directory where OpenList stores its configuration and database"
},
"binaryPath": {
"label": "OpenList Binary Path",
"placeholder": "./binary/openlist.exe",
"help": "Path to the OpenList executable file"
}
},
"startup": {
"title": "Startup Options",
"subtitle": "Configure automatic startup behavior",
"autoLaunch": {
"title": "Auto-launch on startup",
"description": "Automatically start OpenList service when the application launches"
@@ -179,48 +144,16 @@
}
},
"rclone": {
"title": "Storage Configuration",
"subtitle": "Configure remote storage connections",
"config": {
"title": "Remote Storage",
"subtitle": "Configure rclone for remote storage access",
"label": "Rclone Configuration (JSON)",
"tips": "Enter your rclone configuration as JSON. This will be used to configure rclone remotes.",
"invalidJson": "Invalid JSON configuration. Please check your syntax.",
"binaryPath": {
"label": "Rclone Binary Path",
"placeholder": "./binary/rclone.exe",
"help": "Path to the rclone executable file"
},
"configName": {
"label": "Configuration Name",
"placeholder": "remote",
"help": "Name of the rclone remote configuration"
}
},
"mount": {
"title": "Mount Configuration",
"subtitle": "Configure local mount point for remote storage",
"mountPath": {
"label": "Mount Path",
"placeholder": "./mount",
"help": "Local directory where remote storage will be mounted"
},
"autoMount": {
"title": "Auto-mount on startup",
"description": "Automatically mount remote storage when the service starts"
}
},
"flags": {
"title": "Rclone Flags",
"subtitle": "Additional command-line flags for rclone",
"placeholder": "--flag-name=value",
"add": "Add Flag",
"remove": "Remove"
"invalidJson": "Invalid JSON configuration. Please check your syntax."
}
},
"app": {
"title": "Application Settings",
"subtitle": "Configure application preferences and behavior",
"theme": {
"title": "Theme",
@@ -228,8 +161,6 @@
"light": "Light",
"dark": "Dark",
"auto": "Auto",
"lightDesc": "Light theme",
"darkDesc": "Dark theme",
"autoDesc": "Follow system"
},
"monitor": {
@@ -259,45 +190,10 @@
"title": "Auto-launch on startup(Immediate Effect)",
"subtitle": "Automatically start OpenList Desktop application when the system starts",
"description": "Automatically start OpenList service when the application launches"
},
"service": {
"title": "Service Connection",
"subtitle": "Configure connection to the desktop service",
"port": {
"label": "Service Port",
"placeholder": "53211",
"help": "Port number for the desktop service communication"
},
"apiToken": {
"label": "Service API Token",
"placeholder": "API token for service authentication",
"help": "Token used to authenticate with the desktop service"
}
}
},
"advanced": {
"title": "Advanced Settings",
"performance": {
"title": "Performance",
"subtitle": "Performance and optimization settings",
"coming": "Performance settings will be available in a future update"
},
"logging": {
"title": "Logging",
"subtitle": "Configure application logging behavior",
"coming": "Logging configuration will be available in a future update"
}
},
"dialogs": {
"selectOpenListBinary": "Select OpenList Binary",
"selectRcloneBinary": "Select Rclone Binary",
"selectDataDirectory": "Select Data Directory",
"selectMountDirectory": "Select Mount Directory"
}
},
"logs": {
"title": "Logs",
"clear": "Clear Logs",
"search": {
"placeholder": "Search logs... (Ctrl+F)"
},
@@ -326,14 +222,10 @@
"selected": "{count} selected"
},
"filters": {
"all": "All",
"openlist": "OpenList",
"rclone": "Rclone",
"app": "App",
"labels": {
"level": "Level:",
"source": "Source:",
"search": "Search"
"source": "Source:"
},
"levels": {
"all": "All Levels",
@@ -344,57 +236,36 @@
},
"sources": {
"all": "All Sources",
"service": "Service",
"rclone": "Rclone",
"system": "System",
"openlist": "OpenList"
},
"actions": {
"selectAll": "Select All (Ctrl+A)",
"clearSelection": "Clear Selection (Esc)",
"autoScroll": "Auto-scroll"
},
"searchPlaceholder": "Search logs...",
"clear": "Clear search"
}
},
"levels": {
"debug": "Debug",
"info": "Info",
"warn": "Warning",
"error": "Error"
},
"empty": "No log entries",
"viewer": {
"noLogsFound": "No logs to display",
"noLogsMatch": "No logs match your search criteria",
"tryAdjusting": "Try adjusting your search or filters",
"logsWillAppear": "Logs will appear here when services are running",
"scrollToBottom": "Scroll to bottom"
"logsWillAppear": "Logs will appear here when services are running"
},
"settings": {
"fontSize": "Font Size:",
"maxLines": "Max Lines:",
"showTimestamp": "Show Timestamp",
"showSource": "Show Source",
"compactMode": "Compact Mode",
"stripAnsiColors": "Strip ANSI Colors"
},
"messages": {
"confirmClear": "Are you sure you want to clear all logs?",
"exported": "Logs exported successfully",
"copied": "Logs copied to clipboard"
"confirmClear": "Are you sure you want to clear all logs?"
},
"headers": {
"time": "Time",
"timestamp": "Timestamp",
"level": "Level",
"source": "Source",
"message": "Message"
},
"status": {
"service": "Service:",
"running": "Running",
"stopped": "Stopped",
"autoScroll": "Auto-scroll:",
"updates": "Updates:",
"paused": "Paused",
@@ -425,7 +296,6 @@
"openInExplorer": "Open in Explorer"
},
"status": {
"status": "Status",
"mounted": "Mounted",
"unmounted": "Unmounted",
"error": "Error"
@@ -462,11 +332,7 @@
"addFlag": "Add Flag",
"removeFlag": "Remove Flag",
"types": {
"webdav": "WebDAV",
"s3": "Amazon S3",
"gdrive": "Google Drive",
"onedrive": "OneDrive",
"dropbox": "Dropbox"
"webdav": "WebDAV"
}
},
"meta": {
@@ -486,7 +352,6 @@
"failedToMount": "Failed to mount remote",
"failedToUnmount": "Failed to unmount remote",
"failedToDelete": "Failed to delete configuration",
"failedToRefresh": "Failed to refresh mount status",
"failedToStartService": "Failed to start rclone service",
"failedToStopService": "Failed to stop rclone service"
},
@@ -500,11 +365,6 @@
"title": "OpenList",
"loading": "Initializing OpenList Desktop..."
},
"language": {
"current": "Current Language",
"chinese": "中文",
"english": "English"
},
"tutorial": {
"welcome": {
"title": "Welcome to OpenList Desktop",
@@ -533,8 +393,7 @@
"skip": "Skip Tutorial",
"next": "Next",
"previous": "Previous",
"complete": "Complete Tutorial",
"neverShow": "Don't show again"
"complete": "Complete Tutorial"
},
"update": {
"title": "App Updates",

View File

@@ -32,17 +32,13 @@
"stopRclone": "停止 RClone",
"manageMounts": "管理挂载",
"autoLaunch": "自动启动核心(非桌面app)",
"autoMount": "自动挂载",
"openSettings": "打开设置",
"processing": "处理中...",
"adminPassword": "管理员密码",
"showAdminPassword": "显示/复制日志中的管理员密码"
},
"coreMonitor": {
"title": "核心监控",
"online": "在线",
"offline": "离线",
"uptime": "运行时间",
"responseTime": "响应时间",
"healthy": "健康",
"unhealthy": "不健康"
@@ -51,7 +47,6 @@
"title": "版本管理",
"openlist": "OpenList",
"rclone": "Rclone",
"current": "当前",
"selectVersion": "选择版本",
"update": "更新"
},
@@ -72,16 +67,10 @@
"serviceManagement": {
"title": "服务管理",
"serviceStatus": "OpenList 后端服务",
"uptime": "运行时间",
"port": "端口",
"version": "版本",
"install": "安装",
"start": "启动",
"stop": "停止",
"restart": "重启",
"uninstall": "卸载",
"recentLogs": "最近日志",
"noLogs": "无可用日志",
"status": {
"running": "运行中",
"installed": "已安装",
@@ -101,7 +90,6 @@
"subtitle": "配置您的 OpenList 桌面应用程序",
"saveChanges": "保存更改",
"resetToDefaults": "重置为默认值",
"unsavedChanges": "您有未保存的更改",
"confirmReset": "您确定要将所有设置重置为默认值吗?此操作无法撤消。",
"saved": "设置保存成功!",
"saveFailed": "保存设置失败,请重试。",
@@ -126,20 +114,13 @@
"title": "网络配置",
"subtitle": "配置 Web 界面和网络设置"
},
"storage": {
"title": "存储配置",
"subtitle": "配置数据目录和二进制文件位置"
},
"startup": {
"title": "启动选项",
"subtitle": "配置自动启动行为"
},
"service": {
"title": "服务配置",
"subtitle": "配置 OpenList 服务设置",
"network": {
"title": "网络配置",
"subtitle": "配置 Web 界面和网络设置",
"port": {
"label": "监听端口",
"placeholder": "5244",
@@ -155,23 +136,7 @@
"description": "使用 HTTPS 进行安全通信(需要配置 SSL 证书)"
}
},
"storage": {
"title": "存储配置",
"subtitle": "配置数据目录和二进制文件位置",
"dataDir": {
"label": "数据目录",
"placeholder": "./data",
"help": "OpenList 存储配置和数据库的目录"
},
"binaryPath": {
"label": "OpenList 二进制路径",
"placeholder": "./binary/openlist.exe",
"help": "OpenList 可执行文件的路径"
}
},
"startup": {
"title": "启动选项",
"subtitle": "配置自动启动行为",
"autoLaunch": {
"title": "开机自启",
"description": "应用程序启动时自动启动 OpenList 服务"
@@ -179,48 +144,16 @@
}
},
"rclone": {
"title": "存储配置",
"subtitle": "配置远程存储连接",
"config": {
"title": "远程存储",
"subtitle": "配置 rclone 远程存储访问",
"label": "Rclone 配置JSON)",
"label": "Rclone 配置 (JSON)",
"invalidJson": "无效的 JSON 配置。请检查您的语法。",
"tips": "输入你的JSON配置",
"binaryPath": {
"label": "Rclone 二进制路径",
"placeholder": "./binary/rclone.exe",
"help": "rclone 可执行文件的路径"
},
"configName": {
"label": "配置名称",
"placeholder": "remote",
"help": "rclone 远程配置的名称"
}
},
"mount": {
"title": "挂载配置",
"subtitle": "配置远程存储的本地挂载点",
"mountPath": {
"label": "挂载路径",
"placeholder": "./mount",
"help": "远程存储将挂载到的本地目录"
},
"autoMount": {
"title": "启动时自动挂载",
"description": "服务启动时自动挂载远程存储"
}
},
"flags": {
"title": "Rclone 标志",
"subtitle": "rclone 的额外命令行标志",
"placeholder": "--flag-name=value",
"add": "添加标志",
"remove": "移除"
"tips": "输入你的JSON配置"
}
},
"app": {
"title": "应用设置",
"subtitle": "配置应用程序首选项和行为",
"theme": {
"title": "主题",
@@ -228,8 +161,6 @@
"light": "浅色",
"dark": "深色",
"auto": "自动",
"lightDesc": "浅色主题",
"darkDesc": "深色主题",
"autoDesc": "跟随系统"
},
"monitor": {
@@ -259,45 +190,10 @@
"title": "开机自动启动应用(立即生效)",
"subtitle": "在系统启动时自动启动 OpenList 桌面应用",
"description": "在系统启动时自动启动 OpenList 桌面应用"
},
"service": {
"title": "服务连接",
"subtitle": "配置与桌面服务的连接",
"port": {
"label": "服务端口",
"placeholder": "53211",
"help": "桌面服务通信的端口号"
},
"apiToken": {
"label": "服务 API 令牌",
"placeholder": "API 令牌,用于服务身份验证",
"help": "用于身份验证的令牌"
}
}
},
"advanced": {
"title": "高级设置",
"performance": {
"title": "性能",
"subtitle": "性能和优化设置",
"coming": "性能设置将在未来版本中提供"
},
"logging": {
"title": "日志",
"subtitle": "配置应用程序日志行为",
"coming": "日志配置将在未来版本中提供"
}
},
"dialogs": {
"selectOpenListBinary": "选择 OpenList 二进制文件",
"selectRcloneBinary": "选择 Rclone 二进制文件",
"selectDataDirectory": "选择数据目录",
"selectMountDirectory": "选择挂载目录"
}
},
"logs": {
"title": "日志",
"clear": "清除日志",
"search": {
"placeholder": "搜索日志... (Ctrl+F)"
},
@@ -326,14 +222,10 @@
"selected": "已选择 {count} 个"
},
"filters": {
"all": "全部",
"openlist": "OpenList",
"rclone": "Rclone",
"app": "应用程序",
"labels": {
"level": "级别:",
"source": "来源:",
"search": "搜索"
"source": "来源:"
},
"levels": {
"all": "所有级别",
@@ -344,57 +236,36 @@
},
"sources": {
"all": "所有来源",
"service": "服务",
"rclone": "Rclone",
"system": "系统",
"openlist": "OpenList"
},
"actions": {
"selectAll": "全选 (Ctrl+A)",
"clearSelection": "清除选择 (Esc)",
"autoScroll": "自动滚动"
},
"searchPlaceholder": "搜索日志...",
"clear": "清除搜索"
}
},
"levels": {
"debug": "调试",
"info": "信息",
"warn": "警告",
"error": "错误"
},
"empty": "没有日志条目",
"viewer": {
"noLogsFound": "没有日志显示",
"noLogsMatch": "没有日志匹配您的搜索条件",
"tryAdjusting": "尝试调整您的搜索或过滤器",
"logsWillAppear": "服务运行时日志将在此处显示",
"scrollToBottom": "滚动到底部"
"logsWillAppear": "服务运行时日志将在此处显示"
},
"settings": {
"fontSize": "字体大小:",
"maxLines": "最大行数:",
"showTimestamp": "显示时间戳",
"showSource": "显示来源",
"compactMode": "紧凑模式",
"stripAnsiColors": "去除 ANSI 颜色"
},
"messages": {
"confirmClear": "您确定要清除所有日志吗?",
"exported": "日志导出成功",
"copied": "日志已复制到剪贴板"
"confirmClear": "您确定要清除所有日志吗?"
},
"headers": {
"time": "时间",
"timestamp": "时间",
"level": "级别",
"source": "来源",
"message": "消息"
},
"status": {
"service": "服务:",
"running": "运行中",
"stopped": "已停止",
"autoScroll": "自动滚动:",
"updates": "更新:",
"paused": "已暂停",
@@ -425,7 +296,6 @@
"openInExplorer": "在文件管理器中打开"
},
"status": {
"status": "状态",
"mounted": "已挂载",
"unmounted": "未挂载",
"error": "错误"
@@ -462,11 +332,7 @@
"addFlag": "添加标志",
"removeFlag": "移除标志",
"types": {
"webdav": "WebDAV",
"s3": "Amazon S3",
"gdrive": "Google Drive",
"onedrive": "OneDrive",
"dropbox": "Dropbox"
"webdav": "WebDAV"
}
},
"meta": {
@@ -486,7 +352,6 @@
"failedToMount": "挂载远程失败",
"failedToUnmount": "卸载远程失败",
"failedToDelete": "删除配置失败",
"failedToRefresh": "刷新挂载状态失败",
"failedToStartService": "启动 rclone 服务失败",
"failedToStopService": "停止 rclone 服务失败"
},
@@ -500,11 +365,6 @@
"title": "OpenList",
"loading": "正在初始化"
},
"language": {
"current": "当前语言",
"chinese": "中文",
"english": "English"
},
"tutorial": {
"welcome": {
"title": "欢迎使用 OpenList 桌面版",
@@ -533,8 +393,7 @@
"skip": "跳过教程",
"next": "下一步",
"previous": "上一步",
"complete": "完成教程",
"neverShow": "不再显示"
"complete": "完成教程"
},
"update": {
"title": "应用更新",