forked from Shinonome/dots-hyprland
Rearrange for tidier structure (#2212)
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
# Translation Management Tool Suite
|
||||
|
||||
This suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions.
|
||||
|
||||
## Tool Components
|
||||
|
||||
### 1. `translation-manager.py` - Main Translation Manager
|
||||
- Extract translatable texts
|
||||
- Compare and update translation files
|
||||
- Interactive addition/removal of translation keys
|
||||
|
||||
### 2. `translation-cleaner.py` - Translation File Maintenance Tool
|
||||
- Clean unused translation keys
|
||||
- Synchronize key structure across different language files
|
||||
|
||||
### 3. `manage-translations.sh` - Convenient Wrapper Script
|
||||
- Provides a unified command-line interface
|
||||
- Displays translation status
|
||||
- Simplifies common operations
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using the Wrapper Script (Recommended)
|
||||
|
||||
```bash
|
||||
# Enter the tools directory
|
||||
cd .config/quickshell/translations/tools
|
||||
|
||||
# Show help
|
||||
./manage-translations.sh --help
|
||||
|
||||
# Show current translation status
|
||||
./manage-translations.sh status
|
||||
|
||||
# Extract translatable texts
|
||||
./manage-translations.sh extract
|
||||
|
||||
# Update all translation files
|
||||
./manage-translations.sh update
|
||||
|
||||
# Update a specific language
|
||||
./manage-translations.sh update -l zh_CN
|
||||
|
||||
# Clean unused keys
|
||||
./manage-translations.sh clean
|
||||
|
||||
# Synchronize keys across all language files
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
Or run from the project root:
|
||||
```bash
|
||||
# Run from the project root
|
||||
.config/quickshell/translations/tools/manage-translations.sh status
|
||||
.config/quickshell/translations/tools/manage-translations.sh update
|
||||
```
|
||||
|
||||
## Detailed Usage
|
||||
|
||||
### Translation Manager (`translation-manager.py`)
|
||||
|
||||
Basic usage:
|
||||
```bash
|
||||
# Process all languages
|
||||
./translation-manager.py
|
||||
|
||||
# Specify a particular language
|
||||
./translation-manager.py --language zh_CN
|
||||
|
||||
# Extract translatable texts only
|
||||
./translation-manager.py --extract-only
|
||||
|
||||
# Show extracted texts
|
||||
./translation-manager.py --extract-only --show-temp
|
||||
```
|
||||
|
||||
Parameter description:
|
||||
- `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`)
|
||||
- `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`)
|
||||
- `--language`, `-l`: Specify the language code to process
|
||||
- `--extract-only`, `-e`: Only extract translatable texts
|
||||
- `--show-temp`: Show the content of the temporary extraction file
|
||||
|
||||
### Translation Cleaner (`translation-cleaner.py`)
|
||||
|
||||
```bash
|
||||
# Clean unused translation keys
|
||||
./translation-cleaner.py --clean
|
||||
|
||||
# Synchronize translation keys (using en_US as the base)
|
||||
./translation-cleaner.py --sync
|
||||
|
||||
# Specify a different source language for syncing
|
||||
./translation-cleaner.py --sync --source-lang zh_CN
|
||||
|
||||
# Clean without creating backups
|
||||
./translation-cleaner.py --clean --no-backup
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Regular Translation Update Workflow
|
||||
|
||||
1. **Check status**:
|
||||
```bash
|
||||
./manage-translations.sh status
|
||||
```
|
||||
|
||||
2. **Update translations**:
|
||||
```bash
|
||||
./manage-translations.sh update
|
||||
```
|
||||
|
||||
3. **Clean unused keys** (optional):
|
||||
```bash
|
||||
./manage-translations.sh clean
|
||||
```
|
||||
|
||||
### Adding a New Language
|
||||
|
||||
1. **Create a new language file**:
|
||||
```bash
|
||||
./manage-translations.sh update -l new_lang
|
||||
```
|
||||
|
||||
2. **Synchronize key structure**:
|
||||
```bash
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
### Cleanup After Large Refactoring
|
||||
|
||||
1. **Backup translation files**:
|
||||
```bash
|
||||
cp -r .config/quickshell/translations .config/quickshell/translations.backup
|
||||
```
|
||||
|
||||
2. **Clean unused keys**:
|
||||
```bash
|
||||
./manage-translations.sh clean
|
||||
```
|
||||
|
||||
3. **Synchronize all languages**:
|
||||
```bash
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
## Supported Translatable Text Formats
|
||||
|
||||
The tool recognizes the following formats for translatable texts:
|
||||
|
||||
```qml
|
||||
// Basic format
|
||||
Translation.tr("Hello, world!")
|
||||
Translation.tr('Hello, world!')
|
||||
Translation.tr(`Hello, world!`)
|
||||
|
||||
// With line breaks
|
||||
Translation.tr("Line 1\nLine 2")
|
||||
|
||||
// With escape characters
|
||||
Translation.tr("Say \"Hello\"")
|
||||
|
||||
// With parameter placeholders
|
||||
Translation.tr("Hello, %1!").arg(name)
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Status Display
|
||||
```
|
||||
$ ./manage-translations.sh status
|
||||
Analyzing translation status...
|
||||
=== Current Project Status ===
|
||||
166 translatable texts extracted
|
||||
|
||||
=== Translation File Status ===
|
||||
en_US: 470 keys
|
||||
zh_CN: 470 keys
|
||||
```
|
||||
|
||||
### Update Translations
|
||||
```
|
||||
$ ./manage-translations.sh update -l zh_CN
|
||||
Updating translation files...
|
||||
==================================================
|
||||
Processing language: zh_CN
|
||||
==================================================
|
||||
Analysis result:
|
||||
Missing keys: 5
|
||||
Extra keys: 20
|
||||
|
||||
Found 5 missing translation keys:
|
||||
1. "New feature text"
|
||||
2. "Another new text"
|
||||
...
|
||||
|
||||
Add these 5 missing keys? (y/n): y
|
||||
5 keys added
|
||||
|
||||
Found 20 extra translation keys:
|
||||
1. "Removed old text" -> "已删除的旧文本"
|
||||
...
|
||||
|
||||
Delete these 20 extra keys? (y/n): y
|
||||
20 keys deleted
|
||||
|
||||
Translation file saved
|
||||
```
|
||||
|
||||
### Clean Unused Keys
|
||||
```
|
||||
$ ./manage-translations.sh clean
|
||||
Cleaning unused translation keys...
|
||||
Processing language: zh_CN
|
||||
Found 50 unused keys:
|
||||
1. "old_unused_text"
|
||||
2. "deprecated_message"
|
||||
...
|
||||
|
||||
Delete these 50 unused keys? (y/n): y
|
||||
50 keys deleted
|
||||
Original key count: 470, after cleaning: 420
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Directory Structure
|
||||
|
||||
```bash
|
||||
# Use custom directories
|
||||
./translation-manager.py \
|
||||
--translations-dir /path/to/translations \
|
||||
--source-dir /path/to/source
|
||||
```
|
||||
|
||||
### Ignore Mark Feature
|
||||
|
||||
For dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"dynamic_key": "Some dynamic value /*keep*/"
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files
|
||||
|
||||
2. **Text extraction limitations**:
|
||||
- ~~Only supports static strings, not dynamically constructed strings~~
|
||||
- Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management.
|
||||
- Must use the `Translation.tr()` format
|
||||
|
||||
3. **File encoding**: All files must use UTF-8 encoding
|
||||
|
||||
4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Q: Text does not appear after adding Translation.tr?**
|
||||
A: You need to import the translation feature in your QML file using `import "root:/"`, otherwise the translation text will not be displayed correctly.
|
||||
|
||||
**Q: The number of extracted texts does not match expectations?**
|
||||
A: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings.
|
||||
|
||||
**Q: Some translations are missing after syncing?**
|
||||
A: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing.
|
||||
|
||||
**Q: The cleaning operation deleted needed keys?**
|
||||
A: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code.
|
||||
|
||||
### Restore Backup
|
||||
|
||||
```bash
|
||||
# Restore a single file
|
||||
cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json
|
||||
|
||||
# Restore all files
|
||||
cp .config/quickshell/translations.backup/* .config/quickshell/translations/
|
||||
```
|
||||
@@ -0,0 +1,286 @@
|
||||
# 翻译管理工具套件
|
||||
|
||||
这套工具用于管理项目的翻译文件,自动提取可翻译文本,比较不同语言文件之间的差异,并提供维护功能。
|
||||
|
||||
## 工具组成
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
## 示例输出
|
||||
|
||||
### 状态显示
|
||||
```
|
||||
$ ./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
|
||||
```
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **备份重要**:在执行清理操作前,工具会自动创建备份,但建议手动备份重要文件
|
||||
|
||||
2. **文本提取限制**:
|
||||
- ~~只支持静态字符串,不支持动态构建的字符串~~
|
||||
- 动态资源(如变量拼接、运行时生成的文本)无法自动提取,需要在翻译文件中手动添加,并使用 `/*keep*/` 标记进行忽略管理。
|
||||
- 必须使用 `Translation.tr()` 格式
|
||||
### 忽略标记功能
|
||||
|
||||
对于动态资源或特殊文本,如果不希望被自动清理,可在翻译值末尾添加 `/*keep*/`,工具会自动忽略这些键,不会在清理和同步时删除。
|
||||
|
||||
示例:
|
||||
```json
|
||||
{
|
||||
"dynamic_key": "Some dynamic value /*keep*/"
|
||||
}
|
||||
```
|
||||
|
||||
3. **文件编码**:所有文件必须使用 UTF-8 编码
|
||||
|
||||
4. **键名规范**:建议使用英文作为键名,避免使用特殊字符
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
|
||||
**Q: 添加了 Translation.tr 后文字不显示?**
|
||||
A: 需要在 QML 文件中使用 `import "root:/"` 导入翻译功能,否则无法正常显示翻译文本。
|
||||
|
||||
**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/
|
||||
```
|
||||
@@ -0,0 +1,285 @@
|
||||
# Translation Management Tool Suite
|
||||
|
||||
This suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions.
|
||||
|
||||
## Tool Components
|
||||
|
||||
### 1. `translation-manager.py` - Main Translation Manager
|
||||
- Extract translatable texts
|
||||
- Compare and update translation files
|
||||
- Interactive addition/removal of translation keys
|
||||
|
||||
### 2. `translation-cleaner.py` - Translation File Maintenance Tool
|
||||
- Clean unused translation keys
|
||||
- Synchronize key structure across different language files
|
||||
|
||||
### 3. `manage-translations.sh` - Convenient Wrapper Script
|
||||
- Provides a unified command-line interface
|
||||
- Displays translation status
|
||||
- Simplifies common operations
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using the Wrapper Script (Recommended)
|
||||
|
||||
```bash
|
||||
# Enter the tools directory
|
||||
cd .config/quickshell/translations/tools
|
||||
|
||||
# Show help
|
||||
./manage-translations.sh --help
|
||||
|
||||
# Show current translation status
|
||||
./manage-translations.sh status
|
||||
|
||||
# Extract translatable texts
|
||||
./manage-translations.sh extract
|
||||
|
||||
# Update all translation files
|
||||
./manage-translations.sh update
|
||||
|
||||
# Update a specific language
|
||||
./manage-translations.sh update -l zh_CN
|
||||
|
||||
# Clean unused keys
|
||||
./manage-translations.sh clean
|
||||
|
||||
# Synchronize keys across all language files
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
Or run from the project root:
|
||||
```bash
|
||||
# Run from the project root
|
||||
.config/quickshell/translations/tools/manage-translations.sh status
|
||||
.config/quickshell/translations/tools/manage-translations.sh update
|
||||
```
|
||||
|
||||
## Detailed Usage
|
||||
|
||||
### Translation Manager (`translation-manager.py`)
|
||||
|
||||
Basic usage:
|
||||
```bash
|
||||
# Process all languages
|
||||
./translation-manager.py
|
||||
|
||||
# Specify a particular language
|
||||
./translation-manager.py --language zh_CN
|
||||
|
||||
# Extract translatable texts only
|
||||
./translation-manager.py --extract-only
|
||||
|
||||
# Show extracted texts
|
||||
./translation-manager.py --extract-only --show-temp
|
||||
```
|
||||
|
||||
Parameter description:
|
||||
- `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`)
|
||||
- `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`)
|
||||
- `--language`, `-l`: Specify the language code to process
|
||||
- `--extract-only`, `-e`: Only extract translatable texts
|
||||
- `--show-temp`: Show the content of the temporary extraction file
|
||||
|
||||
### Translation Cleaner (`translation-cleaner.py`)
|
||||
|
||||
```bash
|
||||
# Clean unused translation keys
|
||||
./translation-cleaner.py --clean
|
||||
|
||||
# Synchronize translation keys (using en_US as the base)
|
||||
./translation-cleaner.py --sync
|
||||
|
||||
# Specify a different source language for syncing
|
||||
./translation-cleaner.py --sync --source-lang zh_CN
|
||||
|
||||
# Clean without creating backups
|
||||
./translation-cleaner.py --clean --no-backup
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Regular Translation Update Workflow
|
||||
|
||||
1. **Check status**:
|
||||
```bash
|
||||
./manage-translations.sh status
|
||||
```
|
||||
|
||||
2. **Update translations**:
|
||||
```bash
|
||||
./manage-translations.sh update
|
||||
```
|
||||
|
||||
3. **Clean unused keys** (optional):
|
||||
```bash
|
||||
./manage-translations.sh clean
|
||||
```
|
||||
|
||||
### Adding a New Language
|
||||
|
||||
1. **Create a new language file**:
|
||||
```bash
|
||||
./manage-translations.sh update -l new_lang
|
||||
```
|
||||
|
||||
2. **Synchronize key structure**:
|
||||
```bash
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
### Cleanup After Large Refactoring
|
||||
|
||||
1. **Backup translation files**:
|
||||
```bash
|
||||
cp -r .config/quickshell/translations .config/quickshell/translations.backup
|
||||
```
|
||||
|
||||
2. **Clean unused keys**:
|
||||
```bash
|
||||
./manage-translations.sh clean
|
||||
```
|
||||
|
||||
3. **Synchronize all languages**:
|
||||
```bash
|
||||
./manage-translations.sh sync
|
||||
```
|
||||
|
||||
## Supported Translatable Text Formats
|
||||
|
||||
The tool recognizes the following formats for translatable texts:
|
||||
|
||||
```qml
|
||||
// Basic format
|
||||
Translation.tr("Hello, world!")
|
||||
Translation.tr('Hello, world!')
|
||||
Translation.tr(`Hello, world!`)
|
||||
|
||||
// With line breaks
|
||||
Translation.tr("Line 1\nLine 2")
|
||||
|
||||
// With escape characters
|
||||
Translation.tr("Say \"Hello\"")
|
||||
|
||||
// With parameter placeholders
|
||||
Translation.tr("Hello, %1!").arg(name)
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Status Display
|
||||
```
|
||||
$ ./manage-translations.sh status
|
||||
Analyzing translation status...
|
||||
=== Current Project Status ===
|
||||
166 translatable texts extracted
|
||||
|
||||
=== Translation File Status ===
|
||||
en_US: 470 keys
|
||||
zh_CN: 470 keys
|
||||
```
|
||||
|
||||
### Update Translations
|
||||
```
|
||||
$ ./manage-translations.sh update -l zh_CN
|
||||
Updating translation files...
|
||||
==================================================
|
||||
Processing language: zh_CN
|
||||
==================================================
|
||||
Analysis result:
|
||||
Missing keys: 5
|
||||
Extra keys: 20
|
||||
|
||||
Found 5 missing translation keys:
|
||||
1. "New feature text"
|
||||
2. "Another new text"
|
||||
...
|
||||
|
||||
Add these 5 missing keys? (y/n): y
|
||||
5 keys added
|
||||
|
||||
Found 20 extra translation keys:
|
||||
1. "Removed old text" -> "已删除的旧文本"
|
||||
...
|
||||
|
||||
Delete these 20 extra keys? (y/n): y
|
||||
20 keys deleted
|
||||
|
||||
Translation file saved
|
||||
```
|
||||
|
||||
### Clean Unused Keys
|
||||
```
|
||||
$ ./manage-translations.sh clean
|
||||
Cleaning unused translation keys...
|
||||
Processing language: zh_CN
|
||||
Found 50 unused keys:
|
||||
1. "old_unused_text"
|
||||
2. "deprecated_message"
|
||||
...
|
||||
|
||||
Delete these 50 unused keys? (y/n): y
|
||||
50 keys deleted
|
||||
Original key count: 470, after cleaning: 420
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Directory Structure
|
||||
|
||||
```bash
|
||||
# Use custom directories
|
||||
./translation-manager.py \
|
||||
--translations-dir /path/to/translations \
|
||||
--source-dir /path/to/source
|
||||
```
|
||||
|
||||
### Ignore Mark Feature
|
||||
|
||||
For dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"dynamic_key": "Some dynamic value /*keep*/"
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files
|
||||
|
||||
2. **Text extraction limitations**:
|
||||
- ~~Only supports static strings, not dynamically constructed strings~~
|
||||
- Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management.
|
||||
- Must use the `Translation.tr()` format
|
||||
|
||||
3. **File encoding**: All files must use UTF-8 encoding
|
||||
|
||||
4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Q: Text does not appear after adding Translation.tr?**
|
||||
A: You need to import the translation feature in your QML file using `import "root:/"`, otherwise the translation text will not be displayed correctly.
|
||||
|
||||
**Q: The number of extracted texts does not match expectations?**
|
||||
A: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings.
|
||||
|
||||
**Q: Some translations are missing after syncing?**
|
||||
A: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing.
|
||||
|
||||
**Q: The cleaning operation deleted needed keys?**
|
||||
A: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code.
|
||||
|
||||
### Restore Backup
|
||||
|
||||
```bash
|
||||
# Restore a single file
|
||||
cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json
|
||||
|
||||
# Restore all files
|
||||
cp .config/quickshell/translations.backup/* .config/quickshell/translations/
|
||||
```
|
||||
@@ -0,0 +1,155 @@
|
||||
#!/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] <command>"
|
||||
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 " -y, --yes Skip all confirmation prompts (auto-confirm)"
|
||||
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=""
|
||||
YES_FLAG=""
|
||||
|
||||
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
|
||||
;;
|
||||
-y|--yes)
|
||||
YES_FLAG="-y"
|
||||
shift
|
||||
;;
|
||||
-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 $YES_FLAG --extract-only --show-temp
|
||||
;;
|
||||
update)
|
||||
echo "Updating translation files..."
|
||||
if [ -n "$LANG_CODE" ]; then
|
||||
python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS $YES_FLAG --language "$LANG_CODE"
|
||||
else
|
||||
python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS $YES_FLAG
|
||||
fi
|
||||
;;
|
||||
clean)
|
||||
echo "Cleaning unused translation keys..."
|
||||
python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS $YES_FLAG --clean
|
||||
;;
|
||||
sync)
|
||||
echo "Syncing translation keys..."
|
||||
python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS $YES_FLAG --sync
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $COMMAND"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/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, yes_mode: bool = False):
|
||||
"""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)
|
||||
|
||||
# Find unused keys, skip those whose value ends with /*keep*/
|
||||
unused_keys = set()
|
||||
for k in translations.keys():
|
||||
v = translations[k]
|
||||
if k not in current_texts:
|
||||
if isinstance(v, str) and v.strip().endswith('/*keep*/'):
|
||||
continue
|
||||
unused_keys.add(k)
|
||||
|
||||
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")
|
||||
|
||||
if yes_mode:
|
||||
response = 'y'
|
||||
print(f"Delete these {len(unused_keys)} unused keys? (auto-confirmed by --yes)")
|
||||
else:
|
||||
response = input(f"Delete these {len(unused_keys)} unused keys? (y/n): ")
|
||||
if response.lower().strip() in ['y', 'yes']:
|
||||
if backup:
|
||||
# Create backup only when user confirms deletion
|
||||
backup_file = Path(translations_dir) / f"{lang}.json.bak"
|
||||
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}")
|
||||
# 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, yes_mode: bool = False):
|
||||
"""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:
|
||||
if yes_mode:
|
||||
response = 'y'
|
||||
print(f" Delete {len(extra_keys)} extra keys? (auto-confirmed by --yes)")
|
||||
else:
|
||||
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 (ensure UTF-8, fix for special chars)
|
||||
with open(target_file, 'w', encoding='utf-8', newline='') 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")
|
||||
parser.add_argument("-y", "--yes", action="store_true",
|
||||
help="Skip all confirmation prompts (auto-confirm)")
|
||||
|
||||
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, yes_mode=args.yes)
|
||||
elif args.sync:
|
||||
sync_translations(translations_dir, args.source_lang, yes_mode=args.yes)
|
||||
else:
|
||||
print("Please specify an operation:")
|
||||
print(" --clean: Clean unused translation keys")
|
||||
print(" --sync: Sync translation keys")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,334 @@
|
||||
#!/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, yes_mode: bool = False):
|
||||
self.translations_dir = Path(translations_dir)
|
||||
self.source_dir = Path(source_dir)
|
||||
self.temp_extracted_file = None
|
||||
self.yes_mode = yes_mode
|
||||
|
||||
# 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
|
||||
|
||||
try:
|
||||
if '\\u' in text or '\\x' in text:
|
||||
clean_text = bytes(text, "utf-8").decode("unicode_escape")
|
||||
else:
|
||||
clean_text = (
|
||||
text.replace('\\n', '\n')
|
||||
.replace('\\t', '\t')
|
||||
.replace('\\r', '\r')
|
||||
.replace('\\"', '"')
|
||||
.replace('\\\'', "'")
|
||||
.replace('\\f', '\f')
|
||||
.replace('\\b', '\b')
|
||||
.replace('\\\\', '\\')
|
||||
)
|
||||
except Exception:
|
||||
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, create backup only if updating"""
|
||||
translations = self.load_translation_file(lang_code)
|
||||
modified = False
|
||||
backup_created = 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?"):
|
||||
if not backup_created:
|
||||
backup_file = self.translations_dir / f"{lang_code}.json.bak"
|
||||
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}")
|
||||
backup_created = True
|
||||
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:
|
||||
# Only show extra keys that are not marked with /*keep*/
|
||||
filtered_extra_keys = [key for key in extra_keys if not (isinstance(translations.get(key, ""), str) and translations.get(key, "").strip().endswith('/*keep*/'))]
|
||||
if filtered_extra_keys:
|
||||
print(f"\nFound {len(filtered_extra_keys)} extra translation keys:")
|
||||
for i, key in enumerate(sorted(filtered_extra_keys), 1):
|
||||
print(f"{i}. \"{key}\" -> \"{translations.get(key, '')}\"")
|
||||
if self.ask_yes_no(f"\nDelete these {len(filtered_extra_keys)} extra keys?"):
|
||||
if not backup_created:
|
||||
backup_file = self.translations_dir / f"{lang_code}.json.bak"
|
||||
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}")
|
||||
backup_created = True
|
||||
deleted_count = 0
|
||||
for key in filtered_extra_keys:
|
||||
if key in translations:
|
||||
del translations[key]
|
||||
modified = True
|
||||
deleted_count += 1
|
||||
print(f"Deleted {deleted_count} 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, auto-confirm if yes_mode is True"""
|
||||
if getattr(self, "yes_mode", False):
|
||||
print(f"{question} (auto-confirmed by --yes)")
|
||||
return True
|
||||
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/ii",
|
||||
help="Source code directory (default: .config/quickshell/ii)")
|
||||
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")
|
||||
parser.add_argument("-y", "--yes", action="store_true",
|
||||
help="Skip all confirmation prompts (auto-confirm)")
|
||||
|
||||
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, yes_mode=args.yes)
|
||||
|
||||
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:
|
||||
if manager.yes_mode:
|
||||
print("No existing translation files found, auto-skipping language creation due to --yes")
|
||||
return
|
||||
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")
|
||||
if manager.yes_mode:
|
||||
choice = 'a'
|
||||
print("Auto-selecting all languages due to --yes")
|
||||
else:
|
||||
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)}")
|
||||
# Load translation file for current lang to get values
|
||||
current_translations = manager.load_translation_file(lang)
|
||||
filtered_extra_keys = [key for key in extra_keys if not (isinstance(current_translations.get(key, ""), str) and current_translations.get(key, "").strip().endswith('/*keep*/'))]
|
||||
ignored_extra_keys = [key for key in extra_keys if (isinstance(current_translations.get(key, ""), str) and current_translations.get(key, "").strip().endswith('/*keep*/'))]
|
||||
print(f" Extra keys: {len(filtered_extra_keys)}")
|
||||
if ignored_extra_keys:
|
||||
print(f" Ignored keys: {len(ignored_extra_keys)} (marked with /*keep*/)")
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user