Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f2ab65a
perf: optimize sorting with useMemo in translation history dialog
Y-RyuZU Jul 16, 2025
5f864fb
feat(scan): implement TX022 file count-based progress for scan operat…
Y-RyuZU Jul 16, 2025
2dd9c6a
fix(scan): improve scan progress visibility and accuracy
Y-RyuZU Jul 16, 2025
096292a
fix(scan): add progress updates during file analysis phase
Y-RyuZU Jul 16, 2025
74015d0
feat(scan): add small progress bar for scan operations
Y-RyuZU Jul 16, 2025
12be890
fix(scan): improve progress bar layout and alignment
Y-RyuZU Jul 16, 2025
6038368
fix(scan): prevent progress container from resizing with filename length
Y-RyuZU Jul 16, 2025
fa1bf6a
fix(scan): add immediate progress display when scan starts
Y-RyuZU Jul 16, 2025
5c9be53
perf(scan): optimize re-scan performance and improve feedback
Y-RyuZU Jul 16, 2025
76bf224
perf(scan): improve scan performance with better optimizations
Y-RyuZU Jul 16, 2025
29aafee
fix(scan): revert problematic optimizations causing delays
Y-RyuZU Jul 16, 2025
9291cec
refactor(scan): simplify scan function following "Simple over Easy" p…
Y-RyuZU Jul 16, 2025
59d22a2
fix(scan): add intermediate progress updates after file discovery
Y-RyuZU Jul 16, 2025
f2a7243
fix(scan): prevent UI blocking during scan button press
Y-RyuZU Jul 16, 2025
d4112e7
feat(ui): implement comprehensive animation system for better UX
Y-RyuZU Jul 16, 2025
50b7a6c
feat(backend): add translation existence checking and improve error m…
Y-RyuZU Jul 16, 2025
2c83987
fix(config): add PathsConfig interface to AppConfig
Y-RyuZU Jul 16, 2025
d663771
feat(scan): implement file count-based progress bars for scan operations
Y-RyuZU Jul 16, 2025
15c07fd
refactor(json): implement sorted JSON serialization for consistency
Y-RyuZU Jul 16, 2025
88bfa3a
feat(backend): enhance backup system and logging with consistency imp…
Y-RyuZU Jul 16, 2025
4bd5b04
feat(i18n): add translations for scan progress and error messages
Y-RyuZU Jul 16, 2025
9046861
fix(lint): remove unused FileService import from settings-dialog
Y-RyuZU Jul 16, 2025
d4fb54f
fix: apply cargo fmt for Rust formatting compliance
Y-RyuZU Jul 17, 2025
f726f5f
fix(ui): constrain maximum widths for better layout consistency
Y-RyuZU Jul 17, 2025
88c3398
fix(clippy): resolve uninlined_format_args warnings
Y-RyuZU Jul 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"fallback": "Fallback to Entry-Based",
"fallbackHint": "Use entry-based chunking if token estimation fails"
},
"skipExistingTranslations": {
"title": "Skip when translations exist",
"hint": "Skip items that already have target language files (Mods, Quests, Guidebooks only)"
},
"typicalLocation": "Typical location",
"pathSettings": "Path Settings",
"minecraftDirectory": "Minecraft Directory",
Expand Down Expand Up @@ -173,8 +177,17 @@
"selectProfileDirectoryFirst": "Please select a profile directory first",
"noTargetLanguageSelected": "No target language selected. Please select a target language from the dropdown in the translation tab.",
"translationInProgress": "Translation in Progress",
"cannotSwitchTabs": "Cannot switch tabs while translation is in progress. Please wait for the current translation to complete or cancel it.",
"failedToLoad": "Failed to load"
"scanInProgress": "Scan in Progress",
"cannotSwitchTabs": "Cannot switch tabs while operation is in progress. Please wait for it to complete.",
"failedToLoad": "Failed to load",
"directoryNotFound": "Directory not found: {{path}}",
"modsDirectoryNotFound": "Mods directory not found: {{path}}. Please select a valid Minecraft profile directory.",
"questsDirectoryNotFound": "Quests directory not found: {{path}}. Please select a valid Minecraft profile directory containing quest files.",
"guidebooksDirectoryNotFound": "Guidebooks directory not found: {{path}}. Please select a valid Minecraft profile directory containing guidebook files.",
"customFilesDirectoryNotFound": "Custom files directory not found: {{path}}. Please select a valid directory containing JSON or SNBT files.",
"profileDirectoryNotFound": "Profile directory not found: {{path}}. Please select a valid Minecraft profile directory.",
"failedToLoadLogs": "Failed to load session logs",
"noMinecraftDir": "Minecraft directory is not set. Please configure it in settings."
},
"info": {
"translationCancelled": "Translation cancelled by user"
Expand Down Expand Up @@ -240,7 +253,11 @@
"failed": "Failed",
"pending": "Pending",
"in_progress": "In Progress"
}
},
"sessionDetails": "Session Details",
"viewLogs": "View Logs",
"sessionLogs": "Session Logs",
"noLogsFound": "No logs found for this session"
},
"update": {
"title": "Update Available",
Expand Down
23 changes: 20 additions & 3 deletions public/locales/ja/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"fallback": "エントリベースへのフォールバック",
"fallbackHint": "トークン推定が失敗した場合、エントリベースのチャンク分割を使用"
},
"skipExistingTranslations": {
"title": "既存の翻訳がある場合スキップ",
"hint": "対象言語ファイルが既に存在するアイテムをスキップします(Mod、クエスト、ガイドブックのみ)"
},
"typicalLocation": "一般的な場所",
"pathSettings": "パス設定",
"minecraftDirectory": "Minecraftディレクトリ",
Expand Down Expand Up @@ -173,8 +177,17 @@
"selectProfileDirectoryFirst": "最初にプロファイルディレクトリを選択してください",
"noTargetLanguageSelected": "対象言語が選択されていません。翻訳タブのドロップダウンから対象言語を選択してください。",
"translationInProgress": "翻訳中",
"cannotSwitchTabs": "翻訳中はタブを切り替えることができません。現在の翻訳が完了するまでお待ちいただくか、キャンセルしてください。",
"failedToLoad": "読み込みに失敗しました"
"scanInProgress": "スキャン中",
"cannotSwitchTabs": "処理中はタブを切り替えることができません。処理が完了するまでお待ちください。",
"failedToLoad": "読み込みに失敗しました",
"directoryNotFound": "ディレクトリが見つかりません: {{path}}",
"modsDirectoryNotFound": "Modsディレクトリが見つかりません: {{path}}。有効なMinecraftプロファイルディレクトリを選択してください。",
"questsDirectoryNotFound": "クエストディレクトリが見つかりません: {{path}}。クエストファイルを含む有効なMinecraftプロファイルディレクトリを選択してください。",
"guidebooksDirectoryNotFound": "ガイドブックディレクトリが見つかりません: {{path}}。ガイドブックファイルを含む有効なMinecraftプロファイルディレクトリを選択してください。",
"customFilesDirectoryNotFound": "カスタムファイルディレクトリが見つかりません: {{path}}。JSONまたはSNBTファイルを含む有効なディレクトリを選択してください。",
"profileDirectoryNotFound": "プロファイルディレクトリが見つかりません: {{path}}。有効なMinecraftプロファイルディレクトリを選択してください。",
"failedToLoadLogs": "セッションログの読み込みに失敗しました",
"noMinecraftDir": "Minecraftディレクトリが設定されていません。設定で設定してください。"
},
"info": {
"translationCancelled": "翻訳はユーザーによってキャンセルされました"
Expand Down Expand Up @@ -240,7 +253,11 @@
"failed": "失敗",
"pending": "保留中",
"in_progress": "進行中"
}
},
"sessionDetails": "セッション詳細",
"viewLogs": "ログを見る",
"sessionLogs": "セッションログ",
"noLogsFound": "このセッションのログが見つかりません"
},
"update": {
"title": "アップデートが利用可能",
Expand Down
15 changes: 15 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ tauri-build = { version = "2.1.0", features = [] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.4.0", features = [] }
tauri = { version = "2.4.0", features = ["test"] }
tauri-plugin-log = "2.0.0-rc"
tauri-plugin-dialog = "2.0.0"
tauri-plugin-shell = "2.0.0"
Expand All @@ -34,3 +34,7 @@ chrono = "0.4"
dirs = "5.0"
rfd = "0.12"
toml = "0.8"

[dev-dependencies]
tempfile = "3.0"
tokio = { version = "1.0", features = ["full"] }
37 changes: 30 additions & 7 deletions src-tauri/src/backup.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::filesystem::serialize_json_sorted;
use crate::logging::AppLogger;
/**
* Simplified backup module for translation system
Expand All @@ -10,6 +11,28 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use tauri::State;

/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
fn validate_session_id_format(session_id: &str) -> bool {
if session_id.len() != 19 {
return false;
}

let chars: Vec<char> = session_id.chars().collect();

// Check pattern: YYYY-MM-DD_HH-MM-SS
chars[4] == '-' &&
chars[7] == '-' &&
chars[10] == '_' &&
chars[13] == '-' &&
chars[16] == '-' &&
chars[0..4].iter().all(|c| c.is_ascii_digit()) &&
chars[5..7].iter().all(|c| c.is_ascii_digit()) &&
chars[8..10].iter().all(|c| c.is_ascii_digit()) &&
chars[11..13].iter().all(|c| c.is_ascii_digit()) &&
chars[14..16].iter().all(|c| c.is_ascii_digit()) &&
chars[17..19].iter().all(|c| c.is_ascii_digit())
}
Comment on lines +14 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve validation function for better maintainability and efficiency.

The function correctly validates the session ID format, but has several areas for improvement:

  1. Efficiency: Converting to Vec<char> for every validation is wasteful for a simple string format check.
  2. Readability: The manual position checking is verbose and error-prone.
  3. Semantic validation: The function doesn't validate actual date/time ranges (e.g., month 13 would pass).

Consider this more efficient and readable implementation:

-/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
-fn validate_session_id_format(session_id: &str) -> bool {
-    if session_id.len() != 19 {
-        return false;
-    }
-    
-    let chars: Vec<char> = session_id.chars().collect();
-    
-    // Check pattern: YYYY-MM-DD_HH-MM-SS
-    chars[4] == '-' && 
-    chars[7] == '-' && 
-    chars[10] == '_' && 
-    chars[13] == '-' && 
-    chars[16] == '-' &&
-    chars[0..4].iter().all(|c| c.is_ascii_digit()) &&
-    chars[5..7].iter().all(|c| c.is_ascii_digit()) &&
-    chars[8..10].iter().all(|c| c.is_ascii_digit()) &&
-    chars[11..13].iter().all(|c| c.is_ascii_digit()) &&
-    chars[14..16].iter().all(|c| c.is_ascii_digit()) &&
-    chars[17..19].iter().all(|c| c.is_ascii_digit())
-}
+/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
+fn validate_session_id_format(session_id: &str) -> bool {
+    if session_id.len() != 19 {
+        return false;
+    }
+    
+    let bytes = session_id.as_bytes();
+    
+    // Check separators at expected positions
+    if bytes[4] != b'-' || bytes[7] != b'-' || bytes[10] != b'_' || 
+       bytes[13] != b'-' || bytes[16] != b'-' {
+        return false;
+    }
+    
+    // Check that all other positions are ASCII digits
+    let digit_ranges = [(0, 4), (5, 7), (8, 10), (11, 13), (14, 16), (17, 19)];
+    digit_ranges.iter().all(|&(start, end)| {
+        bytes[start..end].iter().all(|&b| b.is_ascii_digit())
+    })
+}

Alternative approach using regex (if regex dependency is acceptable):

+use regex::Regex;
+
+/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
+fn validate_session_id_format(session_id: &str) -> bool {
+    static REGEX: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
+    let regex = REGEX.get_or_init(|| {
+        Regex::new(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$").unwrap()
+    });
+    regex.is_match(session_id)
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
fn validate_session_id_format(session_id: &str) -> bool {
if session_id.len() != 19 {
return false;
}
let chars: Vec<char> = session_id.chars().collect();
// Check pattern: YYYY-MM-DD_HH-MM-SS
chars[4] == '-' &&
chars[7] == '-' &&
chars[10] == '_' &&
chars[13] == '-' &&
chars[16] == '-' &&
chars[0..4].iter().all(|c| c.is_ascii_digit()) &&
chars[5..7].iter().all(|c| c.is_ascii_digit()) &&
chars[8..10].iter().all(|c| c.is_ascii_digit()) &&
chars[11..13].iter().all(|c| c.is_ascii_digit()) &&
chars[14..16].iter().all(|c| c.is_ascii_digit()) &&
chars[17..19].iter().all(|c| c.is_ascii_digit())
}
/// Validate session ID format: YYYY-MM-DD_HH-MM-SS
fn validate_session_id_format(session_id: &str) -> bool {
if session_id.len() != 19 {
return false;
}
let bytes = session_id.as_bytes();
// Check separators at expected positions
if bytes[4] != b'-' || bytes[7] != b'-' || bytes[10] != b'_'
|| bytes[13] != b'-' || bytes[16] != b'-' {
return false;
}
// Check that all other positions are ASCII digits
let digit_ranges = [(0, 4), (5, 7), (8, 10), (11, 13), (14, 16), (17, 19)];
digit_ranges.iter().all(|&(start, end)| {
bytes[start..end].iter().all(|&b| b.is_ascii_digit())
})
}
🤖 Prompt for AI Agents
In src-tauri/src/backup.rs between lines 14 and 34, the
validate_session_id_format function inefficiently converts the input string to a
Vec<char> and manually checks each character position, which is verbose and
error-prone. To fix this, replace the manual checks with a regex pattern that
matches the exact format YYYY-MM-DD_HH-MM-SS, improving readability and
maintainability. Additionally, enhance the function to parse the date and time
components and validate their semantic correctness (e.g., month between 1 and
12, day valid for the month, hour between 0 and 23, etc.) to ensure the session
ID represents a valid datetime.


/// Backup metadata structure matching TypeScript interface
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -117,9 +140,9 @@ pub fn create_backup(
}
}

// Save metadata
// Save metadata with sorted keys
let metadata_path = backup_dir.join("metadata.json");
let metadata_json = serde_json::to_string_pretty(&metadata)
let metadata_json = serialize_json_sorted(&metadata)
.map_err(|e| format!("Failed to serialize backup metadata: {e}"))?;

fs::write(&metadata_path, metadata_json)
Expand Down Expand Up @@ -309,8 +332,8 @@ pub async fn list_translation_sessions(minecraft_dir: String) -> Result<Vec<Stri
if path.is_dir() {
if let Some(dir_name) = path.file_name() {
if let Some(name_str) = dir_name.to_str() {
// Simple validation: check if it matches YYYY-MM-DD_HH-MM-SS format
if name_str.len() == 19 && name_str.chars().nth(10) == Some('_') {
// Validate session ID format: YYYY-MM-DD_HH-MM-SS
if validate_session_id_format(name_str) {
sessions.push(name_str.to_string());
}
}
Expand Down Expand Up @@ -400,9 +423,9 @@ pub async fn update_translation_summary(

summary.translations.push(entry);

// Write updated summary back to file
let json = serde_json::to_string_pretty(&summary)
.map_err(|e| format!("Failed to serialize summary: {e}"))?;
// Write updated summary back to file with sorted keys
let json =
serialize_json_sorted(&summary).map_err(|e| format!("Failed to serialize summary: {e}"))?;

fs::write(&summary_path, json).map_err(|e| format!("Failed to write summary file: {e}"))?;

Expand Down
13 changes: 7 additions & 6 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::filesystem::serialize_json_sorted;
use log::{error, info};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
Expand Down Expand Up @@ -161,8 +162,8 @@ pub fn load_config() -> std::result::Result<String, String> {
// Create a default config
let default_config = default_config();

// Serialize the default config
let config_json = match serde_json::to_string_pretty(&default_config) {
// Serialize the default config with sorted keys
let config_json = match serialize_json_sorted(&default_config) {
Ok(json) => json,
Err(e) => return Err(format!("Failed to serialize default config: {e}")),
};
Expand Down Expand Up @@ -201,8 +202,8 @@ pub fn load_config() -> std::result::Result<String, String> {

// TODO: Update the config with any missing fields from default_config()

// Serialize the updated config
let updated_config_json = match serde_json::to_string_pretty(&config) {
// Serialize the updated config with sorted keys
let updated_config_json = match serialize_json_sorted(&config) {
Ok(json) => json,
Err(e) => return Err(format!("Failed to serialize updated config: {e}")),
};
Expand Down Expand Up @@ -233,8 +234,8 @@ pub fn save_config(config_json: &str) -> std::result::Result<bool, String> {
Err(e) => return Err(format!("Failed to create config file: {e}")),
};

// Serialize the config
let config_json = match serde_json::to_string_pretty(&config) {
// Serialize the config with sorted keys
let config_json = match serialize_json_sorted(&config) {
Ok(json) => json,
Err(e) => return Err(format!("Failed to serialize config: {e}")),
};
Expand Down
Loading
Loading