feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection

- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru
- Auto-detect system language via navigator.languages; fallback to English
- Add BCP47 locale mapping for date formatting and sorting
- Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each)
- Update language selector with native language names
- Extend locale key parity test to cover all 7 languages (70 assertions)
- Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features
- Add credential encryption, host metrics, session backup crypto, and zmodem bridge
- Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
This commit is contained in:
Logan.Li committed 2026-08-12 00:07:48 +08:00
1 parent fea6d8db04
commit b0d0e3a5f3
301 files changed
+28002 -2705

No files matched your search

+101
View File
@@ -101,6 +101,34 @@ impl IdResponse {
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ShellOutputResponse {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl ShellOutputResponse {
pub fn ok(data: Vec<u8>) -> Self {
Self {
success: true,
data: Some(data),
error: None,
}
}
pub fn error(error: impl Into<String>) -> Self {
Self {
success: false,
data: None,
error: Some(error.into()),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecResponse {
@@ -212,3 +240,76 @@ pub struct SftpProgressEvent {
pub total: u64,
pub percentage: u8,
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HostDiskMetric {
pub mount: String,
pub percent: u8,
pub used_kb: u64,
pub total_kb: u64,
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HostMetrics {
#[serde(skip_serializing_if = "Option::is_none")]
pub os_pretty_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone_offset: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uptime_sec: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_percent: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mem_used_kb: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mem_total_kb: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mem_percent: Option<u8>,
pub disks: Vec<HostDiskMetric>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HostMetricsResponse {
pub success: bool,
/// True when another ssh_exec (e.g. Agent) holds the connection — caller should keep stale UI.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub skipped: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<HostMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl HostMetricsResponse {
pub fn ok(metrics: HostMetrics) -> Self {
Self {
success: true,
skipped: false,
metrics: Some(metrics),
error: None,
}
}
pub fn skipped() -> Self {
Self {
success: true,
skipped: true,
metrics: None,
error: None,
}
}
pub fn error(error: impl Into<String>) -> Self {
Self {
success: false,
skipped: false,
metrics: None,
error: Some(error.into()),
}
}
}