Skip to content

Commit ab74751

Browse files
Y-RyuZUclaude
andcommitted
feat: add translation existence checking and improve mod selection
- Add backend support for checking existing translations in mod JAR files - Implement `check_mod_translation_exists` Tauri command with comprehensive tests - Add debug component for testing translation detection functionality - Fix placeholder replacement bug in completion dialog (completed/total params) - Add support for tracking existing translations in mod scan results - Improve translation targeting with pre-translation existence checks - Add integration tests for mod translation flow - Fix Rust formatting issues to pass CI validation This enhancement allows the application to detect which mods already have translations, enabling smarter translation workflows and preventing unnecessary API calls. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 2cf51be commit ab74751

File tree

18 files changed

+1872
-67
lines changed

18 files changed

+1872
-67
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Translation Detection Fix Plan
2+
3+
## Summary
4+
5+
We have successfully created and fixed both frontend and backend tests for the mod translation detection feature. All tests are now passing with proper mock data.
6+
7+
## What Was Fixed
8+
9+
### 1. Frontend Tests
10+
- **Problem**: Tests were using Vitest syntax but the project uses Jest
11+
- **Solution**: Converted all tests to use Jest syntax and mocking
12+
- **Location**: `/src/__tests__/services/mod-translation-check.test.ts`
13+
- **Key Changes**:
14+
- Replaced `vi.fn()` with `jest.fn()`
15+
- Used `FileService.setTestInvokeOverride()` for proper mocking
16+
- Removed Vitest imports and replaced with Jest equivalents
17+
18+
### 2. Backend Tests
19+
- **Problem**: Limited test coverage for edge cases
20+
- **Solution**: Added comprehensive test cases including:
21+
- Special characters in mod IDs
22+
- Empty language codes
23+
- Performance testing with large JARs
24+
- Concurrent access testing
25+
- Nested JAR handling
26+
- **Location**: `/src-tauri/src/minecraft/mod_translation_test.rs`
27+
- **Test Count**: 13 comprehensive test cases
28+
29+
### 3. Integration Tests
30+
- **Created**: New integration test suite
31+
- **Location**: `/src/__tests__/integration/mod-translation-flow.test.ts`
32+
- **Coverage**:
33+
- Complete translation detection flow
34+
- Different target language handling
35+
- Configuration handling (skipExistingTranslations)
36+
- Error handling throughout the flow
37+
- Performance and concurrency testing
38+
39+
## Test Results
40+
41+
All tests are now passing:
42+
- Frontend tests: 9 tests passing
43+
- Backend tests: 13 tests passing
44+
- Integration tests: 5 tests passing
45+
- Total: 66 tests passing across all test files
46+
47+
## Next Steps for Debugging "New" vs "Exists" Issue
48+
49+
If translations are still showing as "New" when they should show "Exists", use these debugging steps:
50+
51+
### 1. Use the Debug Component
52+
```tsx
53+
// Add to a test page
54+
import { TranslationCheckDebug } from "@/components/debug/translation-check-debug";
55+
56+
export default function DebugPage() {
57+
return <TranslationCheckDebug />;
58+
}
59+
```
60+
61+
### 2. Backend Debug Command
62+
The backend includes a debug command that provides detailed information:
63+
```rust
64+
// Available at: debug_mod_translation_check
65+
// Returns detailed info about language files in the JAR
66+
```
67+
68+
### 3. Common Issues to Check
69+
70+
1. **Case Sensitivity**: The detection is case-insensitive, but verify the language codes match
71+
2. **Path Structure**: Ensure files are at `assets/{mod_id}/lang/{language}.{json|lang}`
72+
3. **Mod ID Mismatch**: Verify the mod ID used in detection matches the actual mod structure
73+
4. **File Format**: Both `.json` and `.lang` formats are supported
74+
75+
### 4. Manual Verification Steps
76+
77+
1. Extract the JAR file and check the structure:
78+
```bash
79+
unzip -l mod.jar | grep -E "assets/.*/lang/"
80+
```
81+
82+
2. Verify the mod ID in fabric.mod.json or mods.toml:
83+
```bash
84+
unzip -p mod.jar fabric.mod.json | jq '.id'
85+
```
86+
87+
3. Check if the language file path matches expected pattern:
88+
```
89+
assets/{mod_id}/lang/{language_code}.json
90+
assets/{mod_id}/lang/{language_code}.lang
91+
```
92+
93+
## Code Quality Improvements
94+
95+
1. **Type Safety**: All mock data is properly typed
96+
2. **Test Coverage**: Edge cases and error scenarios are covered
97+
3. **Performance**: Tests include performance benchmarks
98+
4. **Concurrency**: Tests verify thread-safe operation
99+
100+
## Conclusion
101+
102+
The test suite is now comprehensive and all tests are passing. If the "New" vs "Exists" issue persists in production, use the debug tools and manual verification steps to identify the root cause.

public/locales/en/common.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,13 @@
126126
"settings": "Settings"
127127
},
128128
"buttons": {
129-
"scanMods": "Scan Mods",
130-
"scanQuests": "Scan Quests",
131-
"scanGuidebooks": "Scan Guidebooks",
132-
"scanFiles": "Scan Files",
129+
"scanMods": "Scan",
130+
"scanQuests": "Scan",
131+
"scanGuidebooks": "Scan",
132+
"scanFiles": "Scan",
133133
"selectDirectory": "Select Directory",
134-
"selectProfileDirectory": "Select Profile Directory",
135-
"translate": "Translate Selected",
134+
"selectProfileDirectory": "Select Profile",
135+
"translate": "Translate",
136136
"translating": "Translating...",
137137
"scanning": "Scanning...",
138138
"cancel": "Cancel"
@@ -151,14 +151,14 @@
151151
"fileName": "File Name",
152152
"type": "Type",
153153
"path": "Path",
154-
"noModsFound": "No mods found. Click 'Scan Mods' to scan for mods.",
155-
"noQuestsFound": "No quests found. Click 'Scan Quests' to scan for quests.",
156-
"noGuidebooksFound": "No guidebooks found. Click 'Scan Guidebooks' to scan for guidebooks.",
157-
"noFilesFound": "No files found. Click 'Scan Files' to scan for JSON and SNBT files.",
158-
"scanningForMods": "Scanning for mods...",
159-
"scanningForQuests": "Scanning for quests...",
160-
"scanningForGuidebooks": "Scanning for guidebooks...",
161-
"scanningForFiles": "Scanning for files..."
154+
"noModsFound": "No mods found. Click 'Scan' to scan for mods.",
155+
"noQuestsFound": "No quests found. Click 'Scan' to scan for quests.",
156+
"noGuidebooksFound": "No guidebooks found. Click 'Scan' to scan for guidebooks.",
157+
"noFilesFound": "No files found. Click 'Scan' to scan for JSON and SNBT files.",
158+
"scanningForMods": "Scanning...",
159+
"scanningForQuests": "Scanning...",
160+
"scanningForGuidebooks": "Scanning...",
161+
"scanningForFiles": "Scanning..."
162162
},
163163
"progress": {
164164
"translatingMods": "Translating mods...",

public/locales/ja/common.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,13 @@
126126
"settings": "設定"
127127
},
128128
"buttons": {
129-
"scanMods": "Modをスキャン",
130-
"scanQuests": "クエストをスキャン",
131-
"scanGuidebooks": "ガイドブックをスキャン",
132-
"scanFiles": "ファイルをスキャン",
129+
"scanMods": "スキャン",
130+
"scanQuests": "スキャン",
131+
"scanGuidebooks": "スキャン",
132+
"scanFiles": "スキャン",
133133
"selectDirectory": "ディレクトリを選択",
134-
"selectProfileDirectory": "プロファイルディレクトリを選択",
135-
"translate": "選択したものを翻訳",
134+
"selectProfileDirectory": "プロファイルを選択",
135+
"translate": "翻訳",
136136
"translating": "翻訳中...",
137137
"scanning": "スキャン中...",
138138
"cancel": "キャンセル"
@@ -151,14 +151,14 @@
151151
"fileName": "ファイル名",
152152
"type": "タイプ",
153153
"path": "パス",
154-
"noModsFound": "Modが見つかりません。「Modをスキャン」をクリックしてModをスキャンしてください。",
155-
"noQuestsFound": "クエストが見つかりません。「クエストをスキャン」をクリックしてクエストをスキャンしてください。",
156-
"noGuidebooksFound": "ガイドブックが見つかりません。「ガイドブックをスキャン」をクリックしてガイドブックをスキャンしてください。",
157-
"noFilesFound": "ファイルが見つかりません。「ファイルをスキャン」をクリックしてJSONとSNBTファイルをスキャンしてください。",
158-
"scanningForMods": "Modをスキャン中...",
159-
"scanningForQuests": "クエストをスキャン中...",
160-
"scanningForGuidebooks": "ガイドブックをスキャン中...",
161-
"scanningForFiles": "ファイルをスキャン中..."
154+
"noModsFound": "Modが見つかりません。「スキャン」をクリックしてModをスキャンしてください。",
155+
"noQuestsFound": "クエストが見つかりません。「スキャン」をクリックしてクエストをスキャンしてください。",
156+
"noGuidebooksFound": "ガイドブックが見つかりません。「スキャン」をクリックしてガイドブックをスキャンしてください。",
157+
"noFilesFound": "ファイルが見つかりません。「スキャン」をクリックしてJSONとSNBTファイルをスキャンしてください。",
158+
"scanningForMods": "スキャン中...",
159+
"scanningForQuests": "スキャン中...",
160+
"scanningForGuidebooks": "スキャン中...",
161+
"scanningForFiles": "スキャン中..."
162162
},
163163
"progress": {
164164
"translatingMods": "Modを翻訳中...",

src-tauri/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ use minecraft::{
3131
extract_patchouli_books, write_patchouli_book,
3232
};
3333

34+
#[cfg(debug_assertions)]
35+
use minecraft::debug_translation_check::debug_mod_translation_check;
36+
3437
#[cfg_attr(mobile, tauri::mobile_entry_point)]
3538
pub fn run() {
3639
// Initialize the logger
@@ -128,7 +131,10 @@ pub fn run() {
128131
list_translation_sessions,
129132
get_translation_summary,
130133
update_translation_summary,
131-
batch_update_translation_summary
134+
batch_update_translation_summary,
135+
// Debug commands (only in debug builds)
136+
#[cfg(debug_assertions)]
137+
debug_mod_translation_check
132138
])
133139
.run(tauri::generate_context!())
134140
.expect("error while running tauri application");
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use super::check_mod_translation_exists;
2+
use std::path::Path;
3+
4+
/// Debug function to test translation detection on a real mod file
5+
pub async fn debug_check_translation(mod_path: &str, mod_id: &str) {
6+
println!("\n=== Debug Translation Check ===");
7+
println!("Mod Path: {}", mod_path);
8+
println!("Mod ID: {}", mod_id);
9+
println!("File exists: {}", Path::new(mod_path).exists());
10+
11+
let test_languages = vec![
12+
"ja_jp", "JA_JP", "ja_JP", // Test case variations
13+
"zh_cn", "ko_kr", "de_de", "fr_fr", "es_es",
14+
];
15+
16+
println!("\nChecking translations:");
17+
for lang in test_languages {
18+
match check_mod_translation_exists(mod_path, mod_id, lang).await {
19+
Ok(exists) => {
20+
println!(
21+
" {} - {}",
22+
lang,
23+
if exists { "EXISTS" } else { "NOT FOUND" }
24+
);
25+
}
26+
Err(e) => {
27+
println!(" {} - ERROR: {}", lang, e);
28+
}
29+
}
30+
}
31+
32+
// Additional debug: List all files in the JAR that match lang pattern
33+
println!("\nAttempting to list language files in JAR:");
34+
if let Ok(file) = std::fs::File::open(mod_path) {
35+
if let Ok(mut archive) = zip::ZipArchive::new(file) {
36+
for i in 0..archive.len() {
37+
if let Ok(file) = archive.by_index(i) {
38+
let name = file.name();
39+
if name.contains("/lang/")
40+
&& (name.ends_with(".json") || name.ends_with(".lang"))
41+
{
42+
println!(" Found: {}", name);
43+
}
44+
}
45+
}
46+
} else {
47+
println!(" ERROR: Failed to read as ZIP archive");
48+
}
49+
} else {
50+
println!(" ERROR: Failed to open file");
51+
}
52+
53+
println!("==============================\n");
54+
}
55+
56+
/// Command to run debug check from CLI
57+
#[tauri::command]
58+
pub async fn debug_mod_translation_check(
59+
mod_path: String,
60+
mod_id: String,
61+
) -> Result<String, String> {
62+
let mut output = String::new();
63+
64+
output.push_str(&format!("Debug Translation Check for: {}\n", mod_path));
65+
output.push_str(&format!("Mod ID: {}\n", mod_id));
66+
output.push_str(&format!(
67+
"File exists: {}\n\n",
68+
Path::new(&mod_path).exists()
69+
));
70+
71+
// Check various language codes
72+
let languages = vec!["ja_jp", "JA_JP", "zh_cn", "ko_kr", "en_us"];
73+
74+
for lang in languages {
75+
match check_mod_translation_exists(&mod_path, &mod_id, lang).await {
76+
Ok(exists) => {
77+
output.push_str(&format!(
78+
"{}: {}\n",
79+
lang,
80+
if exists { "EXISTS" } else { "NOT FOUND" }
81+
));
82+
}
83+
Err(e) => {
84+
output.push_str(&format!("{}: ERROR - {}\n", lang, e));
85+
}
86+
}
87+
}
88+
89+
// List all language files
90+
output.push_str("\nLanguage files in JAR:\n");
91+
if let Ok(file) = std::fs::File::open(&mod_path) {
92+
if let Ok(mut archive) = zip::ZipArchive::new(file) {
93+
let mut found_any = false;
94+
for i in 0..archive.len() {
95+
if let Ok(file) = archive.by_index(i) {
96+
let name = file.name();
97+
if name.contains("/lang/")
98+
&& (name.ends_with(".json") || name.ends_with(".lang"))
99+
{
100+
output.push_str(&format!(" - {}\n", name));
101+
found_any = true;
102+
103+
// Check if this matches the expected pattern
104+
let expected_pattern = format!("assets/{}/lang/", mod_id);
105+
if name.starts_with(&expected_pattern) {
106+
output.push_str(" ✓ Matches expected pattern\n");
107+
} else {
108+
output.push_str(" ✗ Does NOT match expected pattern\n");
109+
}
110+
}
111+
}
112+
}
113+
if !found_any {
114+
output.push_str(" No language files found\n");
115+
}
116+
} else {
117+
output.push_str(" ERROR: Not a valid ZIP/JAR file\n");
118+
}
119+
} else {
120+
output.push_str(" ERROR: File not found or cannot be opened\n");
121+
}
122+
123+
Ok(output)
124+
}

src-tauri/src/minecraft/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,3 +977,12 @@ pub async fn detect_snbt_content_type(file_path: &str) -> Result<String, String>
977977
Ok("direct_text".to_string())
978978
}
979979
}
980+
981+
// Include tests module
982+
#[cfg(test)]
983+
#[path = "mod_translation_test.rs"]
984+
mod mod_translation_test;
985+
986+
// Debug module
987+
#[cfg(debug_assertions)]
988+
pub mod debug_translation_check;

0 commit comments

Comments
 (0)