diff --git a/.config/quickshell/translations/en_US.json b/.config/quickshell/translations/en_US.json index 6f7c8ec50..34887b99b 100644 --- a/.config/quickshell/translations/en_US.json +++ b/.config/quickshell/translations/en_US.json @@ -35,7 +35,6 @@ "Closes right sidebar on press": "Closes right sidebar on press", "Copy": "Copy", "Copy code": "Copy code", - "Ctrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Ctrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window", "Current API endpoint: {0}\nSet it with {1}mode PROVIDER": "Current API endpoint: {0}\nSet it with {1}mode PROVIDER", "Current model: {0}\nSet it with {1}model MODEL": "Current model: {0}\nSet it with {1}model MODEL", "Decrease brightness": "Decrease brightness", @@ -48,7 +47,6 @@ "Download complete": "Download complete", "Edit": "Edit", "Enter text to translate...": "Enter text to translate...", - "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly", "Finished tasks will go here": "Finished tasks will go here", "For desktop wallpapers | Good quality": "For desktop wallpapers | Good quality", "For storing API keys and other sensitive information": "For storing API keys and other sensitive information", @@ -63,10 +61,7 @@ "Input": "Input", "Intelligence": "Intelligence", "Interface": "Interface", - "Interrupts possibility of overview being toggled on release. ": "Interrupts possibility of overview being toggled on release. ", - "Invalid API provider. Supported: \n- ": "Invalid API provider. Supported: \n- ", "Invalid arguments. Must provide `key` and `value`.": "Invalid arguments. Must provide `key` and `value`.", - "Invalid model. Supported: \n```\n": "Invalid model. Supported: \n```\n", "Jump to current month": "Jump to current month", "Keep system awake": "Keep system awake", "Large images | God tier quality, no NSFW.": "Large images | God tier quality, no NSFW.", @@ -86,8 +81,6 @@ "Nothing here!": "Nothing here!", "Notifications": "Notifications", "OK": "OK", - "Online via {0} | {1}'s model": "Online via {0} | {1}'s model", - "Online | Google's model\nGives up-to-date information with search.": "Online | Google's model\nGives up-to-date information with search.", "Open file link": "Open file link", "Opens cheatsheet on press": "Opens cheatsheet on press", "Opens left sidebar on press": "Opens left sidebar on press", @@ -98,7 +91,6 @@ "Output": "Output", "Page {0}": "Page {0}", "Plasma Settings": "Plasma Settings", - "Provider set to ": "Provider set to ", "Reboot": "Reboot", "Reboot to firmware settings": "Reboot to firmware settings", "Reload Hyprland & Quickshell": "Reload Hyprland & Quickshell", @@ -122,19 +114,15 @@ "Shutdown": "Shutdown", "Silent": "Silent", "Sleep": "Sleep", - "Switched to search mode. Continue with the user's request.": "Switched to search mode. Continue with the user's request.", "System": "System", "Task Manager": "Task Manager", "Task description": "Task description", "Temperature must be between 0 and 2": "Temperature must be between 0 and 2", "Temperature set to {0}": "Temperature set to {0}", "Temperature: {0}": "Temperature: {0}", - "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number", - "The current API used. Endpoint: ": "The current API used. Endpoint: ", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "The hentai one | Great quantity, a lot of NSFW, quality varies wildly", "The popular one | Best quantity, but quality can vary wildly": "The popular one | Best quantity, but quality can vary wildly", "Thinking": "Thinking", - "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ": "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ", "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.": "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.", "Toggle clipboard query on overview widget": "Toggle clipboard query on overview widget", "Toggle emoji query on overview widget": "Toggle emoji query on overview widget", @@ -155,318 +143,34 @@ "Unknown Album": "Unknown Album", "Unknown Artist": "Unknown Artist", "Unknown Title": "Unknown Title", - "Unknown command: ": "Unknown command: ", "Unknown function call: {0}": "Unknown function call: {0}", "Uptime: {0}": "Uptime: {0}", - "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in **bold** to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.": "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in **bold** to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.", "View Markdown source": "View Markdown source", "Volume": "Volume", "Volume mixer": "Volume mixer", "Waifus only | Excellent quality, limited quantity": "Waifus only | Excellent quality, limited quantity", "Waiting for response...": "Waiting for response...", "Workspace": "Workspace", - "\nSet with /mode PROVIDER": "\nSet with /mode PROVIDER", - "about": "About", - "accessed": "Accessed", - "account": "Account", - "active": "Active", - "active_state": "Active", - "addon": "Add-on", - "addons": "Add-ons", - "address": "Address", - "admin": "Administrator", - "align": "Align", - "always_on_top": "Always on Top", - "animate": "Animate", - "app": "App", - "appearance": "Appearance", - "application": "Application", - "applications": "Applications", - "apply": "Apply", - "apps": "Apps", - "auto": "Auto", - "background": "Background", - "balanced": "Balanced", - "bar": "Bar", - "bars": "Bars", - "battery": "Battery", - "bluetooth": "Bluetooth", - "blur": "Blur", - "border": "Border", - "bottom": "Bottom", - "brightness": "Brightness", - "bring_to_front": "Bring to Front", - "bytes": "{0} bytes", - "cancel": "Cancel", - "center": "Center", - "characters": "{0} characters", - "charging": "Charging", - "clear_all": "Clear All", - "clicked": "Clicked", - "close": "Close", - "collapse": "Collapse", - "color": "Color", - "command": "Command", - "connected": "Connected", - "connecting": "Connecting...", - "context_menu": "Context Menu", - "copy": "Copy", - "cpu": "CPU", - "created": "Created", - "critical": "Critical", - "customize": "Customize", - "cut": "Cut", - "dark": "Dark", - "dashboard": "Dashboard", - "date": "Date", - "debug": "Debug", - "default": "Default", - "degrees": "{0}°", - "delete": "Delete", - "demo": "Demo", - "desktop": "Desktop", - "dialog": "Dialog", - "disabled": "Disabled", - "discharging": "Discharging", - "disconnected": "Disconnected", - "disk": "Disk", - "display": "Display", - "distribute": "Distribute", - "dock": "Dock", - "documents": "Documents", - "double_clicked": "Double Clicked", - "downloads": "Downloads", - "dragged": "Dragged", - "dropdown": "Dropdown", - "dropped": "Dropped", - "effect": "Effect", - "email": "Email", - "enabled": "Enabled", - "error": "Error", - "ethernet": "Ethernet", - "example": "Example", - "execute": "Execute", - "exit": "Exit", - "expand": "Expand", - "extension": "Extension", - "extensions": "Extensions", - "fan": "Fan", - "favorites": "Favorites", - "file": "File", - "files": "Files", - "filter": "Filter", - "flip": "Flip", - "floating": "Floating", - "focus": "Focus", - "folder": "Folder", - "folders": "Folders", - "foreground": "Foreground", - "format": "Format", - "full": "Full", - "fullscreen": "Fullscreen", - "gigabytes": "{0} GB", - "gigahertz": "{0} GHz", - "glow": "Glow", - "group": "Group", - "guest": "Guest", - "headphones": "Headphones", - "help": "Help", - "hertz": "{0} Hz", - "hibernate": "Hibernate", - "hidden": "Hidden", - "hide": "Hide", - "highlight": "Highlight", - "hint": "Hint", - "home": "Home", - "hour": "Hour", - "hours": "Hours", - "hover": "Hover", - "inactive": "Inactive", - "info": "Info", - "input": "Input", - "install": "Install", - "justify": "Justify", - "keybinds": "Key Bindings", - "keyboard": "Keyboard", - "kilobytes": "{0} KB", - "kilohertz": "{0} kHz", - "landscape": "Landscape", - "language": "Language", - "launcher": "Launcher", - "left": "Left", - "light": "Light", - "loading": "Loading...", - "lock": "Lock", - "log": "Log", - "logout": "Logout", - "long_pressed": "Long Pressed", - "low": "Low", - "manual": "Manual", - "margin": "Margin", - "maximize": "Maximize", - "megabytes": "{0} MB", - "megahertz": "{0} MHz", - "memory": "Memory", - "menu": "Menu", - "menubar": "Menu Bar", - "microphone": "Microphone", - "minimize": "Minimize", - "minute": "Minute", - "minutes": "Minutes", - "mirror": "Mirror", - "modal": "Modal", - "modified": "Modified", - "monitor": "Monitor", - "mouse": "Mouse", - "move": "Move", - "music": "Music", - "mute": "Mute", - "network": "Network", - "next": "Next", - "no": "No", - "no_notifications": "No notifications", - "notifications": "Notifications", - "off": "Off", - "ok": "OK", - "on": "On", - "opacity": "Opacity", - "open": "Open", - "orientation": "Orientation", - "outline": "Outline", - "output": "Output", - "overview": "Overview", - "owner": "Owner", - "package": "Package", - "packages": "Packages", - "padding": "Padding", - "panel": "Panel", - "panels": "Panels", - "panned": "Panned", - "password": "Password", - "paste": "Paste", - "pause": "Pause", - "percent": "{0}%", - "performance": "Performance", - "permissions": "Permissions", - "phone": "Phone", - "pictures": "Pictures", - "pinched": "Pinched", - "pinned": "Pinned", - "pixels": "{0}px", - "placeholder": "Placeholder", - "play": "Play", - "plugin": "Plugin", - "plugins": "Plugins", - "popup": "Popup", - "portrait": "Portrait", - "power": "Power", - "power_saver": "Power Saver", - "pressed": "Pressed", - "previous": "Previous", - "profile": "Profile", - "properties": "Properties", - "quiet": "Quiet", - "quit": "Quit", - "read": "Read", - "recent": "Recent", - "recording": "Recording", - "refresh": "Refresh", - "refresh_rate": "Refresh Rate", - "reload": "Reload", - "rename": "Rename", - "reset": "Reset", - "resize": "Resize", - "resolution": "Resolution", - "restart": "Restart", - "restore": "Restore", - "right": "Right", - "right_clicked": "Right Clicked", - "rotate": "Rotate", - "rotated": "Rotated", - "sample": "Sample", - "save": "Save", - "scale": "Scale", - "screenshot": "Screenshot", - "scrolled": "Scrolled", - "search": "Search", - "second": "Second", - "seconds": "Seconds", - "selection": "Selection", - "send_to_back": "Send to Back", - "settings": "Settings", - "shadow": "Shadow", - "shortcuts": "Shortcuts", - "show": "Show", - "shutdown": "Shutdown", - "sidebar": "Sidebar", - "silent": "Silent", - "size": "Size", - "snapped": "Snapped", - "software": "Software", - "space": "Space", - "speaker": "Speaker", - "statusbar": "Status Bar", - "sticky": "Sticky", - "stop": "Stop", - "style": "Style", - "success": "Success", - "suspend": "Suspend", - "swiped": "Swiped", - "systray": "System Tray", - "tapped": "Tapped", - "taskbar": "Taskbar", - "temperature": "Temperature", - "terabytes": "{0} TB", - "terminal": "Terminal", - "test": "Test", - "theme": "Theme", - "tiled": "Tiled", - "time": "Time", - "tips": "Tips", - "today": "Today", - "tomorrow": "Tomorrow", - "toolbar": "Toolbar", - "tooltip": "Tooltip", - "top": "Top", - "touchpad": "Touchpad", - "trace": "Trace", - "transform": "Transform", - "transition": "Transition", - "transparency": "Transparency", - "trash": "Trash", - "tutorial": "Tutorial", - "type": "Type", - "uninstall": "Uninstall", - "unlock": "Unlock", - "unmute": "Unmute", - "unpinned": "Unpinned", - "unsticky": "Unsticky", - "update": "Update", - "upgrade": "Upgrade", - "user": "User", - "username": "Username", - "verbose": "Verbose", - "version": "Version", - "videos": "Videos", - "visible": "Visible", - "volume": "Volume", - "warning": "Warning", - "welcome": "Welcome", - "widget": "Widget", - "widgets": "Widgets", - "wifi": "Wi-Fi", - "window": "Window", - "windowed": "Windowed", - "windows": "Windows", - "workspace": "Workspace", - "workspaces": "Workspaces", - "write": "Write", - "yes": "Yes", - "yesterday": "Yesterday", - "zoomed": "Zoomed", "{0} (copied)": "{0} (copied)", "{0} Safe Storage": "{0} Safe Storage", "{0} does not require an API key": "{0} does not require an API key", "{0} queries pending": "{0} queries pending", - "{0} | Right-click to configure": "{0} | Right-click to configure" + "{0} | Right-click to configure": "{0} | Right-click to configure", + "Set with /mode PROVIDER": "Set with /mode PROVIDER", + "Invalid API provider. Supported: \n-": "Invalid API provider. Supported: \n-", + "Unknown command:": "Unknown command:", + "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window", + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key.": "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key.", + "The current API used. Endpoint:": "The current API used. Endpoint:", + "Provider set to": "Provider set to", + "Invalid model. Supported: \n```": "Invalid model. Supported: \n```", + "Interrupts possibility of overview being toggled on release.": "Interrupts possibility of overview being toggled on release.", + "Enter tags, or \"{0}\" for commands": "Enter tags, or \"{0}\" for commands", + "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number", + "Online | Google's model\nGives up-to-date information with search.": "Online | Google's model\nGives up-to-date information with search.", + "Online via {0} | {1}'s model": "Online via {0} | {1}'s model", + "Switched to search mode. Continue with the user's request.": "Switched to search mode. Continue with the user's request.", + "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly", + "Message the model... \"{0}\" for commands": "Message the model... \"{0}\" for commands", + "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}": "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}" } \ No newline at end of file diff --git a/.config/quickshell/translations/tools/README.md b/.config/quickshell/translations/tools/README.md new file mode 100644 index 000000000..1840f6c3d --- /dev/null +++ b/.config/quickshell/translations/tools/README.md @@ -0,0 +1,111 @@ +# Translation Management Tools + +This directory contains a toolset for managing project translation files. + +## Directory Structure + +``` +translations/ +├── tools/ # Translation management tools directory +│ ├── translation-manager.py # Main translation manager +│ ├── translation-cleaner.py # Translation maintenance tool +│ ├── manage-translations.sh # Convenient wrapper script +│ ├── translation-tools-guide.md # Detailed usage documentation +│ └── README.md # This file +├── en_US.json # English translation file +├── zh_CN.json # Chinese translation file +└── ... # Other language files +``` + +## Quick Start + +### Running from tools directory + +```bash +# Enter tools directory +cd .config/quickshell/translations/tools + +# Check current translation status +./manage-translations.sh status + +# Update all translation files +./manage-translations.sh update + +# Update specific language +./manage-translations.sh update -l zh_CN + +# Clean unused keys +./manage-translations.sh clean + +# Sync all language files +./manage-translations.sh sync +``` + +### Running from project root directory + +```bash +# Run from project root directory (recommended to use relative paths) +.config/quickshell/translations/tools/manage-translations.sh status +.config/quickshell/translations/tools/manage-translations.sh update +``` + +## Tool Description + +### 🛠️ `manage-translations.sh` - Main Entry Point +Convenient command-line interface that integrates all translation management functions. + +### 🔍 `translation-manager.py` - Core Manager +- Extract translatable texts +- Compare translation file differences +- Interactive translation updates + +### 🧹 `translation-cleaner.py` - Maintenance Tool +- Clean unused translation keys +- Sync language file structure +- Create backup files + +## Common Workflows + +### After adding new translatable texts +```bash +./manage-translations.sh update +``` + +### Clean up after code refactoring +```bash +./manage-translations.sh clean +``` + +### Add new language +```bash +./manage-translations.sh update -l new_language_code +``` + +### Check translation status +```bash +./manage-translations.sh status +``` + +## Documentation + +- 📖 [Detailed Usage Guide](./translation-tools-guide.md) + +## Important Notes + +1. **Running Location**: Tools automatically detect relative paths, can be run from tools directory or project root +2. **Backup**: Cleanup operations automatically create backup files +3. **Encoding**: All files use UTF-8 encoding +4. **Permissions**: Ensure scripts have execution permissions + +## Supported Translation Formats + +The tool recognizes translatable texts in the following formats: +```qml +qsTr("Your text here") +qsTr('Single quotes work too') +i18n.t("JavaScript translations") +``` + +--- + +If you have any issues, please refer to the detailed documentation or check error messages in script output. diff --git a/.config/quickshell/translations/tools/manage-translations.sh b/.config/quickshell/translations/tools/manage-translations.sh new file mode 100755 index 000000000..c20896a93 --- /dev/null +++ b/.config/quickshell/translations/tools/manage-translations.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# Translation management script - convenient wrapper + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TRANSLATIONS_DIR="$(dirname "$SCRIPT_DIR")" +SOURCE_DIR="$(dirname "$(dirname "$TRANSLATIONS_DIR")")" + +show_help() { + echo "Translation Management Tool - Convenient Wrapper" + echo "" + echo "Usage: $0 [options] " + echo "" + echo "Commands:" + echo " extract Extract translatable texts to temporary file" + echo " update Update translation files (add missing/remove extra keys)" + echo " clean Clean unused translation keys" + echo " sync Sync keys across all language files" + echo " status Show translation status" + echo "" + echo "Options:" + echo " -l, --lang LANG Specify language (e.g.: zh_CN)" + echo " -t, --trans-dir DIR Translation files directory (default: $TRANSLATIONS_DIR)" + echo " -s, --source-dir DIR Source code directory (default: $SOURCE_DIR)" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 extract # Extract translatable texts" + echo " $0 update -l zh_CN # Update Chinese translations" + echo " $0 update # Update all translations" + echo " $0 clean # Clean unused keys" + echo " $0 sync # Sync keys across all languages" + echo " $0 status # Show translation status" +} + +show_status() { + echo "Analyzing translation status..." + + # Extract current text count + echo "=== Current Project Status ===" + python3 "$SCRIPT_DIR/translation-manager.py" \ + --translations-dir "$TRANSLATIONS_DIR" \ + --source-dir "$SOURCE_DIR" \ + --extract-only | grep "Extracted" + + echo "" + echo "=== Translation File Status ===" + + if [ -d "$TRANSLATIONS_DIR" ]; then + for file in "$TRANSLATIONS_DIR"/*.json; do + if [ -f "$file" ]; then + lang=$(basename "$file" .json) + count=$(jq 'length' "$file" 2>/dev/null || echo "error") + echo " $lang: $count keys" + fi + done + else + echo " Translation directory does not exist: $TRANSLATIONS_DIR" + fi +} + +# Parse command line arguments +LANG_CODE="" +COMMAND="" + +while [[ $# -gt 0 ]]; do + case $1 in + -l|--lang) + LANG_CODE="$2" + shift 2 + ;; + -t|--trans-dir) + TRANSLATIONS_DIR="$2" + shift 2 + ;; + -s|--source-dir) + SOURCE_DIR="$2" + shift 2 + ;; + -h|--help) + show_help + exit 0 + ;; + extract|update|clean|sync|status) + if [ -n "$COMMAND" ]; then + echo "Error: Only one command can be specified" + exit 1 + fi + COMMAND="$1" + shift + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac +done + +if [ -z "$COMMAND" ]; then + echo "Error: A command must be specified" + show_help + exit 1 +fi + +# Check dependencies +if ! command -v python3 >/dev/null 2>&1; then + echo "Error: python3 is required" + exit 1 +fi + +if [ "$COMMAND" = "status" ] && ! command -v jq >/dev/null 2>&1; then + echo "Warning: jq is not installed, status display may be incomplete" +fi + +# Build base arguments +BASE_ARGS="--translations-dir $TRANSLATIONS_DIR --source-dir $SOURCE_DIR" + +case $COMMAND in + extract) + echo "Extracting translatable texts..." + python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS --extract-only --show-temp + ;; + update) + echo "Updating translation files..." + if [ -n "$LANG_CODE" ]; then + python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS --language "$LANG_CODE" + else + python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS + fi + ;; + clean) + echo "Cleaning unused translation keys..." + python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS --clean + ;; + sync) + echo "Syncing translation keys..." + python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS --sync + ;; + status) + show_status + ;; + *) + echo "Unknown command: $COMMAND" + show_help + exit 1 + ;; +esac diff --git a/.config/quickshell/translations/tools/translation-cleaner.py b/.config/quickshell/translations/tools/translation-cleaner.py new file mode 100755 index 000000000..e53082fda --- /dev/null +++ b/.config/quickshell/translations/tools/translation-cleaner.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Translation File Maintenance Helper +Used to clean and organize translation files, removing unused keys +""" + +import os +import sys +import json +import argparse +import importlib.util +from pathlib import Path +from typing import Dict, Set, List + +# Import from the same directory using importlib +current_dir = os.path.dirname(os.path.abspath(__file__)) +manager_path = os.path.join(current_dir, 'translation-manager.py') +spec = importlib.util.spec_from_file_location("translation_manager", manager_path) +translation_manager = importlib.util.module_from_spec(spec) +spec.loader.exec_module(translation_manager) +TranslationManager = translation_manager.TranslationManager + +def clean_translation_files(translations_dir: str, source_dir: str, backup: bool = True): + """Clean translation files by removing unused keys""" + print("Starting translation file cleanup...") + + # Create manager + manager = TranslationManager(translations_dir, source_dir) + + # Extract currently used texts + print("Extracting currently used translatable texts...") + current_texts = manager.extract_translatable_texts() + print(f"Extracted {len(current_texts)} currently used texts") + + # Get all language files + languages = manager.get_available_languages() + if not languages: + print("No translation files found") + return + + print(f"Found language files: {', '.join(languages)}") + + total_removed = 0 + + for lang in languages: + print(f"\nProcessing language: {lang}") + + # Load translation file + translations = manager.load_translation_file(lang) + original_count = len(translations) + + if backup: + # Create backup + backup_file = Path(translations_dir) / f"{lang}.json.backup" + with open(backup_file, 'w', encoding='utf-8') as f: + json.dump(translations, f, ensure_ascii=False, indent=2) + print(f"Created backup: {backup_file}") + + # Find unused keys + unused_keys = set(translations.keys()) - current_texts + + if unused_keys: + print(f"Found {len(unused_keys)} unused keys:") + for i, key in enumerate(sorted(unused_keys)[:10], 1): # Only show first 10 + print(f" {i}. \"{key[:50]}{'...' if len(key) > 50 else ''}\"") + if len(unused_keys) > 10: + print(f" ... and {len(unused_keys) - 10} more keys") + + response = input(f"Delete these {len(unused_keys)} unused keys? (y/n): ") + if response.lower().strip() in ['y', 'yes']: + # Delete unused keys + for key in unused_keys: + del translations[key] + + # Save cleaned file + manager.save_translation_file(lang, translations) + removed_count = len(unused_keys) + total_removed += removed_count + print(f"Deleted {removed_count} keys") + else: + print("Skipped deletion") + else: + print("No unused keys found") + + new_count = len(translations) + print(f"Original key count: {original_count}, after cleanup: {new_count}") + + print(f"\nCleanup completed! Total deleted {total_removed} unused keys.") + +def sync_translations(translations_dir: str, source_lang: str = "en_US", target_langs: List[str] = None): + """Sync translation keys to ensure all language files have the same keys""" + print(f"Starting translation key sync using {source_lang} as reference...") + + translations_path = Path(translations_dir) + + # Load source language file + source_file = translations_path / f"{source_lang}.json" + if not source_file.exists(): + print(f"Error: Source language file does not exist: {source_file}") + return + + with open(source_file, 'r', encoding='utf-8') as f: + source_translations = json.load(f) + + source_keys = set(source_translations.keys()) + print(f"Source language {source_lang} has {len(source_keys)} keys") + + # Get target language list + if target_langs is None: + target_langs = [] + for file_path in translations_path.glob("*.json"): + lang_code = file_path.stem + if lang_code != source_lang: + target_langs.append(lang_code) + + if not target_langs: + print("No target language files found") + return + + print(f"Target languages: {', '.join(target_langs)}") + + for target_lang in target_langs: + print(f"\nSyncing language: {target_lang}") + + target_file = translations_path / f"{target_lang}.json" + if target_file.exists(): + with open(target_file, 'r', encoding='utf-8') as f: + target_translations = json.load(f) + else: + target_translations = {} + + target_keys = set(target_translations.keys()) + + # Find missing and extra keys + missing_keys = source_keys - target_keys + extra_keys = target_keys - source_keys + + print(f" Missing keys: {len(missing_keys)}") + print(f" Extra keys: {len(extra_keys)}") + + # Add missing keys + if missing_keys: + for key in missing_keys: + # Use source language value as placeholder by default + target_translations[key] = source_translations[key] + print(f" Added {len(missing_keys)} missing keys") + + # Ask whether to delete extra keys + if extra_keys: + response = input(f" Delete {len(extra_keys)} extra keys? (y/n): ") + if response.lower().strip() in ['y', 'yes']: + for key in extra_keys: + del target_translations[key] + print(f" Deleted {len(extra_keys)} extra keys") + + # Save file + with open(target_file, 'w', encoding='utf-8') as f: + json.dump(target_translations, f, ensure_ascii=False, indent=2) + + print(f" Saved: {target_file}") + +def main(): + parser = argparse.ArgumentParser(description="Translation File Maintenance Helper") + parser.add_argument("--translations-dir", "-t", + default=".config/quickshell/translations", + help="Translation files directory") + parser.add_argument("--source-dir", "-s", + default=".config/quickshell", + help="Source code directory") + parser.add_argument("--clean", "-c", action="store_true", + help="Clean unused translation keys") + parser.add_argument("--sync", action="store_true", + help="Sync translation keys") + parser.add_argument("--source-lang", default="en_US", + help="Source language for syncing (default: en_US)") + parser.add_argument("--no-backup", action="store_true", + help="Do not create backup files when cleaning") + + args = parser.parse_args() + + # Convert to absolute paths + translations_dir = os.path.abspath(args.translations_dir) + source_dir = os.path.abspath(args.source_dir) + + if args.clean: + clean_translation_files(translations_dir, source_dir, backup=not args.no_backup) + elif args.sync: + sync_translations(translations_dir, args.source_lang) + else: + print("Please specify an operation:") + print(" --clean: Clean unused translation keys") + print(" --sync: Sync translation keys") + +if __name__ == "__main__": + main() diff --git a/.config/quickshell/translations/tools/translation-manager.py b/.config/quickshell/translations/tools/translation-manager.py new file mode 100755 index 000000000..ffcb26092 --- /dev/null +++ b/.config/quickshell/translations/tools/translation-manager.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Translation File Management Script +Used to update and extract translatable texts, manage JSON translation file key comparison +""" + +import os +import json +import re +import sys +import argparse +from pathlib import Path +from typing import Dict, Set, List, Tuple +import tempfile +import subprocess + +class TranslationManager: + def __init__(self, translations_dir: str, source_dir: str): + self.translations_dir = Path(translations_dir) + self.source_dir = Path(source_dir) + self.temp_extracted_file = None + + # Ensure translation directory exists + self.translations_dir.mkdir(parents=True, exist_ok=True) + + def extract_translatable_texts(self) -> Set[str]: + """Extract translatable texts from source code""" + translatable_texts = set() + + # Search patterns: Translation.tr("text") or Translation.tr('text') + # Improved regex that handles nested quotes correctly + patterns = [ + r'Translation\.tr\s*\(\s*(["\'])(((?!\1)[^\\]|\\.)*)(\1)\s*\)', # Double or single quotes with escape support + r'Translation\.tr\s*\(\s*`([^`]*(?:\\.[^`]*)*?)`\s*\)', # Backticks (template strings) + ] + + # Search all .qml and .js files + file_extensions = ['*.qml', '*.js'] + + for ext in file_extensions: + for file_path in self.source_dir.rglob(ext): + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + for pattern in patterns: + matches = re.findall(pattern, content, re.MULTILINE | re.DOTALL) + for match in matches: + # Handle different match group structures + if isinstance(match, tuple): + # For improved regex, text is in the second group + if len(match) >= 3: + text = match[1] # Second group is the text content + else: + text = match[0] if match else "" + else: + text = match + + # Decode escape characters + try: + clean_text = text.encode().decode('unicode_escape') + except: + clean_text = text + + # Clean text (remove extra whitespace) + clean_text = clean_text.strip() + if clean_text: + translatable_texts.add(clean_text) + + except (UnicodeDecodeError, IOError) as e: + print(f"Warning: Cannot read file {file_path}: {e}") + + return translatable_texts + + def create_temp_translation_file(self, texts: Set[str]) -> str: + """Create temporary JSON file containing extracted texts""" + temp_data = {} + for text in sorted(texts): + temp_data[text] = text # Key and value are the same, indicating untranslated + + # Create temporary file + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f: + json.dump(temp_data, f, ensure_ascii=False, indent=2) + self.temp_extracted_file = f.name + + return self.temp_extracted_file + + def load_translation_file(self, lang_code: str) -> Dict[str, str]: + """Load translation file for specified language""" + file_path = self.translations_dir / f"{lang_code}.json" + if file_path.exists(): + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError) as e: + print(f"Warning: Cannot load translation file {file_path}: {e}") + return {} + return {} + + def save_translation_file(self, lang_code: str, translations: Dict[str, str]): + """Save translation file""" + file_path = self.translations_dir / f"{lang_code}.json" + try: + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(translations, f, ensure_ascii=False, indent=2) + print(f"Translation file saved: {file_path}") + except IOError as e: + print(f"Error: Cannot save translation file {file_path}: {e}") + + def get_available_languages(self) -> List[str]: + """Get list of available languages""" + languages = [] + for file_path in self.translations_dir.glob("*.json"): + lang_code = file_path.stem + languages.append(lang_code) + return sorted(languages) + + def compare_translations(self, extracted_texts: Set[str], target_lang: str) -> Tuple[Set[str], Set[str]]: + """Compare extracted texts with existing translation file""" + existing_translations = self.load_translation_file(target_lang) + existing_keys = set(existing_translations.keys()) + + missing_keys = extracted_texts - existing_keys # Missing keys + extra_keys = existing_keys - extracted_texts # Extra keys + + return missing_keys, extra_keys + + def interactive_update(self, lang_code: str, missing_keys: Set[str], extra_keys: Set[str]): + """Interactively update translation file""" + translations = self.load_translation_file(lang_code) + modified = False + + # Handle missing keys + if missing_keys: + print(f"\nFound {len(missing_keys)} missing translation keys:") + for i, key in enumerate(sorted(missing_keys), 1): + print(f"{i}. \"{key}\"") + + if self.ask_yes_no(f"\nAdd these {len(missing_keys)} missing keys?"): + for key in missing_keys: + translations[key] = key # Default value is the key itself + modified = True + print(f"Added {len(missing_keys)} keys") + + # Handle extra keys + if extra_keys: + print(f"\nFound {len(extra_keys)} extra translation keys:") + for i, key in enumerate(sorted(extra_keys), 1): + print(f"{i}. \"{key}\" -> \"{translations.get(key, '')}\"") + + if self.ask_yes_no(f"\nDelete these {len(extra_keys)} extra keys?"): + for key in extra_keys: + if key in translations: + del translations[key] + modified = True + print(f"Deleted {len(extra_keys)} keys") + + # Save changes + if modified: + self.save_translation_file(lang_code, translations) + else: + print("No changes made") + + def ask_yes_no(self, question: str) -> bool: + """Ask user for confirmation""" + while True: + response = input(f"{question} (y/n): ").lower().strip() + if response in ['y', 'yes']: + return True + elif response in ['n', 'no']: + return False + else: + print("Please enter y/yes or n/no") + + def cleanup(self): + """Clean up temporary files""" + if self.temp_extracted_file and os.path.exists(self.temp_extracted_file): + os.unlink(self.temp_extracted_file) + +def main(): + parser = argparse.ArgumentParser(description="Translation file management tool") + parser.add_argument("--translations-dir", "-t", + default=".config/quickshell/translations", + help="Translation files directory (default: .config/quickshell/translations)") + parser.add_argument("--source-dir", "-s", + default=".config/quickshell", + help="Source code directory (default: .config/quickshell)") + parser.add_argument("--language", "-l", + help="Specify language code to process (e.g., zh_CN)") + parser.add_argument("--extract-only", "-e", action="store_true", + help="Only extract translatable texts to temporary file") + parser.add_argument("--show-temp", action="store_true", + help="Show temporary extracted file content") + + args = parser.parse_args() + + # Convert to absolute paths + translations_dir = os.path.abspath(args.translations_dir) + source_dir = os.path.abspath(args.source_dir) + + print(f"Translation directory: {translations_dir}") + print(f"Source code directory: {source_dir}") + + # Check if directories exist + if not os.path.exists(source_dir): + print(f"Error: Source code directory does not exist: {source_dir}") + sys.exit(1) + + # Create manager + manager = TranslationManager(translations_dir, source_dir) + + try: + # Extract translatable texts + print("\nExtracting translatable texts...") + extracted_texts = manager.extract_translatable_texts() + print(f"Extracted {len(extracted_texts)} translatable texts") + + # Create temporary file + temp_file = manager.create_temp_translation_file(extracted_texts) + print(f"Created temporary file: {temp_file}") + + if args.show_temp: + print("\nTemporary file contents:") + with open(temp_file, 'r', encoding='utf-8') as f: + print(f.read()) + + if args.extract_only: + print("Extract-only mode, program finished") + return + + # Get available languages + available_languages = manager.get_available_languages() + + if args.language: + target_languages = [args.language] + else: + print(f"\nAvailable languages: {', '.join(available_languages) if available_languages else 'None'}") + + if not available_languages: + print("No existing translation files found") + lang_input = input("Enter language code to create (e.g.: zh_CN): ").strip() + if lang_input: + target_languages = [lang_input] + else: + print("No language specified, program finished") + return + else: + print("Choose language to process:") + for i, lang in enumerate(available_languages, 1): + print(f"{i}. {lang}") + print("a. Process all languages") + + choice = input("Please choose (enter number, language code, or 'a'): ").strip() + + if choice.lower() == 'a': + target_languages = available_languages + elif choice.isdigit() and 1 <= int(choice) <= len(available_languages): + target_languages = [available_languages[int(choice) - 1]] + elif choice in available_languages: + target_languages = [choice] + else: + print("Invalid choice, program finished") + return + + # Process each language + for lang in target_languages: + print(f"\n{'='*50}") + print(f"Processing language: {lang}") + print('='*50) + + missing_keys, extra_keys = manager.compare_translations(extracted_texts, lang) + + if not missing_keys and not extra_keys: + print(f"Translation file for language {lang} is already up to date") + continue + + print(f"Analysis results:") + print(f" Missing keys: {len(missing_keys)}") + print(f" Extra keys: {len(extra_keys)}") + + if missing_keys or extra_keys: + manager.interactive_update(lang, missing_keys, extra_keys) + + finally: + # Clean up temporary files + manager.cleanup() + +if __name__ == "__main__": + main() diff --git a/.config/quickshell/translations/tools/translation-tools-guide.md b/.config/quickshell/translations/tools/translation-tools-guide.md new file mode 100644 index 000000000..527576329 --- /dev/null +++ b/.config/quickshell/translations/tools/translation-tools-guide.md @@ -0,0 +1,246 @@ +# Translation Management Tool Suite + +This tool suite is used to manage project translation files, automatically extract translatable texts, compare differences between different language files, and provide maintenance functions. + +## Tool Components + +### 1. `translation-manager.py` - Main Translation Manager +- Extract translatable texts +- Compare and update translation files +- Interactive adding/removing translation keys + +### 2. `translation-cleaner.py` - Translation File Maintenance Tool +- Clean unused translation keys +- Sync key structure across different language files + +### 3. `manage-translations.sh` - Convenient Wrapper Script +- Provide unified command-line interface +- Display translation status +- Simplify common operations + +## Quick Start + +### Check Translation Status +```bash +./manage-translations.sh status +``` + +### Extract Translatable Texts +```bash +./manage-translations.sh extract +``` + +### Update Translation Files +```bash +# Update all languages +./manage-translations.sh update + +# Update specific language +./manage-translations.sh update -l zh_CN +``` + +### Clean Unused Keys +```bash +./manage-translations.sh clean +``` + +### Sync Keys Across Languages +```bash +./manage-translations.sh sync +``` + +## Detailed Usage + +### translation-manager.py + +The main translation management tool that extracts translatable texts from source code and manages translation files. + +#### Command Line Options +```bash +python3 translation-manager.py [options] + +Options: + -h, --help Show help message + -t, --translations-dir DIR Translation files directory (default: .config/quickshell/translations) + -s, --source-dir DIR Source code directory (default: .config/quickshell) + -l, --language LANG Specify language code to process (e.g., zh_CN) + -e, --extract-only Only extract translatable texts to temporary file + --show-temp Show temporary extracted file content +``` + +#### Features +1. **Text Extraction**: Uses regex patterns to extract translatable texts from QML and JavaScript files +2. **Smart Filtering**: Automatically removes duplicates and cleans up extracted texts +3. **Interactive Updates**: Guides users through adding missing keys and removing extra ones +4. **Backup Support**: Creates backups before making changes + +#### Supported Text Patterns +- `qsTr("text")` and `qsTr('text')` +- `i18n.t("text")` and `i18n.t('text')` +- Supports nested quotes and escape characters +- Handles multiline strings + +### translation-cleaner.py + +A maintenance tool for cleaning up and synchronizing translation files. + +#### Command Line Options +```bash +python3 translation-cleaner.py [options] + +Options: + -h, --help Show help message + -t, --translations-dir DIR Translation files directory + -s, --source-dir DIR Source code directory + -c, --clean Clean unused translation keys + --sync Sync translation keys + --source-lang LANG Source language for syncing (default: en_US) + --no-backup Do not create backup files when cleaning +``` + +#### Features +1. **Unused Key Cleanup**: Identifies and removes translation keys that are no longer used in the source code +2. **Key Synchronization**: Ensures all language files have the same set of keys +3. **Backup Protection**: Creates backup files before making destructive changes +4. **Interactive Confirmation**: Asks for user confirmation before deleting keys + +### manage-translations.sh + +A convenient wrapper script that provides a unified interface to all translation tools. + +#### Commands +```bash +./manage-translations.sh [options] + +Commands: + extract Extract translatable texts to temporary file + update Update translation files (add missing/remove extra keys) + clean Clean unused translation keys + sync Sync keys across all language files + status Show translation status + +Options: + -l, --lang LANG Specify language (e.g.: zh_CN) + -t, --trans-dir DIR Translation files directory + -s, --source-dir DIR Source code directory + -h, --help Show help message +``` + +## Workflow Examples + +### Initial Setup +1. Create translation directory structure +2. Extract all translatable texts: `./manage-translations.sh extract` +3. Create initial translation files: `./manage-translations.sh update` + +### Regular Maintenance +1. Check status: `./manage-translations.sh status` +2. Update translations after code changes: `./manage-translations.sh update` +3. Clean up unused keys periodically: `./manage-translations.sh clean` + +### Adding New Language +1. Create new language file: `./manage-translations.sh update -l new_lang` +2. Sync keys if needed: `./manage-translations.sh sync` + +## File Structure + +``` +translations/ +├── tools/ # Translation management tools +│ ├── translation-manager.py # Main extraction and update tool +│ ├── translation-cleaner.py # Cleanup and sync tool +│ ├── manage-translations.sh # Wrapper script +│ └── translation-tools-guide.md # This documentation +├── en_US.json # English translations (reference) +├── zh_CN.json # Chinese translations +└── [other_lang].json # Other language files +``` + +## Configuration + +### Text Extraction Patterns + +The tool uses regex patterns to identify translatable texts. Current patterns include: + +1. **QML qsTr patterns**: + - `qsTr("text")` and `qsTr('text')` + - Supports escaped quotes and nested quotes + +2. **JavaScript i18n patterns**: + - `i18n.t("text")` and `i18n.t('text')` + - Supports template literals and complex expressions + +3. **Custom patterns** can be added by modifying the patterns list in `translation-manager.py` + +### File Extensions + +By default, the tool processes: +- `.qml` files (QML/QtQuick) +- `.js` files (JavaScript) + +Additional file types can be added by modifying the file extension filters. + +## Best Practices + +1. **Regular Updates**: Run `./manage-translations.sh status` regularly to check for new translatable texts +2. **Clean Periodically**: Use `./manage-translations.sh clean` to remove unused keys +3. **Backup Important**: Always backup translation files before major changes +4. **Consistent Patterns**: Use consistent function calls (`qsTr`, `i18n.t`) for translatable texts +5. **Review Changes**: Always review the changes before confirming deletions or additions + +## Troubleshooting + +### Common Issues + +1. **Missing Texts**: If some translatable texts are not extracted, check if they match the supported patterns +2. **Path Issues**: Ensure the source and translation directory paths are correct +3. **Permission Errors**: Make sure the script has write permissions to the translation directory +4. **Encoding Issues**: All files should be saved in UTF-8 encoding + +### Getting Help + +For additional help: +- Use `--help` option with any tool +- Check the console output for error messages +- Verify file paths and permissions + +## Advanced Usage + +### Custom Regex Patterns + +To add support for new translation function patterns, modify the `patterns` list in `TranslationManager.extract_translatable_texts()`: + +```python +patterns = [ + # Existing patterns... + r'customTranslate\s*\(\s*(["\'])((?:\\.|(?!\1)[^\\])*?)\1\s*\)', # Custom pattern +] +``` + +### Batch Processing + +For processing multiple projects or directories: + +```bash +# Process multiple directories +for dir in project1 project2 project3; do + ./manage-translations.sh -s "$dir" -t "$dir/translations" update +done +``` + +### Integration with Build Systems + +The tools can be integrated into build systems to automatically update translations: + +```bash +# In your build script +./manage-translations.sh update --non-interactive +``` + +## Version History + +- **v1.0**: Initial version with basic extraction and update functionality +- **v1.1**: Added improved regex patterns for better text extraction +- **v1.2**: Added cleaning and synchronization tools +- **v1.3**: Added English output and improved user interface +- **v1.4**: Moved all tools to dedicated tools directory and improved documentation diff --git a/.config/quickshell/translations/tools/翻译管理脚本说明.md b/.config/quickshell/translations/tools/翻译管理脚本说明.md new file mode 100644 index 000000000..fa9850cf8 --- /dev/null +++ b/.config/quickshell/translations/tools/翻译管理脚本说明.md @@ -0,0 +1,287 @@ +# 翻译管理工具套件 + +这套工具用于管理项目的翻译文件,自动提取可翻译文本,比较不同语言文件之间的差异,并提供维护功能。 + +## 工具组成 + +### 1. `translation-manager.py` - 主要翻译管理器 +- 提取可翻译文本 +- 比较和更新翻译文件 +- 交互式添加/删除翻译键 + +### 2. `translation-cleaner.py` - 翻译文件维护工具 +- 清理不再使用的翻译键 +- 同步不同语言文件的键结构 + +### 3. `manage-translations.sh` - 便捷包装脚本 +- 提供统一的命令行界面 +- 显示翻译状态 +- 简化常用操作 + +## 快速开始 + +### 使用便捷脚本(推荐) + +```bash +# 进入工具目录 +cd .config/quickshell/translations/tools + +# 查看帮助 +./manage-translations.sh --help + +# 显示当前翻译状态 +./manage-translations.sh status + +# 提取可翻译文本 +./manage-translations.sh extract + +# 更新所有翻译文件 +./manage-translations.sh update + +# 更新特定语言 +./manage-translations.sh update -l zh_CN + +# 清理不再使用的键 +./manage-translations.sh clean + +# 同步所有语言文件的键 +./manage-translations.sh sync +``` + +或者从项目根目录运行: +```bash +# 从项目根目录运行 +.config/quickshell/translations/tools/manage-translations.sh status +.config/quickshell/translations/tools/manage-translations.sh update +``` + +## 详细使用说明 + +### 翻译管理器 (`translation-manager.py`) + +基本用法: +```bash +# 处理所有语言 +./translation-manager.py + +# 指定特定语言 +./translation-manager.py --language zh_CN + +# 仅提取可翻译文本 +./translation-manager.py --extract-only + +# 显示提取的文本 +./translation-manager.py --extract-only --show-temp +``` + +参数说明: +- `--translations-dir`, `-t`: 翻译文件目录(默认:`.config/quickshell/translations`) +- `--source-dir`, `-s`: 源代码目录(默认:`.config/quickshell`) +- `--language`, `-l`: 指定要处理的语言代码 +- `--extract-only`, `-e`: 仅提取可翻译文本 +- `--show-temp`: 显示临时提取文件的内容 + +### 翻译清理器 (`translation-cleaner.py`) + +```bash +# 清理不再使用的翻译键 +./translation-cleaner.py --clean + +# 同步翻译键(以 en_US 为基准) +./translation-cleaner.py --sync + +# 指定不同的源语言进行同步 +./translation-cleaner.py --sync --source-lang zh_CN + +# 清理时不创建备份 +./translation-cleaner.py --clean --no-backup +``` + +## 工作流程 + +### 日常翻译更新流程 + +1. **检查状态**: + ```bash + ./manage-translations.sh status + ``` + +2. **更新翻译**: + ```bash + ./manage-translations.sh update + ``` + +3. **清理无用键**(可选): + ```bash + ./manage-translations.sh clean + ``` + +### 新增语言流程 + +1. **创建新语言文件**: + ```bash + ./manage-translations.sh update -l new_lang + ``` + +2. **同步键结构**: + ```bash + ./manage-translations.sh sync + ``` + +### 大规模重构后的清理流程 + +1. **备份翻译文件**: + ```bash + cp -r .config/quickshell/translations .config/quickshell/translations.backup + ``` + +2. **清理无用键**: + ```bash + ./manage-translations.sh clean + ``` + +3. **同步所有语言**: + ```bash + ./manage-translations.sh sync + ``` + +## 支持的翻译文本格式 + +工具可以识别以下格式的可翻译文本: + +```qml +// 基本格式 +Translation.tr("Hello, world!") +Translation.tr('Hello, world!') +Translation.tr(`Hello, world!`) + +// 带换行符 +Translation.tr("Line 1\nLine 2") + +// 带转义字符 +Translation.tr("Say \"Hello\"") + +// 带参数占位符 +Translation.tr("Hello, %1!").arg(name) +Translation.tr("{0} files selected").arg(count) +``` + +## 示例输出 + +### 状态显示 +``` +$ ./manage-translations.sh status +正在分析翻译状态... +=== 当前项目状态 === +提取到 166 个可翻译文本 + +=== 翻译文件状态 === + en_US: 470 个键 + zh_CN: 470 个键 +``` + +### 更新翻译 +``` +$ ./manage-translations.sh update -l zh_CN +更新翻译文件... +================================================== +处理语言: zh_CN +================================================== +分析结果: + 缺少的键: 5 + 多余的键: 20 + +发现 5 个缺少的翻译键: +1. "New feature text" +2. "Another new text" +... + +是否添加这 5 个缺少的键? (y/n): y +已添加 5 个键 + +发现 20 个多余的翻译键: +1. "Removed old text" -> "已删除的旧文本" +... + +是否删除这 20 个多余的键? (y/n): y +已删除 20 个键 + +已保存翻译文件 +``` + +### 清理无用键 +``` +$ ./manage-translations.sh clean +清理不再使用的翻译键... +处理语言: zh_CN +发现 50 个不再使用的键: + 1. "old_unused_text" + 2. "deprecated_message" + ... + +是否删除这 50 个不再使用的键? (y/n): y +已删除 50 个键 +原始键数: 470, 清理后: 420 +``` + +## 高级功能 + +### 自定义目录结构 + +```bash +# 使用自定义目录 +./translation-manager.py \ + --translations-dir /path/to/translations \ + --source-dir /path/to/source +``` + +### 批量处理 + +```bash +# 创建处理脚本 +cat > update-all-translations.sh << 'EOF' +#!/bin/bash +for lang in zh_CN ja_JP ko_KR; do + echo "Processing $lang..." + ./manage-translations.sh update -l $lang +done +EOF + +chmod +x update-all-translations.sh +./update-all-translations.sh +``` + +## 注意事项 + +1. **备份重要**:在执行清理操作前,工具会自动创建备份,但建议手动备份重要文件 + +2. **文本提取限制**: + - 只支持静态字符串,不支持动态构建的字符串 + - 必须使用 `Translation.tr()` 格式 + +3. **文件编码**:所有文件必须使用 UTF-8 编码 + +4. **键名规范**:建议使用英文作为键名,避免使用特殊字符 + +## 故障排除 + +### 常见问题 + +**Q: 提取的文本数量与预期不符?** +A: 检查是否所有可翻译文本都使用了 `Translation.tr()` 格式,确保没有动态构建的字符串。 + +**Q: 同步后某些翻译丢失?** +A: 检查源语言文件是否包含所有必要的键,考虑使用不同的源语言进行同步。 + +**Q: 清理操作删除了需要的键?** +A: 从自动创建的备份文件中恢复,检查源代码中是否正确使用了 `Translation.tr()`。 + +### 恢复备份 + +```bash +# 恢复单个文件 +cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json + +# 恢复所有文件 +cp .config/quickshell/translations.backup/* .config/quickshell/translations/ +``` diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index f73a6949f..a0d099397 100644 --- a/.config/quickshell/translations/zh_CN.json +++ b/.config/quickshell/translations/zh_CN.json @@ -27,28 +27,26 @@ "Clear chat history": "清除聊天记录", "Clear the current list of images": "清除当前图片列表", "Close": "关闭", - "Closes cheatsheet on press": "Closes cheatsheet on press", - "Closes left sidebar on press": "Closes left sidebar on press", - "Closes media controls on press": "Closes media controls on press", - "Closes on screen keyboard on press": "Closes on screen keyboard on press", - "Closes overview": "Closes overview", - "Closes right sidebar on press": "Closes right sidebar on press", + "Closes cheatsheet on press": "按下时关闭快捷键表", + "Closes left sidebar on press": "按下时关闭左侧边栏", + "Closes media controls on press": "按下时关闭媒体控制", + "Closes on screen keyboard on press": "按下时关闭屏幕键盘", + "Closes overview": "关闭概览", + "Closes right sidebar on press": "按下时关闭右侧边栏", "Copy": "复制", "Copy code": "复制代码", - "Ctrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Ctrl+O 展开侧边栏\nCtrl+P 将侧边栏分离为窗口", "Current API endpoint: {0}\nSet it with {1}mode PROVIDER": "当前 API 端点:{0}\n使用 {1}mode PROVIDER 设置", "Current model: {0}\nSet it with {1}model MODEL": "当前模型:{0}\n使用 {1}model MODEL 设置", - "Decrease brightness": "Decrease brightness", + "Decrease brightness": "降低亮度", "Delete": "删除", "Desktop": "桌面", - "Detach left sidebar into a window/Attach it back": "Detach left sidebar into a window/Attach it back", + "Detach left sidebar into a window/Attach it back": "将左侧边栏分离为窗口/重新附加", "Disable NSFW content": "禁用 NSFW 内容", "Done": "完成", "Download": "下载", "Download complete": "下载完成", "Edit": "编辑", "Enter text to translate...": "输入要翻译的文本...", - "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "实验性 | 在线 | Google 模型\n功能更多但搜索速度较慢", "Finished tasks will go here": "已完成的任务将显示在这里", "For desktop wallpapers | Good quality": "桌面壁纸专用 | 质量好", "For storing API keys and other sensitive information": "用于存储 API 密钥和其他敏感信息", @@ -56,17 +54,14 @@ "Get the next page of results": "获取下一页结果", "Go to source ({0})": "转到源 ({0})", "Hibernate": "休眠", - "Hides brightness OSD on press": "Hides brightness OSD on press", - "Hides volume OSD on press": "Hides volume OSD on press", - "Hold to show workspace numbers, release to show icons": "Hold to show workspace numbers, release to show icons", - "Increase brightness": "Increase brightness", + "Hides brightness OSD on press": "按下时隐藏亮度显示", + "Hides volume OSD on press": "按下时隐藏音量显示", + "Hold to show workspace numbers, release to show icons": "按住显示工作区编号,松开显示图标", + "Increase brightness": "提高亮度", "Input": "输入", "Intelligence": "智能", "Interface": "界面", - "Interrupts possibility of overview being toggled on release. ": "Interrupts possibility of overview being toggled on release. ", - "Invalid API provider. Supported: \n- ": "无效的 API 提供商。支持的:\n- ", "Invalid arguments. Must provide `key` and `value`.": "参数无效。必须提供 `key` 和 `value`。", - "Invalid model. Supported: \n```\n": "无效模型。支持的:\n```\n", "Jump to current month": "跳转到当前月份", "Keep system awake": "保持系统唤醒", "Large images | God tier quality, no NSFW.": "大尺寸图片 | 顶级质量,无 NSFW", @@ -86,24 +81,21 @@ "Nothing here!": "这里什么都没有!", "Notifications": "通知", "OK": "确定", - "Online via {0} | {1}'s model": "通过 {0} 在线 | {1} 模型", - "Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。", "Open file link": "打开文件链接", - "Opens cheatsheet on press": "Opens cheatsheet on press", - "Opens left sidebar on press": "Opens left sidebar on press", - "Opens media controls on press": "Opens media controls on press", - "Opens on screen keyboard on press": "Opens on screen keyboard on press", - "Opens right sidebar on press": "Opens right sidebar on press", - "Opens session screen on press": "Opens session screen on press", + "Opens cheatsheet on press": "按下时打开快捷键表", + "Opens left sidebar on press": "按下时打开左侧边栏", + "Opens media controls on press": "按下时打开媒体控制", + "Opens on screen keyboard on press": "按下时打开屏幕键盘", + "Opens right sidebar on press": "按下时打开右侧边栏", + "Opens session screen on press": "按下时打开会话屏幕", "Output": "输出", "Page {0}": "第 {0} 页", "Plasma Settings": "Plasma 设置", - "Provider set to ": "提供商设置为", "Reboot": "重启", - "Reboot to firmware settings": "Reboot to firmware settings", + "Reboot to firmware settings": "重启到固件设置", "Reload Hyprland & Quickshell": "重新加载 Hyprland 和 Quickshell", "Run": "运行", - "Run command": "Run command", + "Run command": "运行命令", "Save": "保存", "Save to Downloads": "保存到下载文件夹", "Scroll to change brightness": "滚动调节亮度", @@ -122,351 +114,63 @@ "Shutdown": "关机", "Silent": "静音", "Sleep": "睡眠", - "Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。", "System": "系统", "Task Manager": "任务管理器", "Task description": "任务描述", "Temperature must be between 0 and 2": "温度必须在 0 到 2 之间", "Temperature set to {0}": "温度设置为 {0}", "Temperature: {0}": "温度:{0}", - "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查您的标签和 NSFW 设置\n- 如果没有想到标签,请输入页码", - "The current API used. Endpoint: ": "当前使用的 API。端点:", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "成人向 | 数量巨大,大量 NSFW,质量参差不齐", "The popular one | Best quantity, but quality can vary wildly": "最受欢迎 | 数量最多,但质量参差不齐", "Thinking": "思考中", - "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ": "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ", - "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.": "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.", - "Toggle clipboard query on overview widget": "Toggle clipboard query on overview widget", - "Toggle emoji query on overview widget": "Toggle emoji query on overview widget", - "Toggles cheatsheet on press": "Toggles cheatsheet on press", - "Toggles left sidebar on press": "Toggles left sidebar on press", - "Toggles media controls on press": "Toggles media controls on press", - "Toggles on screen keyboard on press": "Toggles on screen keyboard on press", - "Toggles overview on press": "Toggles overview on press", - "Toggles overview on release": "Toggles overview on release", - "Toggles right sidebar on press": "Toggles right sidebar on press", - "Toggles session screen on press": "Toggles session screen on press", + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.": "为确保此功能正常工作,请在自动触发的子映射中使用 binditn = MODKEYS, catchall,该子映射包含所有内容。", + "Toggle clipboard query on overview widget": "在概览组件中切换剪贴板查询", + "Toggle emoji query on overview widget": "在概览组件中切换表情符号查询", + "Toggles cheatsheet on press": "按下时切换快捷键表", + "Toggles left sidebar on press": "按下时切换左侧边栏", + "Toggles media controls on press": "按下时切换媒体控制", + "Toggles on screen keyboard on press": "按下时切换屏幕键盘", + "Toggles overview on press": "按下时切换概览", + "Toggles overview on release": "松开时切换概览", + "Toggles right sidebar on press": "按下时切换右侧边栏", + "Toggles session screen on press": "按下时切换会话屏幕", "Translation goes here...": "翻译结果会显示在这里...", "Translator": "翻译器", - "Triggers brightness OSD on press": "Triggers brightness OSD on press", - "Triggers volume OSD on press": "Triggers volume OSD on press", + "Triggers brightness OSD on press": "按下时触发亮度显示", + "Triggers volume OSD on press": "按下时触发音量显示", "Unfinished": "未完成", "Unknown": "未知", "Unknown Album": "未知专辑", "Unknown Artist": "未知艺术家", "Unknown Title": "未知标题", - "Unknown command: ": "未知命令:", "Unknown function call: {0}": "未知函数调用:{0}", "Uptime: {0}": "运行时间:{0}", - "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in **bold** to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.": "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in **bold** to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.", "View Markdown source": "查看 Markdown 源码", "Volume": "音量", "Volume mixer": "音量混合器", "Waifus only | Excellent quality, limited quantity": "仅限角色 | 优秀质量,数量有限", "Waiting for response...": "等待响应...", "Workspace": "工作区", - "\nSet with /mode PROVIDER": "\n使用 /mode PROVIDER 设置", - "about": "关于", - "accessed": "访问时间", - "account": "账户", - "active": "活动", - "active_state": "活动状态", - "addon": "附加组件", - "addons": "附加组件", - "address": "地址", - "admin": "管理员", - "align": "对齐", - "always_on_top": "总在最前", - "animate": "动画", - "app": "应用", - "appearance": "外观", - "application": "应用程序", - "applications": "应用程序", - "apply": "应用", - "apps": "应用", - "auto": "自动", - "background": "背景", - "balanced": "平衡", - "bar": "栏", - "bars": "栏", - "battery": "电池", - "bluetooth": "蓝牙", - "blur": "模糊", - "border": "边框", - "bottom": "底部", - "brightness": "亮度", - "bring_to_front": "置于顶层", - "bytes": "{0} 字节", - "cancel": "取消", - "center": "居中", - "characters": "{0} 个字符", - "charging": "充电中", - "clear_all": "清除全部", - "clicked": "点击", - "close": "关闭", - "collapse": "折叠", - "color": "颜色", - "command": "命令", - "connected": "已连接", - "connecting": "连接中...", - "context_menu": "右键菜单", - "copy": "复制", - "cpu": "处理器", - "created": "创建时间", - "critical": "电量危险", - "customize": "自定义", - "cut": "剪切", - "dark": "深色", - "dashboard": "仪表板", - "date": "日期", - "debug": "调试", - "default": "默认", - "degrees": "{0}°", - "delete": "删除", - "demo": "演示", - "desktop": "桌面", - "dialog": "对话框", - "disabled": "已禁用", - "discharging": "放电中", - "disconnected": "已断开", - "disk": "磁盘", - "display": "显示", - "distribute": "分布", - "dock": "停靠栏", - "documents": "文档", - "double_clicked": "双击", - "downloads": "下载", - "dragged": "拖拽", - "dropdown": "下拉菜单", - "dropped": "拖放", - "effect": "效果", - "email": "邮箱", - "enabled": "已启用", - "error": "错误", - "ethernet": "以太网", - "example": "示例", - "execute": "执行", - "exit": "退出", - "expand": "展开", - "extension": "扩展", - "extensions": "扩展", - "fan": "风扇", - "favorites": "收藏夹", - "file": "文件", - "files": "文件", - "filter": "滤镜", - "flip": "翻转", - "floating": "浮动", - "focus": "焦点", - "folder": "文件夹", - "folders": "文件夹", - "foreground": "前景", - "format": "格式", - "full": "已充满", - "fullscreen": "全屏", - "gigabytes": "{0} GB", - "gigahertz": "{0} GHz", - "glow": "发光", - "group": "群组", - "guest": "访客", - "headphones": "耳机", - "help": "帮助", - "hertz": "{0} Hz", - "hibernate": "休眠", - "hidden": "隐藏", - "hide": "隐藏", - "highlight": "高亮", - "hint": "提示", - "home": "主目录", - "hour": "小时", - "hours": "小时", - "hover": "悬停", - "inactive": "非活动", - "info": "信息", - "input": "输入", - "install": "安装", - "justify": "两端对齐", - "keybinds": "按键绑定", - "keyboard": "键盘", - "kilobytes": "{0} KB", - "kilohertz": "{0} kHz", - "landscape": "横向", - "language": "语言", - "launcher": "启动器", - "left": "左对齐", - "light": "浅色", - "loading": "加载中...", - "lock": "锁定", - "log": "日志", - "logout": "注销", - "long_pressed": "长按", - "low": "电量低", - "manual": "手册", - "margin": "边距", - "maximize": "最大化", - "megabytes": "{0} MB", - "megahertz": "{0} MHz", - "memory": "内存", - "menu": "菜单", - "menubar": "菜单栏", - "microphone": "麦克风", - "minimize": "最小化", - "minute": "分钟", - "minutes": "分钟", - "mirror": "镜像", - "modal": "模态框", - "modified": "修改时间", - "monitor": "显示器", - "mouse": "鼠标", - "move": "移动", - "music": "音乐", - "mute": "静音", - "network": "网络", - "next": "下一个", - "no": "否", - "no_notifications": "无通知", - "notifications": "通知", - "off": "关", - "ok": "确定", - "on": "开", - "opacity": "不透明度", - "open": "打开", - "orientation": "方向", - "outline": "轮廓", - "output": "输出", - "overview": "概览", - "owner": "所有者", - "package": "软件包", - "packages": "软件包", - "padding": "内边距", - "panel": "面板", - "panels": "面板", - "panned": "平移", - "password": "密码", - "paste": "粘贴", - "pause": "暂停", - "percent": "{0}%", - "performance": "性能", - "permissions": "权限", - "phone": "电话", - "pictures": "图片", - "pinched": "捏合", - "pinned": "固定", - "pixels": "{0}像素", - "placeholder": "占位符", - "play": "播放", - "plugin": "插件", - "plugins": "插件", - "popup": "弹出窗口", - "portrait": "纵向", - "power": "电源", - "power_saver": "节能", - "pressed": "按下", - "previous": "上一个", - "profile": "配置文件", - "properties": "属性", - "quiet": "安静", - "quit": "退出", - "read": "读取", - "recent": "最近", - "recording": "录制", - "refresh": "刷新", - "refresh_rate": "刷新率", - "reload": "重新加载", - "rename": "重命名", - "reset": "重置", - "resize": "调整大小", - "resolution": "分辨率", - "restart": "重启", - "restore": "还原", - "right": "右对齐", - "right_clicked": "右键点击", - "rotate": "旋转", - "rotated": "旋转", - "sample": "样本", - "save": "保存", - "scale": "缩放", - "screenshot": "截图", - "scrolled": "滚动", - "search": "搜索", - "second": "秒", - "seconds": "秒", - "selection": "选择", - "send_to_back": "置于底层", - "settings": "设置", - "shadow": "阴影", - "shortcuts": "快捷键", - "show": "显示", - "shutdown": "关机", - "sidebar": "侧边栏", - "silent": "静默", - "size": "大小", - "snapped": "贴靠", - "software": "软件", - "space": "间距", - "speaker": "扬声器", - "statusbar": "状态栏", - "sticky": "粘性", - "stop": "停止", - "style": "样式", - "success": "成功", - "suspend": "挂起", - "swiped": "滑动", - "systray": "系统托盘", - "tapped": "轻触", - "taskbar": "任务栏", - "temperature": "温度", - "terabytes": "{0} TB", - "terminal": "终端", - "test": "测试", - "theme": "主题", - "tiled": "平铺", - "time": "时间", - "tips": "提示", - "today": "今天", - "tomorrow": "明天", - "toolbar": "工具栏", - "tooltip": "工具提示", - "top": "顶部", - "touchpad": "触摸板", - "trace": "跟踪", - "transform": "变换", - "transition": "过渡", - "transparency": "透明度", - "trash": "回收站", - "tutorial": "教程", - "type": "类型", - "uninstall": "卸载", - "unlock": "解锁", - "unmute": "取消静音", - "unpinned": "取消固定", - "unsticky": "取消粘性", - "update": "更新", - "upgrade": "升级", - "user": "用户", - "username": "用户名", - "verbose": "详细", - "version": "版本", - "videos": "视频", - "visible": "可见", - "volume": "音量", - "warning": "警告", - "welcome": "欢迎", - "widget": "组件", - "widgets": "组件", - "wifi": "Wi-Fi", - "window": "窗口", - "windowed": "窗口化", - "windows": "窗口", - "workspace": "工作区", - "workspaces": "工作区", - "write": "写入", - "yes": "是", - "yesterday": "昨天", - "zoomed": "缩放", "{0} (copied)": "{0}(已复制)", "{0} Safe Storage": "{0} 安全存储", "{0} does not require an API key": "{0} 不需要 API 密钥", "{0} queries pending": "{0} 个查询等待中", - "{0} | Right-click to configure": "{0} | 右键点击进行配置" + "{0} | Right-click to configure": "{0} | 右键点击进行配置", + "Set with /mode PROVIDER": "使用 /mode PROVIDER 设置", + "Invalid API provider. Supported: \n-": "无效的 API 提供商。支持的:\n-", + "Unknown command:": "未知命令:", + "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "输入 /key 开始使用在线模型\nCtrl+O 展开侧边栏\nCtrl+P 将侧边栏分离为窗口", + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key.": "这是必要的,因为在按住键时,quickshell 中的 GlobalShortcut.onReleased 会在您是否按下其他键时触发。", + "The current API used. Endpoint:": "当前使用的 API。端点:", + "Provider set to": "提供商设置为", + "Invalid model. Supported: \n```": "无效模型。支持的:\n```", + "Interrupts possibility of overview being toggled on release.": "中断松开时切换概览的可能性。", + "Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。", + "Message the model... \"{0}\" for commands": "与模型对话... \"{0}\" 查看命令", + "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "实验性 | 在线 | Google 模型\n功能更多但搜索速度较慢", + "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}": "要设置 API 密钥,请将其与命令一起传递\n\n要查看密钥,请将 \"get\" 与命令一起传递
\n\n### 对于 {0}:\n\n**链接**:{1}\n\n{2}", + "Enter tags, or \"{0}\" for commands": "输入标签,或 \"{0}\" 查看命令", + "Online via {0} | {1}'s model": "通过 {0} 在线 | {1} 的模型", + "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查您的标签和 NSFW 设置\n- 如果没有想到标签,请输入页码", + "Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。" } \ No newline at end of file