Internationalize UI (EN default) + 5-language switcher
English is now the default language. Added src/i18n.ts (t() helper + dictionary) covering all UI strings across App, Updater and ModelViewer, plus a language selector in Settings for English, Русский, Deutsch, Français, Español (persisted in localStorage). Backend error strings translated to English. Also removed the purple highlight on the resize handles (cursor-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -46,7 +46,7 @@ fn run_json(conn: &Conn, args: &[&str]) -> Result<Vec<Value>, String> {
|
||||
let out = p4_cmd(conn, true)
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|e| format!("Не удалось запустить p4: {e}"))?;
|
||||
.map_err(|e| format!("Failed to start p4: {e}"))?;
|
||||
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
let mut items = Vec::new();
|
||||
@ -134,7 +134,7 @@ async fn p4_login(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Не удалось запустить p4: {e}"))?;
|
||||
.map_err(|e| format!("Failed to start p4: {e}"))?;
|
||||
|
||||
if let Some(si) = child.stdin.as_mut() {
|
||||
si.write_all(format!("{password}\n").as_bytes())
|
||||
@ -149,7 +149,7 @@ async fn p4_login(
|
||||
err.trim().to_string()
|
||||
};
|
||||
return Err(if err.is_empty() {
|
||||
"Ошибка входа (неверный логин или пароль)".into()
|
||||
"Login failed (wrong user or password)".into()
|
||||
} else {
|
||||
err
|
||||
});
|
||||
@ -181,7 +181,7 @@ async fn p4_restore(
|
||||
fn current(state: &State<AppState>) -> Result<Conn, String> {
|
||||
let c = state.0.lock().unwrap().clone();
|
||||
if c.port.is_empty() {
|
||||
return Err("Нет активного подключения".into());
|
||||
return Err("No active connection".into());
|
||||
}
|
||||
Ok(c)
|
||||
}
|
||||
@ -282,7 +282,7 @@ fn run_text(conn: &Conn, args: &[&str]) -> Result<String, String> {
|
||||
let out = p4_cmd(conn, false)
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|e| format!("Не удалось запустить p4: {e}"))?;
|
||||
.map_err(|e| format!("Failed to start p4: {e}"))?;
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||
if !out.status.success() && !stderr.is_empty() {
|
||||
@ -296,7 +296,7 @@ fn run_text(conn: &Conn, args: &[&str]) -> Result<String, String> {
|
||||
async fn p4_sync(state: State<'_, AppState>, scope: String) -> Result<String, String> {
|
||||
let conn = current(&state)?;
|
||||
let msg = run_text(&conn, &["sync", &scope_spec(&scope)])?;
|
||||
Ok(if msg.is_empty() { "Уже актуально — обновлять нечего.".into() } else { msg })
|
||||
Ok(if msg.is_empty() { "Already up to date — nothing to sync.".into() } else { msg })
|
||||
}
|
||||
|
||||
/// Unified diff of an opened file vs. the have revision.
|
||||
@ -329,7 +329,7 @@ fn create_changelist(conn: &Conn, desc: &str) -> Result<String, String> {
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Не удалось запустить p4: {e}"))?;
|
||||
.map_err(|e| format!("Failed to start p4: {e}"))?;
|
||||
if let Some(si) = child.stdin.as_mut() {
|
||||
si.write_all(spec.as_bytes()).map_err(|e| e.to_string())?;
|
||||
}
|
||||
@ -342,7 +342,7 @@ fn create_changelist(conn: &Conn, desc: &str) -> Result<String, String> {
|
||||
s.split_whitespace()
|
||||
.find(|w| w.chars().all(|c| c.is_ascii_digit()) && !w.is_empty())
|
||||
.map(|n| n.to_string())
|
||||
.ok_or_else(|| format!("Не удалось создать changelist: {}", s.trim()))
|
||||
.ok_or_else(|| format!("Failed to create changelist: {}", s.trim()))
|
||||
}
|
||||
|
||||
/// "Commit" (local): create a pending numbered changelist with a description and
|
||||
@ -356,7 +356,7 @@ async fn p4_commit(
|
||||
) -> Result<String, String> {
|
||||
let conn = current(&state)?;
|
||||
if files.is_empty() {
|
||||
return Err("Нет файлов для коммита".into());
|
||||
return Err("No files to commit".into());
|
||||
}
|
||||
let cl = create_changelist(&conn, &description)?;
|
||||
let mut reopen: Vec<&str> = vec!["reopen", "-c", &cl];
|
||||
@ -383,7 +383,7 @@ async fn p4_submit(
|
||||
) -> Result<String, String> {
|
||||
let conn = current(&state)?;
|
||||
if files.is_empty() {
|
||||
return Err("Не выбрано ни одного файла для submit".into());
|
||||
return Err("No files selected to submit".into());
|
||||
}
|
||||
let total = run_json(&conn, &["opened"])?.len();
|
||||
// all opened files selected → submit the whole default changelist in one go
|
||||
@ -478,7 +478,7 @@ async fn open_in_explorer(state: State<'_, AppState>, target: String) -> Result<
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|v| v.get("path").and_then(|x| x.as_str()).map(String::from))
|
||||
.ok_or_else(|| "Не удалось определить локальный путь".to_string())?;
|
||||
.ok_or_else(|| "Could not resolve the local path".to_string())?;
|
||||
// strip the trailing "..." wildcard and separators → the folder itself
|
||||
p.trim_end_matches(['.', '/', '\\']).to_string()
|
||||
} else {
|
||||
@ -486,7 +486,7 @@ async fn open_in_explorer(state: State<'_, AppState>, target: String) -> Result<
|
||||
};
|
||||
let path = std::path::Path::new(&local);
|
||||
if !path.exists() {
|
||||
return Err(format!("Путь ещё не синхронизирован на диск: {local}"));
|
||||
return Err(format!("Path not synced to disk yet: {local}"));
|
||||
}
|
||||
let mut c = Command::new("explorer");
|
||||
#[cfg(windows)]
|
||||
@ -500,7 +500,7 @@ async fn open_in_explorer(state: State<'_, AppState>, target: String) -> Result<
|
||||
} else {
|
||||
c.arg(&local);
|
||||
}
|
||||
c.spawn().map_err(|e| format!("Не удалось открыть проводник: {e}"))?;
|
||||
c.spawn().map_err(|e| format!("Could not open Explorer: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -517,7 +517,7 @@ async fn p4_describe(state: State<'_, AppState>, change: String) -> Result<Value
|
||||
async fn read_file(path: String) -> Result<tauri::ipc::Response, String> {
|
||||
std::fs::read(&path)
|
||||
.map(tauri::ipc::Response::new)
|
||||
.map_err(|e| format!("Не удалось прочитать файл: {e}"))
|
||||
.map_err(|e| format!("Could not read file: {e}"))
|
||||
}
|
||||
|
||||
/// Read the local working-copy bytes of a depot file (resolves via `p4 where`).
|
||||
@ -528,10 +528,10 @@ async fn read_depot(state: State<'_, AppState>, depot: String) -> Result<tauri::
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|v| v.get("path").and_then(|p| p.as_str()).map(String::from))
|
||||
.ok_or_else(|| "Не удалось определить путь файла".to_string())?;
|
||||
.ok_or_else(|| "Could not resolve the file path".to_string())?;
|
||||
std::fs::read(&local)
|
||||
.map(tauri::ipc::Response::new)
|
||||
.map_err(|e| format!("Не удалось прочитать файл: {e}"))
|
||||
.map_err(|e| format!("Could not read file: {e}"))
|
||||
}
|
||||
|
||||
/// Switch the active workspace (client) without re-authenticating.
|
||||
@ -540,7 +540,7 @@ async fn p4_switch_client(state: State<'_, AppState>, client: String) -> Result<
|
||||
let conn = {
|
||||
let mut c = state.0.lock().unwrap();
|
||||
if c.port.is_empty() {
|
||||
return Err("Нет активного подключения".into());
|
||||
return Err("No active connection".into());
|
||||
}
|
||||
c.client = client;
|
||||
c.clone()
|
||||
|
||||
Reference in New Issue
Block a user