mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 03:14:56 +08:00
chore: add workflow to check unused i18n
This commit is contained in:
213
.github/workflows/i18n-check.yml
vendored
Normal file
213
.github/workflows/i18n-check.yml
vendored
Normal 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
|
||||
@@ -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
288
scripts/find-unused-i18n.js
Normal 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()
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "应用更新",
|
||||
|
||||
Reference in New Issue
Block a user