From f06360a59d514804a75a2827f9f949ab6e7464ca Mon Sep 17 00:00:00 2001 From: xintaofei Date: Tue, 31 Mar 2026 13:49:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=B8=A0=E9=81=93=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=94=AF=E6=8C=81=E5=A4=9A=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/chat_channel/command_dispatcher.rs | 46 +- .../src/chat_channel/command_handlers.rs | 115 ++- .../src/chat_channel/event_subscriber.rs | 26 +- src-tauri/src/chat_channel/i18n.rs | 795 ++++++++++++++++++ .../src/chat_channel/message_formatter.rs | 48 +- src-tauri/src/chat_channel/mod.rs | 1 + src-tauri/src/chat_channel/scheduler.rs | 18 +- src-tauri/src/commands/chat_channel.rs | 53 ++ src-tauri/src/lib.rs | 2 + src-tauri/src/web/handlers/chat_channel.rs | 21 + src-tauri/src/web/router.rs | 2 + src/components/settings/channel-other-tab.tsx | 91 ++ .../settings/chat-channel-settings.tsx | 5 + src/i18n/messages/ar.json | 19 +- src/i18n/messages/de.json | 19 +- src/i18n/messages/en.json | 19 +- src/i18n/messages/es.json | 19 +- src/i18n/messages/fr.json | 19 +- src/i18n/messages/ja.json | 19 +- src/i18n/messages/ko.json | 19 +- src/i18n/messages/pt.json | 19 +- src/i18n/messages/zh-CN.json | 19 +- src/i18n/messages/zh-TW.json | 19 +- src/lib/api.ts | 8 + 24 files changed, 1319 insertions(+), 102 deletions(-) create mode 100644 src-tauri/src/chat_channel/i18n.rs create mode 100644 src/components/settings/channel-other-tab.tsx diff --git a/src-tauri/src/chat_channel/command_dispatcher.rs b/src-tauri/src/chat_channel/command_dispatcher.rs index fa4e471..c8c6cba 100644 --- a/src-tauri/src/chat_channel/command_dispatcher.rs +++ b/src-tauri/src/chat_channel/command_dispatcher.rs @@ -3,12 +3,14 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use super::command_handlers; +use super::i18n::{self, Lang}; use super::manager::ChatChannelManager; use super::types::IncomingCommand; use crate::db::service::{app_metadata_service, chat_channel_message_log_service}; const COMMAND_PREFIX_KEY: &str = "chat_command_prefix"; const DEFAULT_COMMAND_PREFIX: &str = "/"; +const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language"; pub fn spawn_command_dispatcher( mut command_rx: mpsc::Receiver, @@ -37,7 +39,9 @@ pub fn spawn_command_dispatcher( .flatten() .unwrap_or_else(|| DEFAULT_COMMAND_PREFIX.to_string()); - let response = dispatch_command(text, &prefix, &db_conn, &manager).await; + let lang = load_lang(&db_conn).await; + + let response = dispatch_command(text, &prefix, &db_conn, &manager, lang).await; // Send response back via the same channel let send_result = manager.send_to_channel(cmd.channel_id, &response).await; @@ -66,15 +70,25 @@ pub fn spawn_command_dispatcher( }) } +async fn load_lang(db: &DatabaseConnection) -> Lang { + app_metadata_service::get_value(db, MESSAGE_LANGUAGE_KEY) + .await + .ok() + .flatten() + .map(|v| Lang::from_str_lossy(&v)) + .unwrap_or_default() +} + async fn dispatch_command( text: &str, prefix: &str, db: &DatabaseConnection, manager: &ChatChannelManager, + lang: Lang, ) -> super::types::RichMessage { // Check if text starts with the configured prefix if !text.starts_with(prefix) { - return command_handlers::handle_help(prefix); + return command_handlers::handle_help(prefix, lang); } // Strip prefix and parse command + args @@ -84,31 +98,27 @@ async fn dispatch_command( let args = parts.get(1).map(|s| s.trim()).unwrap_or(""); match command.as_str() { - "recent" => command_handlers::handle_recent(db).await, + "recent" => command_handlers::handle_recent(db, lang).await, "search" => { if args.is_empty() { - super::types::RichMessage::info(format!("用法: {prefix}search <关键词>")) - .with_title("参数错误") + super::types::RichMessage::info(i18n::search_usage(lang, prefix)) + .with_title(i18n::invalid_args_title(lang)) } else { - command_handlers::handle_search(db, args).await + command_handlers::handle_search(db, args, lang).await } } "detail" => { if let Ok(id) = args.parse::() { - command_handlers::handle_detail(db, id).await + command_handlers::handle_detail(db, id, lang).await } else { - super::types::RichMessage::info(format!("用法: {prefix}detail <会话ID>")) - .with_title("参数错误") + super::types::RichMessage::info(i18n::detail_usage(lang, prefix)) + .with_title(i18n::invalid_args_title(lang)) } } - "today" => command_handlers::handle_today(db).await, - "status" => command_handlers::handle_status(manager).await, - "help" | "start" => command_handlers::handle_help(prefix), - _ => { - super::types::RichMessage::info(format!( - "未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令", - )) - .with_title("未知命令") - } + "today" => command_handlers::handle_today(db, lang).await, + "status" => command_handlers::handle_status(manager, lang).await, + "help" | "start" => command_handlers::handle_help(prefix, lang), + _ => super::types::RichMessage::info(i18n::unknown_command(lang, prefix, &command)) + .with_title(i18n::unknown_command_title(lang)), } } diff --git a/src-tauri/src/chat_channel/command_handlers.rs b/src-tauri/src/chat_channel/command_handlers.rs index 69dadc0..956238d 100644 --- a/src-tauri/src/chat_channel/command_handlers.rs +++ b/src-tauri/src/chat_channel/command_handlers.rs @@ -1,11 +1,12 @@ use chrono::Utc; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder}; +use super::i18n::{self, Lang}; use super::manager::ChatChannelManager; use super::types::{MessageLevel, RichMessage}; use crate::db::entities::conversation; -pub async fn handle_recent(db: &DatabaseConnection) -> RichMessage { +pub async fn handle_recent(db: &DatabaseConnection, lang: Lang) -> RichMessage { let rows = match conversation::Entity::find() .filter(conversation::Column::DeletedAt.is_null()) .order_by_desc(conversation::Column::CreatedAt) @@ -15,7 +16,7 @@ pub async fn handle_recent(db: &DatabaseConnection) -> RichMessage { Ok(rows) => rows, Err(e) => { return RichMessage { - title: Some("查询失败".to_string()), + title: Some(i18n::query_failed_title(lang).to_string()), body: e.to_string(), fields: Vec::new(), level: MessageLevel::Error, @@ -25,12 +26,13 @@ pub async fn handle_recent(db: &DatabaseConnection) -> RichMessage { let recent: Vec<_> = rows.into_iter().take(5).collect(); if recent.is_empty() { - return RichMessage::info("暂无会话记录").with_title("最近会话"); + return RichMessage::info(i18n::no_conversations(lang)) + .with_title(i18n::recent_conversations_title(lang)); } let mut body = String::new(); for (i, conv) in recent.iter().enumerate() { - let title = conv.title.as_deref().unwrap_or("(无标题)"); + let title = conv.title.as_deref().unwrap_or(i18n::untitled(lang)); let agent = &conv.agent_type; let time = conv.created_at.format("%m-%d %H:%M"); body.push_str(&format!( @@ -42,10 +44,15 @@ pub async fn handle_recent(db: &DatabaseConnection) -> RichMessage { )); } - RichMessage::info(body.trim_end()).with_title("最近 5 条会话") + RichMessage::info(body.trim_end()) + .with_title(i18n::recent_n_conversations_title(lang, recent.len())) } -pub async fn handle_search(db: &DatabaseConnection, keyword: &str) -> RichMessage { +pub async fn handle_search( + db: &DatabaseConnection, + keyword: &str, + lang: Lang, +) -> RichMessage { let rows = match conversation::Entity::find() .filter(conversation::Column::DeletedAt.is_null()) .order_by_desc(conversation::Column::CreatedAt) @@ -55,7 +62,7 @@ pub async fn handle_search(db: &DatabaseConnection, keyword: &str) -> RichMessag Ok(rows) => rows, Err(e) => { return RichMessage { - title: Some("查询失败".to_string()), + title: Some(i18n::query_failed_title(lang).to_string()), body: e.to_string(), fields: Vec::new(), level: MessageLevel::Error, @@ -76,22 +83,35 @@ pub async fn handle_search(db: &DatabaseConnection, keyword: &str) -> RichMessag .collect(); if matched.is_empty() { - return RichMessage::info(format!("未找到包含 \"{keyword}\" 的会话")) - .with_title("搜索结果"); + return RichMessage::info(i18n::search_no_results(lang, keyword)) + .with_title(i18n::search_results_title(lang)); } let mut body = String::new(); for (i, conv) in matched.iter().enumerate() { - let title = conv.title.as_deref().unwrap_or("(无标题)"); + let title = conv.title.as_deref().unwrap_or(i18n::untitled(lang)); let agent = &conv.agent_type; - body.push_str(&format!("{}. [{}] {} (ID:{})\n", i + 1, agent, title, conv.id)); + body.push_str(&format!( + "{}. [{}] {} (ID:{})\n", + i + 1, + agent, + title, + conv.id + )); } - RichMessage::info(body.trim_end()) - .with_title(format!("搜索 \"{}\" - {} 条结果", keyword, matched.len())) + RichMessage::info(body.trim_end()).with_title(i18n::search_results_count_title( + lang, + keyword, + matched.len(), + )) } -pub async fn handle_detail(db: &DatabaseConnection, conversation_id: i32) -> RichMessage { +pub async fn handle_detail( + db: &DatabaseConnection, + conversation_id: i32, + lang: Lang, +) -> RichMessage { let conv = match conversation::Entity::find_by_id(conversation_id) .filter(conversation::Column::DeletedAt.is_null()) .one(db) @@ -99,12 +119,12 @@ pub async fn handle_detail(db: &DatabaseConnection, conversation_id: i32) -> Ric { Ok(Some(c)) => c, Ok(None) => { - return RichMessage::info(format!("会话 {conversation_id} 不存在")) - .with_title("未找到"); + return RichMessage::info(i18n::conversation_not_found(lang, conversation_id)) + .with_title(i18n::not_found_title(lang)); } Err(e) => { return RichMessage { - title: Some("查询失败".to_string()), + title: Some(i18n::query_failed_title(lang).to_string()), body: e.to_string(), fields: Vec::new(), level: MessageLevel::Error, @@ -112,16 +132,22 @@ pub async fn handle_detail(db: &DatabaseConnection, conversation_id: i32) -> Ric } }; - let title = conv.title.as_deref().unwrap_or("(无标题)"); + let title = conv.title.as_deref().unwrap_or(i18n::untitled(lang)); RichMessage::info(title) - .with_title(format!("会话详情 #{}", conv.id)) - .with_field("代理", &conv.agent_type) - .with_field("状态", format!("{:?}", conv.status)) - .with_field("消息数", conv.message_count.to_string()) - .with_field("创建时间", conv.created_at.format("%Y-%m-%d %H:%M").to_string()) + .with_title(i18n::conversation_detail_title(lang, conv.id)) + .with_field(i18n::field_agent(lang), &conv.agent_type) + .with_field(i18n::field_status(lang), format!("{:?}", conv.status)) + .with_field( + i18n::field_message_count(lang), + conv.message_count.to_string(), + ) + .with_field( + i18n::field_created_at(lang), + conv.created_at.format("%Y-%m-%d %H:%M").to_string(), + ) } -pub async fn handle_today(db: &DatabaseConnection) -> RichMessage { +pub async fn handle_today(db: &DatabaseConnection, lang: Lang) -> RichMessage { let now = Utc::now(); let today_start = now .date_naive() @@ -139,7 +165,7 @@ pub async fn handle_today(db: &DatabaseConnection) -> RichMessage { Ok(rows) => rows, Err(e) => { return RichMessage { - title: Some("查询失败".to_string()), + title: Some(i18n::query_failed_title(lang).to_string()), body: e.to_string(), fields: Vec::new(), level: MessageLevel::Error, @@ -148,7 +174,8 @@ pub async fn handle_today(db: &DatabaseConnection) -> RichMessage { }; if rows.is_empty() { - return RichMessage::info("今日暂无编码活动").with_title("今日活动"); + return RichMessage::info(i18n::no_activity_today(lang)) + .with_title(i18n::today_activity_title(lang)); } // Group by agent_type @@ -163,29 +190,33 @@ pub async fn handle_today(db: &DatabaseConnection) -> RichMessage { } } - let mut body = format!("会话总数: {}", rows.len()); - body.push_str("\n\n按代理:"); + let mut body = i18n::total_sessions(lang, rows.len() as u32); + body.push_str(&format!("\n\n{}", i18n::by_agent_label(lang))); for (agent, count) in &by_agent { - body.push_str(&format!("\n {agent} - {count} 个")); + body.push_str(&format!( + "\n {}", + i18n::agent_count(lang, agent, *count) + )); } if !titles.is_empty() { - body.push_str("\n\n最近活动:"); + body.push_str(&format!("\n\n{}", i18n::recent_activity_label(lang))); for t in &titles { body.push_str(&format!("\n • {t}")); } } - RichMessage::info(body).with_title(format!( - "今日活动 ({})", - now.format("%Y-%m-%d") + RichMessage::info(body).with_title(i18n::today_activity_date_title( + lang, + &now.format("%Y-%m-%d").to_string(), )) } -pub async fn handle_status(manager: &ChatChannelManager) -> RichMessage { +pub async fn handle_status(manager: &ChatChannelManager, lang: Lang) -> RichMessage { let statuses = manager.get_status().await; if statuses.is_empty() { - return RichMessage::info("暂无活跃渠道").with_title("渠道状态"); + return RichMessage::info(i18n::no_active_channels(lang)) + .with_title(i18n::channel_status_title(lang)); } let mut body = String::new(); @@ -202,17 +233,9 @@ pub async fn handle_status(manager: &ChatChannelManager) -> RichMessage { )); } - RichMessage::info(body.trim_end()).with_title("渠道状态") + RichMessage::info(body.trim_end()).with_title(i18n::channel_status_title(lang)) } -pub fn handle_help(prefix: &str) -> RichMessage { - RichMessage::info(format!( - "{prefix}recent - 最近 5 条会话\n\ - {prefix}search <关键词> - 搜索会话\n\ - {prefix}detail - 会话详情\n\ - {prefix}today - 今日活动汇总\n\ - {prefix}status - 渠道连接状态\n\ - {prefix}help - 显示帮助", - )) - .with_title("Codeg Bot 帮助") +pub fn handle_help(prefix: &str, lang: Lang) -> RichMessage { + RichMessage::info(i18n::help_body(lang, prefix)).with_title(i18n::help_title(lang)) } diff --git a/src-tauri/src/chat_channel/event_subscriber.rs b/src-tauri/src/chat_channel/event_subscriber.rs index 48ebb64..3d6b4d3 100644 --- a/src-tauri/src/chat_channel/event_subscriber.rs +++ b/src-tauri/src/chat_channel/event_subscriber.rs @@ -5,6 +5,7 @@ use std::time::{Duration, Instant}; use sea_orm::DatabaseConnection; use tokio::task::JoinHandle; +use super::i18n::Lang; use super::manager::ChatChannelManager; use super::message_formatter; use super::types::RichMessage; @@ -13,6 +14,7 @@ use crate::web::event_bridge::WebEventBroadcaster; /// Minimum interval between pushes for the same event type per channel (debounce). const DEBOUNCE_SECS: u64 = 5; +const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language"; pub fn spawn_event_subscriber( broadcaster: Arc, @@ -36,7 +38,9 @@ pub fn spawn_event_subscriber( } }; - let message = match parse_event(&event.channel, &event.payload) { + let lang = load_lang(&db_conn).await; + + let message = match parse_event(&event.channel, &event.payload, lang) { Some((event_type, msg)) => { // Global event filter check let global_filter = app_metadata_service::get_value(&db_conn, "chat_event_filter") @@ -100,14 +104,23 @@ pub fn spawn_event_subscriber( }) } -fn parse_event(channel: &str, payload: &serde_json::Value) -> Option<(String, RichMessage)> { +async fn load_lang(db: &DatabaseConnection) -> Lang { + app_metadata_service::get_value(db, MESSAGE_LANGUAGE_KEY) + .await + .ok() + .flatten() + .map(|v| Lang::from_str_lossy(&v)) + .unwrap_or_default() +} + +fn parse_event(channel: &str, payload: &serde_json::Value, lang: Lang) -> Option<(String, RichMessage)> { match channel { - "acp://event" => parse_acp_event(payload), + "acp://event" => parse_acp_event(payload, lang), _ => None, } } -fn parse_acp_event(payload: &serde_json::Value) -> Option<(String, RichMessage)> { +fn parse_acp_event(payload: &serde_json::Value, lang: Lang) -> Option<(String, RichMessage)> { let event_type = payload.get("type")?.as_str()?; match event_type { @@ -126,7 +139,7 @@ fn parse_acp_event(payload: &serde_json::Value) -> Option<(String, RichMessage)> .unwrap_or("Unknown Agent"); Some(( "turn_complete".to_string(), - message_formatter::format_turn_complete(agent_type, stop_reason), + message_formatter::format_turn_complete(agent_type, stop_reason, lang), )) } "error" => { @@ -140,10 +153,9 @@ fn parse_acp_event(payload: &serde_json::Value) -> Option<(String, RichMessage)> .unwrap_or("Unknown error"); Some(( "error".to_string(), - message_formatter::format_agent_error(agent_type, message), + message_formatter::format_agent_error(agent_type, message, lang), )) } _ => None, } } - diff --git a/src-tauri/src/chat_channel/i18n.rs b/src-tauri/src/chat_channel/i18n.rs new file mode 100644 index 0000000..6d45b61 --- /dev/null +++ b/src-tauri/src/chat_channel/i18n.rs @@ -0,0 +1,795 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Lang { + En, + ZhCn, + ZhTw, + Ja, + Ko, + Es, + De, + Fr, + Pt, + Ar, +} + +impl Default for Lang { + fn default() -> Self { + Lang::En + } +} + +impl Lang { + pub fn from_str_lossy(s: &str) -> Self { + match s { + "en" => Lang::En, + "zh-cn" | "zh-CN" | "zh_CN" => Lang::ZhCn, + "zh-tw" | "zh-TW" | "zh_TW" => Lang::ZhTw, + "ja" => Lang::Ja, + "ko" => Lang::Ko, + "es" => Lang::Es, + "de" => Lang::De, + "fr" => Lang::Fr, + "pt" => Lang::Pt, + "ar" => Lang::Ar, + _ => Lang::En, + } + } +} + +// ── Event messages ── + +pub fn turn_complete_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "会话完成", + Lang::ZhTw => "對話完成", + Lang::Ja => "セッション完了", + Lang::Ko => "세션 완료", + Lang::Es => "Sesión completada", + Lang::De => "Sitzung abgeschlossen", + Lang::Fr => "Session terminée", + Lang::Pt => "Sessão concluída", + Lang::Ar => "اكتملت الجلسة", + Lang::En => "Turn Complete", + } +} + +pub fn turn_complete_body(lang: Lang, agent_type: &str) -> String { + match lang { + Lang::ZhCn => format!("{agent_type} 会话已完成"), + Lang::ZhTw => format!("{agent_type} 對話已完成"), + Lang::Ja => format!("{agent_type} セッションが完了しました"), + Lang::Ko => format!("{agent_type} 세션이 완료되었습니다"), + Lang::Es => format!("{agent_type} sesión completada"), + Lang::De => format!("{agent_type} Sitzung abgeschlossen"), + Lang::Fr => format!("Session {agent_type} terminée"), + Lang::Pt => format!("Sessão {agent_type} concluída"), + Lang::Ar => format!("اكتملت جلسة {agent_type}"), + Lang::En => format!("{agent_type} session completed"), + } +} + +pub fn stop_reason_label(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "结束原因", + Lang::ZhTw => "結束原因", + Lang::Ja => "終了理由", + Lang::Ko => "종료 사유", + Lang::Es => "Motivo de fin", + Lang::De => "Beendigungsgrund", + Lang::Fr => "Raison de fin", + Lang::Pt => "Motivo do término", + Lang::Ar => "سبب الانتهاء", + Lang::En => "Stop Reason", + } +} + +pub fn stop_reason_end_turn(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "正常结束", + Lang::ZhTw => "正常結束", + Lang::Ja => "正常終了", + Lang::Ko => "정상 종료", + Lang::Es => "Finalizado", + Lang::De => "Normal beendet", + Lang::Fr => "Terminé normalement", + Lang::Pt => "Finalizado", + Lang::Ar => "انتهى بشكل طبيعي", + Lang::En => "Completed", + } +} + +pub fn stop_reason_cancelled(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "已取消", + Lang::ZhTw => "已取消", + Lang::Ja => "キャンセル", + Lang::Ko => "취소됨", + Lang::Es => "Cancelado", + Lang::De => "Abgebrochen", + Lang::Fr => "Annulé", + Lang::Pt => "Cancelado", + Lang::Ar => "تم الإلغاء", + Lang::En => "Cancelled", + } +} + +pub fn agent_error_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "代理错误", + Lang::ZhTw => "代理錯誤", + Lang::Ja => "エージェントエラー", + Lang::Ko => "에이전트 오류", + Lang::Es => "Error del agente", + Lang::De => "Agent-Fehler", + Lang::Fr => "Erreur de l'agent", + Lang::Pt => "Erro do agente", + Lang::Ar => "خطأ في الوكيل", + Lang::En => "Agent Error", + } +} + +pub fn agent_error_body(lang: Lang, agent_type: &str) -> String { + match lang { + Lang::ZhCn => format!("{agent_type} 发生错误"), + Lang::ZhTw => format!("{agent_type} 發生錯誤"), + Lang::Ja => format!("{agent_type} でエラーが発生しました"), + Lang::Ko => format!("{agent_type}에서 오류 발생"), + Lang::Es => format!("{agent_type} encontró un error"), + Lang::De => format!("{agent_type} hat einen Fehler"), + Lang::Fr => format!("{agent_type} a rencontré une erreur"), + Lang::Pt => format!("{agent_type} encontrou um erro"), + Lang::Ar => format!("حدث خطأ في {agent_type}"), + Lang::En => format!("{agent_type} encountered an error"), + } +} + +pub fn error_message_label(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "错误信息", + Lang::ZhTw => "錯誤訊息", + Lang::Ja => "エラーメッセージ", + Lang::Ko => "오류 메시지", + Lang::Es => "Mensaje de error", + Lang::De => "Fehlermeldung", + Lang::Fr => "Message d'erreur", + Lang::Pt => "Mensagem de erro", + Lang::Ar => "رسالة الخطأ", + Lang::En => "Error Message", + } +} + +// ── Daily report ── + +pub fn daily_report_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "每日编码报告", + Lang::ZhTw => "每日編碼報告", + Lang::Ja => "日次コーディングレポート", + Lang::Ko => "일일 코딩 보고서", + Lang::Es => "Informe diario de codificación", + Lang::De => "Täglicher Coding-Bericht", + Lang::Fr => "Rapport de codage quotidien", + Lang::Pt => "Relatório diário de codificação", + Lang::Ar => "تقرير البرمجة اليومي", + Lang::En => "Daily Coding Report", + } +} + +pub fn daily_report_summary(lang: Lang, date: &str) -> String { + match lang { + Lang::ZhCn => format!("今日编码活动汇总 ({date})"), + Lang::ZhTw => format!("今日編碼活動匯總 ({date})"), + Lang::Ja => format!("本日のコーディング活動まとめ ({date})"), + Lang::Ko => format!("오늘의 코딩 활동 요약 ({date})"), + Lang::Es => format!("Resumen de actividad de codificación ({date})"), + Lang::De => format!("Coding-Aktivitätszusammenfassung ({date})"), + Lang::Fr => format!("Résumé de l'activité de codage ({date})"), + Lang::Pt => format!("Resumo da atividade de codificação ({date})"), + Lang::Ar => format!("ملخص نشاط البرمجة ({date})"), + Lang::En => format!("Daily coding activity summary ({date})"), + } +} + +pub fn total_sessions(lang: Lang, count: u32) -> String { + match lang { + Lang::ZhCn => format!("会话总数: {count}"), + Lang::ZhTw => format!("對話總數: {count}"), + Lang::Ja => format!("セッション合計: {count}"), + Lang::Ko => format!("총 세션: {count}"), + Lang::Es => format!("Total de sesiones: {count}"), + Lang::De => format!("Sitzungen gesamt: {count}"), + Lang::Fr => format!("Sessions totales : {count}"), + Lang::Pt => format!("Total de sessões: {count}"), + Lang::Ar => format!("إجمالي الجلسات: {count}"), + Lang::En => format!("Total sessions: {count}"), + } +} + +pub fn by_agent_label(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "按代理分布:", + Lang::ZhTw => "按代理分佈:", + Lang::Ja => "エージェント別:", + Lang::Ko => "에이전트별:", + Lang::Es => "Por agente:", + Lang::De => "Nach Agent:", + Lang::Fr => "Par agent :", + Lang::Pt => "Por agente:", + Lang::Ar => "حسب الوكيل:", + Lang::En => "By agent:", + } +} + +pub fn agent_session_count(lang: Lang, agent: &str, count: u32) -> String { + match lang { + Lang::ZhCn => format!("{agent} - {count} 个会话"), + Lang::ZhTw => format!("{agent} - {count} 個對話"), + Lang::Ja => format!("{agent} - {count} セッション"), + Lang::Ko => format!("{agent} - {count}개 세션"), + Lang::Es => format!("{agent} - {count} sesiones"), + Lang::De => format!("{agent} - {count} Sitzungen"), + Lang::Fr => format!("{agent} - {count} sessions"), + Lang::Pt => format!("{agent} - {count} sessões"), + Lang::Ar => format!("{agent} - {count} جلسات"), + Lang::En => format!("{agent} - {count} sessions"), + } +} + +pub fn projects_label(lang: Lang, projects: &str) -> String { + match lang { + Lang::ZhCn => format!("涉及项目: {projects}"), + Lang::ZhTw => format!("涉及專案: {projects}"), + Lang::Ja => format!("関連プロジェクト: {projects}"), + Lang::Ko => format!("관련 프로젝트: {projects}"), + Lang::Es => format!("Proyectos: {projects}"), + Lang::De => format!("Projekte: {projects}"), + Lang::Fr => format!("Projets : {projects}"), + Lang::Pt => format!("Projetos: {projects}"), + Lang::Ar => format!("المشاريع: {projects}"), + Lang::En => format!("Projects: {projects}"), + } +} + +pub fn key_activities_label(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "主要活动:", + Lang::ZhTw => "主要活動:", + Lang::Ja => "主な活動:", + Lang::Ko => "주요 활동:", + Lang::Es => "Actividades clave:", + Lang::De => "Wichtige Aktivitäten:", + Lang::Fr => "Activités principales :", + Lang::Pt => "Atividades principais:", + Lang::Ar => "الأنشطة الرئيسية:", + Lang::En => "Key activities:", + } +} + +// ── Command responses ── + +pub fn query_failed_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "查询失败", + Lang::ZhTw => "查詢失敗", + Lang::Ja => "クエリ失敗", + Lang::Ko => "조회 실패", + Lang::Es => "Error de consulta", + Lang::De => "Abfrage fehlgeschlagen", + Lang::Fr => "Échec de la requête", + Lang::Pt => "Falha na consulta", + Lang::Ar => "فشل الاستعلام", + Lang::En => "Query Failed", + } +} + +pub fn no_conversations(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "暂无会话记录", + Lang::ZhTw => "暫無對話記錄", + Lang::Ja => "セッション履歴なし", + Lang::Ko => "대화 기록 없음", + Lang::Es => "Sin conversaciones", + Lang::De => "Keine Sitzungen", + Lang::Fr => "Aucune session", + Lang::Pt => "Nenhuma sessão", + Lang::Ar => "لا توجد جلسات", + Lang::En => "No conversations found", + } +} + +pub fn recent_conversations_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "最近会话", + Lang::ZhTw => "最近對話", + Lang::Ja => "最近のセッション", + Lang::Ko => "최근 대화", + Lang::Es => "Conversaciones recientes", + Lang::De => "Letzte Sitzungen", + Lang::Fr => "Sessions récentes", + Lang::Pt => "Sessões recentes", + Lang::Ar => "الجلسات الأخيرة", + Lang::En => "Recent Conversations", + } +} + +pub fn untitled(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "(无标题)", + Lang::ZhTw => "(無標題)", + Lang::Ja => "(無題)", + Lang::Ko => "(제목 없음)", + Lang::Es => "(Sin título)", + Lang::De => "(Ohne Titel)", + Lang::Fr => "(Sans titre)", + Lang::Pt => "(Sem título)", + Lang::Ar => "(بدون عنوان)", + Lang::En => "(Untitled)", + } +} + +pub fn recent_n_conversations_title(lang: Lang, n: usize) -> String { + match lang { + Lang::ZhCn => format!("最近 {n} 条会话"), + Lang::ZhTw => format!("最近 {n} 條對話"), + Lang::Ja => format!("最新 {n} セッション"), + Lang::Ko => format!("최근 {n}개 대화"), + Lang::Es => format!("{n} conversaciones más recientes"), + Lang::De => format!("Letzte {n} Sitzungen"), + Lang::Fr => format!("{n} dernières sessions"), + Lang::Pt => format!("{n} sessões mais recentes"), + Lang::Ar => format!("أحدث {n} جلسات"), + Lang::En => format!("{n} Most Recent Conversations"), + } +} + +pub fn search_no_results(lang: Lang, keyword: &str) -> String { + match lang { + Lang::ZhCn => format!("未找到包含 \"{keyword}\" 的会话"), + Lang::ZhTw => format!("未找到包含 \"{keyword}\" 的對話"), + Lang::Ja => format!("\"{keyword}\" を含むセッションが見つかりません"), + Lang::Ko => format!("\"{keyword}\"을(를) 포함하는 대화를 찾을 수 없습니다"), + Lang::Es => format!("No se encontraron conversaciones con \"{keyword}\""), + Lang::De => format!("Keine Sitzungen mit \"{keyword}\" gefunden"), + Lang::Fr => format!("Aucune session trouvée avec \"{keyword}\""), + Lang::Pt => format!("Nenhuma sessão encontrada com \"{keyword}\""), + Lang::Ar => format!("لم يتم العثور على جلسات تحتوي على \"{keyword}\""), + Lang::En => format!("No conversations found matching \"{keyword}\""), + } +} + +pub fn search_results_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "搜索结果", + Lang::ZhTw => "搜尋結果", + Lang::Ja => "検索結果", + Lang::Ko => "검색 결과", + Lang::Es => "Resultados", + Lang::De => "Suchergebnisse", + Lang::Fr => "Résultats", + Lang::Pt => "Resultados", + Lang::Ar => "نتائج البحث", + Lang::En => "Search Results", + } +} + +pub fn search_results_count_title(lang: Lang, keyword: &str, count: usize) -> String { + match lang { + Lang::ZhCn => format!("搜索 \"{keyword}\" - {count} 条结果"), + Lang::ZhTw => format!("搜尋 \"{keyword}\" - {count} 條結果"), + Lang::Ja => format!("\"{keyword}\" の検索 - {count} 件"), + Lang::Ko => format!("\"{keyword}\" 검색 - {count}건"), + Lang::Es => format!("Buscar \"{keyword}\" - {count} resultados"), + Lang::De => format!("Suche \"{keyword}\" - {count} Ergebnisse"), + Lang::Fr => format!("Recherche \"{keyword}\" - {count} résultats"), + Lang::Pt => format!("Busca \"{keyword}\" - {count} resultados"), + Lang::Ar => format!("بحث \"{keyword}\" - {count} نتائج"), + Lang::En => format!("Search \"{keyword}\" - {count} results"), + } +} + +pub fn conversation_not_found(lang: Lang, id: i32) -> String { + match lang { + Lang::ZhCn => format!("会话 {id} 不存在"), + Lang::ZhTw => format!("對話 {id} 不存在"), + Lang::Ja => format!("セッション {id} が見つかりません"), + Lang::Ko => format!("대화 {id}를 찾을 수 없습니다"), + Lang::Es => format!("Conversación {id} no encontrada"), + Lang::De => format!("Sitzung {id} nicht gefunden"), + Lang::Fr => format!("Session {id} introuvable"), + Lang::Pt => format!("Sessão {id} não encontrada"), + Lang::Ar => format!("الجلسة {id} غير موجودة"), + Lang::En => format!("Conversation {id} not found"), + } +} + +pub fn not_found_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "未找到", + Lang::ZhTw => "未找到", + Lang::Ja => "見つかりません", + Lang::Ko => "찾을 수 없음", + Lang::Es => "No encontrado", + Lang::De => "Nicht gefunden", + Lang::Fr => "Introuvable", + Lang::Pt => "Não encontrado", + Lang::Ar => "غير موجود", + Lang::En => "Not Found", + } +} + +pub fn conversation_detail_title(lang: Lang, id: i32) -> String { + match lang { + Lang::ZhCn => format!("会话详情 #{id}"), + Lang::ZhTw => format!("對話詳情 #{id}"), + Lang::Ja => format!("セッション詳細 #{id}"), + Lang::Ko => format!("대화 상세 #{id}"), + Lang::Es => format!("Detalles #{id}"), + Lang::De => format!("Sitzungsdetails #{id}"), + Lang::Fr => format!("Détails #{id}"), + Lang::Pt => format!("Detalhes #{id}"), + Lang::Ar => format!("تفاصيل الجلسة #{id}"), + Lang::En => format!("Conversation Details #{id}"), + } +} + +pub fn field_agent(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "代理", + Lang::ZhTw => "代理", + Lang::Ja => "エージェント", + Lang::Ko => "에이전트", + Lang::Es => "Agente", + Lang::De => "Agent", + Lang::Fr => "Agent", + Lang::Pt => "Agente", + Lang::Ar => "الوكيل", + Lang::En => "Agent", + } +} + +pub fn field_status(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "状态", + Lang::ZhTw => "狀態", + Lang::Ja => "ステータス", + Lang::Ko => "상태", + Lang::Es => "Estado", + Lang::De => "Status", + Lang::Fr => "Statut", + Lang::Pt => "Status", + Lang::Ar => "الحالة", + Lang::En => "Status", + } +} + +pub fn field_message_count(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "消息数", + Lang::ZhTw => "訊息數", + Lang::Ja => "メッセージ数", + Lang::Ko => "메시지 수", + Lang::Es => "Mensajes", + Lang::De => "Nachrichten", + Lang::Fr => "Messages", + Lang::Pt => "Mensagens", + Lang::Ar => "عدد الرسائل", + Lang::En => "Messages", + } +} + +pub fn field_created_at(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "创建时间", + Lang::ZhTw => "建立時間", + Lang::Ja => "作成日時", + Lang::Ko => "생성 시간", + Lang::Es => "Creado", + Lang::De => "Erstellt", + Lang::Fr => "Créé", + Lang::Pt => "Criado", + Lang::Ar => "تاريخ الإنشاء", + Lang::En => "Created", + } +} + +pub fn no_activity_today(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "今日暂无编码活动", + Lang::ZhTw => "今日暫無編碼活動", + Lang::Ja => "本日のコーディング活動はありません", + Lang::Ko => "오늘 코딩 활동이 없습니다", + Lang::Es => "Sin actividad de codificación hoy", + Lang::De => "Heute keine Coding-Aktivität", + Lang::Fr => "Aucune activité de codage aujourd'hui", + Lang::Pt => "Nenhuma atividade de codificação hoje", + Lang::Ar => "لا يوجد نشاط برمجة اليوم", + Lang::En => "No coding activity today", + } +} + +pub fn today_activity_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "今日活动", + Lang::ZhTw => "今日活動", + Lang::Ja => "本日の活動", + Lang::Ko => "오늘의 활동", + Lang::Es => "Actividad de hoy", + Lang::De => "Heutige Aktivität", + Lang::Fr => "Activité du jour", + Lang::Pt => "Atividade de hoje", + Lang::Ar => "نشاط اليوم", + Lang::En => "Today's Activity", + } +} + +pub fn today_activity_date_title(lang: Lang, date: &str) -> String { + match lang { + Lang::ZhCn => format!("今日活动 ({date})"), + Lang::ZhTw => format!("今日活動 ({date})"), + Lang::Ja => format!("本日の活動 ({date})"), + Lang::Ko => format!("오늘의 활동 ({date})"), + Lang::Es => format!("Actividad de hoy ({date})"), + Lang::De => format!("Heutige Aktivität ({date})"), + Lang::Fr => format!("Activité du jour ({date})"), + Lang::Pt => format!("Atividade de hoje ({date})"), + Lang::Ar => format!("نشاط اليوم ({date})"), + Lang::En => format!("Today's Activity ({date})"), + } +} + +pub fn agent_count(lang: Lang, agent: &str, count: u32) -> String { + match lang { + Lang::ZhCn => format!("{agent} - {count} 个"), + Lang::ZhTw => format!("{agent} - {count} 個"), + Lang::Ja => format!("{agent} - {count} 件"), + Lang::Ko => format!("{agent} - {count}개"), + Lang::Es | Lang::De | Lang::Fr | Lang::Pt | Lang::Ar | Lang::En => { + format!("{agent} - {count}") + } + } +} + +pub fn recent_activity_label(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "最近活动:", + Lang::ZhTw => "最近活動:", + Lang::Ja => "最近の活動:", + Lang::Ko => "최근 활동:", + Lang::Es => "Actividad reciente:", + Lang::De => "Letzte Aktivität:", + Lang::Fr => "Activité récente :", + Lang::Pt => "Atividade recente:", + Lang::Ar => "النشاط الأخير:", + Lang::En => "Recent activity:", + } +} + +pub fn no_active_channels(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "暂无活跃渠道", + Lang::ZhTw => "暫無活躍頻道", + Lang::Ja => "アクティブなチャンネルなし", + Lang::Ko => "활성 채널 없음", + Lang::Es => "Sin canales activos", + Lang::De => "Keine aktiven Kanäle", + Lang::Fr => "Aucun canal actif", + Lang::Pt => "Nenhum canal ativo", + Lang::Ar => "لا توجد قنوات نشطة", + Lang::En => "No active channels", + } +} + +pub fn channel_status_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "渠道状态", + Lang::ZhTw => "頻道狀態", + Lang::Ja => "チャンネル状況", + Lang::Ko => "채널 상태", + Lang::Es => "Estado de canales", + Lang::De => "Kanalstatus", + Lang::Fr => "Statut des canaux", + Lang::Pt => "Status dos canais", + Lang::Ar => "حالة القنوات", + Lang::En => "Channel Status", + } +} + +pub fn help_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "Codeg Bot 帮助", + Lang::ZhTw => "Codeg Bot 幫助", + Lang::Ja => "Codeg Bot ヘルプ", + Lang::Ko => "Codeg Bot 도움말", + Lang::Es => "Ayuda de Codeg Bot", + Lang::De => "Codeg Bot Hilfe", + Lang::Fr => "Aide Codeg Bot", + Lang::Pt => "Ajuda do Codeg Bot", + Lang::Ar => "مساعدة Codeg Bot", + Lang::En => "Codeg Bot Help", + } +} + +pub fn help_body(lang: Lang, prefix: &str) -> String { + match lang { + Lang::ZhCn => format!( + "{prefix}recent - 最近 5 条会话\n\ + {prefix}search <关键词> - 搜索会话\n\ + {prefix}detail - 会话详情\n\ + {prefix}today - 今日活动汇总\n\ + {prefix}status - 渠道连接状态\n\ + {prefix}help - 显示帮助" + ), + Lang::ZhTw => format!( + "{prefix}recent - 最近 5 條對話\n\ + {prefix}search <關鍵字> - 搜尋對話\n\ + {prefix}detail - 對話詳情\n\ + {prefix}today - 今日活動匯總\n\ + {prefix}status - 頻道連線狀態\n\ + {prefix}help - 顯示幫助" + ), + Lang::Ja => format!( + "{prefix}recent - 最新5件のセッション\n\ + {prefix}search <キーワード> - セッション検索\n\ + {prefix}detail - セッション詳細\n\ + {prefix}today - 本日の活動まとめ\n\ + {prefix}status - チャンネル接続状況\n\ + {prefix}help - ヘルプを表示" + ), + Lang::Ko => format!( + "{prefix}recent - 최근 5개 대화\n\ + {prefix}search <키워드> - 대화 검색\n\ + {prefix}detail - 대화 상세\n\ + {prefix}today - 오늘의 활동 요약\n\ + {prefix}status - 채널 연결 상태\n\ + {prefix}help - 도움말 표시" + ), + Lang::Es => format!( + "{prefix}recent - 5 conversaciones más recientes\n\ + {prefix}search - Buscar conversaciones\n\ + {prefix}detail - Detalles de conversación\n\ + {prefix}today - Resumen de hoy\n\ + {prefix}status - Estado de canales\n\ + {prefix}help - Mostrar ayuda" + ), + Lang::De => format!( + "{prefix}recent - 5 neueste Sitzungen\n\ + {prefix}search - Sitzungen suchen\n\ + {prefix}detail - Sitzungsdetails\n\ + {prefix}today - Heutige Zusammenfassung\n\ + {prefix}status - Kanalstatus\n\ + {prefix}help - Hilfe anzeigen" + ), + Lang::Fr => format!( + "{prefix}recent - 5 dernières sessions\n\ + {prefix}search - Rechercher des sessions\n\ + {prefix}detail - Détails de la session\n\ + {prefix}today - Résumé du jour\n\ + {prefix}status - Statut des canaux\n\ + {prefix}help - Afficher l'aide" + ), + Lang::Pt => format!( + "{prefix}recent - 5 sessões mais recentes\n\ + {prefix}search - Buscar sessões\n\ + {prefix}detail - Detalhes da sessão\n\ + {prefix}today - Resumo de hoje\n\ + {prefix}status - Status dos canais\n\ + {prefix}help - Mostrar ajuda" + ), + Lang::Ar => format!( + "{prefix}recent - أحدث 5 جلسات\n\ + {prefix}search <كلمة> - البحث في الجلسات\n\ + {prefix}detail - تفاصيل الجلسة\n\ + {prefix}today - ملخص اليوم\n\ + {prefix}status - حالة القنوات\n\ + {prefix}help - عرض المساعدة" + ), + Lang::En => format!( + "{prefix}recent - 5 most recent conversations\n\ + {prefix}search - Search conversations\n\ + {prefix}detail - Conversation details\n\ + {prefix}today - Today's activity summary\n\ + {prefix}status - Channel connection status\n\ + {prefix}help - Show help" + ), + } +} + +// ── Command dispatcher messages ── + +pub fn invalid_args_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "参数错误", + Lang::ZhTw => "參數錯誤", + Lang::Ja => "引数エラー", + Lang::Ko => "인수 오류", + Lang::Es => "Argumentos inválidos", + Lang::De => "Ungültige Argumente", + Lang::Fr => "Arguments invalides", + Lang::Pt => "Argumentos inválidos", + Lang::Ar => "وسيطات غير صالحة", + Lang::En => "Invalid Arguments", + } +} + +pub fn search_usage(lang: Lang, prefix: &str) -> String { + match lang { + Lang::ZhCn => format!("用法: {prefix}search <关键词>"), + Lang::ZhTw => format!("用法: {prefix}search <關鍵字>"), + Lang::Ja => format!("使い方: {prefix}search <キーワード>"), + Lang::Ko => format!("사용법: {prefix}search <키워드>"), + Lang::Es => format!("Uso: {prefix}search "), + Lang::De => format!("Verwendung: {prefix}search "), + Lang::Fr => format!("Utilisation : {prefix}search "), + Lang::Pt => format!("Uso: {prefix}search "), + Lang::Ar => format!("الاستخدام: {prefix}search <كلمة>"), + Lang::En => format!("Usage: {prefix}search "), + } +} + +pub fn detail_usage(lang: Lang, prefix: &str) -> String { + match lang { + Lang::ZhCn => format!("用法: {prefix}detail <会话ID>"), + Lang::ZhTw => format!("用法: {prefix}detail <對話ID>"), + Lang::Ja => format!("使い方: {prefix}detail <セッションID>"), + Lang::Ko => format!("사용법: {prefix}detail <대화ID>"), + Lang::Es => format!("Uso: {prefix}detail "), + Lang::De => format!("Verwendung: {prefix}detail "), + Lang::Fr => format!("Utilisation : {prefix}detail "), + Lang::Pt => format!("Uso: {prefix}detail "), + Lang::Ar => format!("الاستخدام: {prefix}detail "), + Lang::En => format!("Usage: {prefix}detail "), + } +} + +pub fn unknown_command(lang: Lang, prefix: &str, command: &str) -> String { + match lang { + Lang::ZhCn => format!( + "未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令" + ), + Lang::ZhTw => format!( + "未知命令: {prefix}{command}\n輸入 {prefix}help 查看可用命令" + ), + Lang::Ja => format!( + "不明なコマンド: {prefix}{command}\n{prefix}help でヘルプを表示" + ), + Lang::Ko => format!( + "알 수 없는 명령: {prefix}{command}\n{prefix}help 로 도움말 보기" + ), + Lang::Es => format!( + "Comando desconocido: {prefix}{command}\nEscriba {prefix}help para ver los comandos" + ), + Lang::De => format!( + "Unbekannter Befehl: {prefix}{command}\n{prefix}help für Hilfe eingeben" + ), + Lang::Fr => format!( + "Commande inconnue : {prefix}{command}\nTapez {prefix}help pour l'aide" + ), + Lang::Pt => format!( + "Comando desconhecido: {prefix}{command}\nDigite {prefix}help para ajuda" + ), + Lang::Ar => format!( + "أمر غير معروف: {prefix}{command}\nاكتب {prefix}help لعرض المساعدة" + ), + Lang::En => format!( + "Unknown command: {prefix}{command}\nType {prefix}help for available commands" + ), + } +} + +pub fn unknown_command_title(lang: Lang) -> &'static str { + match lang { + Lang::ZhCn => "未知命令", + Lang::ZhTw => "未知命令", + Lang::Ja => "不明なコマンド", + Lang::Ko => "알 수 없는 명령", + Lang::Es => "Comando desconocido", + Lang::De => "Unbekannter Befehl", + Lang::Fr => "Commande inconnue", + Lang::Pt => "Comando desconhecido", + Lang::Ar => "أمر غير معروف", + Lang::En => "Unknown Command", + } +} diff --git a/src-tauri/src/chat_channel/message_formatter.rs b/src-tauri/src/chat_channel/message_formatter.rs index e30dfd8..aae24c2 100644 --- a/src-tauri/src/chat_channel/message_formatter.rs +++ b/src-tauri/src/chat_channel/message_formatter.rs @@ -1,21 +1,25 @@ +use super::i18n::{self, Lang}; use super::types::{MessageLevel, RichMessage}; -pub fn format_turn_complete(agent_type: &str, stop_reason: &str) -> RichMessage { +pub fn format_turn_complete(agent_type: &str, stop_reason: &str, lang: Lang) -> RichMessage { let reason = match stop_reason { - "end_turn" => "正常结束", - "cancelled" => "已取消", + "end_turn" => i18n::stop_reason_end_turn(lang), + "cancelled" => i18n::stop_reason_cancelled(lang), _ => stop_reason, }; - RichMessage::info(format!("{agent_type} 会话已完成")) - .with_title("会话完成") - .with_field("结束原因", reason) + RichMessage::info(i18n::turn_complete_body(lang, agent_type)) + .with_title(i18n::turn_complete_title(lang)) + .with_field(i18n::stop_reason_label(lang), reason) } -pub fn format_agent_error(agent_type: &str, message: &str) -> RichMessage { +pub fn format_agent_error(agent_type: &str, message: &str, lang: Lang) -> RichMessage { RichMessage { - title: Some("代理错误".to_string()), - body: format!("{agent_type} 发生错误"), - fields: vec![("错误信息".to_string(), message.to_string())], + title: Some(i18n::agent_error_title(lang).to_string()), + body: i18n::agent_error_body(lang, agent_type), + fields: vec![( + i18n::error_message_label(lang).to_string(), + message.to_string(), + )], level: MessageLevel::Error, } } @@ -28,31 +32,37 @@ pub struct DailyReportData { pub key_activities: Vec, } -pub fn format_daily_report(report: &DailyReportData) -> RichMessage { - let mut body = format!("今日编码活动汇总 ({})", report.date); +pub fn format_daily_report(report: &DailyReportData, lang: Lang) -> RichMessage { + let mut body = i18n::daily_report_summary(lang, &report.date); - body.push_str(&format!("\n\n会话总数: {}", report.total_conversations)); + body.push_str(&format!( + "\n\n{}", + i18n::total_sessions(lang, report.total_conversations) + )); if !report.conversations_by_agent.is_empty() { - body.push_str("\n\n按代理分布:"); + body.push_str(&format!("\n\n{}", i18n::by_agent_label(lang))); for (agent, count) in &report.conversations_by_agent { - body.push_str(&format!("\n {} - {} 个会话", agent, count)); + body.push_str(&format!( + "\n {}", + i18n::agent_session_count(lang, agent, *count) + )); } } if !report.projects_involved.is_empty() { body.push_str(&format!( - "\n\n涉及项目: {}", - report.projects_involved.join(", ") + "\n\n{}", + i18n::projects_label(lang, &report.projects_involved.join(", ")) )); } if !report.key_activities.is_empty() { - body.push_str("\n\n主要活动:"); + body.push_str(&format!("\n\n{}", i18n::key_activities_label(lang))); for activity in &report.key_activities { body.push_str(&format!("\n • {}", activity)); } } - RichMessage::info(body).with_title("每日编码报告") + RichMessage::info(body).with_title(i18n::daily_report_title(lang)) } diff --git a/src-tauri/src/chat_channel/mod.rs b/src-tauri/src/chat_channel/mod.rs index f0862a6..a52edf5 100644 --- a/src-tauri/src/chat_channel/mod.rs +++ b/src-tauri/src/chat_channel/mod.rs @@ -3,6 +3,7 @@ pub mod command_dispatcher; pub mod command_handlers; pub mod error; pub mod event_subscriber; +pub mod i18n; pub mod manager; pub mod message_formatter; pub mod scheduler; diff --git a/src-tauri/src/chat_channel/scheduler.rs b/src-tauri/src/chat_channel/scheduler.rs index 00093a9..8d3f64c 100644 --- a/src-tauri/src/chat_channel/scheduler.rs +++ b/src-tauri/src/chat_channel/scheduler.rs @@ -4,10 +4,13 @@ use chrono::{Local, NaiveDate, Timelike, Utc}; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder}; use tokio::task::JoinHandle; +use super::i18n::Lang; use super::manager::ChatChannelManager; use super::message_formatter::{self, DailyReportData}; use crate::db::entities::conversation; -use crate::db::service::{chat_channel_message_log_service, chat_channel_service}; +use crate::db::service::{app_metadata_service, chat_channel_message_log_service, chat_channel_service}; + +const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language"; pub fn spawn_daily_report_scheduler( manager: ChatChannelManager, @@ -53,9 +56,11 @@ pub fn spawn_daily_report_scheduler( continue; } + let lang = load_lang(&db_conn).await; + // Generate and send report let report = generate_daily_report(&db_conn).await; - let message = message_formatter::format_daily_report(&report); + let message = message_formatter::format_daily_report(&report, lang); let send_result = manager.send_to_channel(ch.id, &message).await; let (status, error_detail) = match &send_result { @@ -80,6 +85,15 @@ pub fn spawn_daily_report_scheduler( }) } +async fn load_lang(db: &DatabaseConnection) -> Lang { + app_metadata_service::get_value(db, MESSAGE_LANGUAGE_KEY) + .await + .ok() + .flatten() + .map(|v| Lang::from_str_lossy(&v)) + .unwrap_or_default() +} + async fn generate_daily_report(db: &DatabaseConnection) -> DailyReportData { let now = Utc::now(); let today_start = now.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc(); diff --git a/src-tauri/src/commands/chat_channel.rs b/src-tauri/src/commands/chat_channel.rs index 9684666..093be25 100644 --- a/src-tauri/src/commands/chat_channel.rs +++ b/src-tauri/src/commands/chat_channel.rs @@ -296,6 +296,42 @@ pub async fn set_chat_command_prefix_core( Ok(()) } +const MESSAGE_LANGUAGE_KEY: &str = "chat_message_language"; + +pub async fn get_chat_message_language_core( + db: &AppDatabase, +) -> Result { + let val = crate::db::service::app_metadata_service::get_value(&db.conn, MESSAGE_LANGUAGE_KEY) + .await + .map_err(AppCommandError::from)?; + Ok(val.unwrap_or_else(|| "en".to_string())) +} + +pub async fn set_chat_message_language_core( + db: &AppDatabase, + language: String, +) -> Result<(), AppCommandError> { + // Validate language code + let valid = [ + "en", "zh-cn", "zh-tw", "ja", "ko", "es", "de", "fr", "pt", "ar", + ]; + let lang_lower = language.to_lowercase(); + if !valid.contains(&lang_lower.as_str()) { + return Err(AppCommandError::invalid_input(format!( + "Unsupported language: {language}. Supported: {}", + valid.join(", ") + ))); + } + crate::db::service::app_metadata_service::upsert_value( + &db.conn, + MESSAGE_LANGUAGE_KEY, + &lang_lower, + ) + .await + .map_err(AppCommandError::from)?; + Ok(()) +} + const EVENT_FILTER_KEY: &str = "chat_event_filter"; pub async fn get_chat_event_filter_core( @@ -493,3 +529,20 @@ pub async fn set_chat_event_filter( ) -> Result<(), AppCommandError> { set_chat_event_filter_core(&db, filter).await } + +#[cfg(feature = "tauri-runtime")] +#[tauri::command] +pub async fn get_chat_message_language( + db: tauri::State<'_, AppDatabase>, +) -> Result { + get_chat_message_language_core(&db).await +} + +#[cfg(feature = "tauri-runtime")] +#[tauri::command] +pub async fn set_chat_message_language( + db: tauri::State<'_, AppDatabase>, + language: String, +) -> Result<(), AppCommandError> { + set_chat_message_language_core(&db, language).await +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f3c3841..b8448be 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -383,6 +383,8 @@ mod tauri_app { chat_channel_commands::set_chat_command_prefix, chat_channel_commands::get_chat_event_filter, chat_channel_commands::set_chat_event_filter, + chat_channel_commands::get_chat_message_language, + chat_channel_commands::set_chat_message_language, web::start_web_server, web::stop_web_server, web::get_web_server_status, diff --git a/src-tauri/src/web/handlers/chat_channel.rs b/src-tauri/src/web/handlers/chat_channel.rs index 2a70c68..3c106c9 100644 --- a/src-tauri/src/web/handlers/chat_channel.rs +++ b/src-tauri/src/web/handlers/chat_channel.rs @@ -225,3 +225,24 @@ pub async fn set_chat_event_filter( cc_commands::set_chat_event_filter_core(&state.db, params.filter).await?; Ok(Json(())) } + +pub async fn get_chat_message_language( + Extension(state): Extension>, +) -> Result, AppCommandError> { + let result = cc_commands::get_chat_message_language_core(&state.db).await?; + Ok(Json(result)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SetMessageLanguageParams { + pub language: String, +} + +pub async fn set_chat_message_language( + Extension(state): Extension>, + Json(params): Json, +) -> Result, AppCommandError> { + cc_commands::set_chat_message_language_core(&state.db, params.language).await?; + Ok(Json(())) +} diff --git a/src-tauri/src/web/router.rs b/src-tauri/src/web/router.rs index 8d4a147..8828831 100644 --- a/src-tauri/src/web/router.rs +++ b/src-tauri/src/web/router.rs @@ -197,6 +197,8 @@ pub fn build_router(state: Arc, token: String, static_dir: std::path:: .route("/set_chat_command_prefix", post(handlers::chat_channel::set_chat_command_prefix)) .route("/get_chat_event_filter", post(handlers::chat_channel::get_chat_event_filter)) .route("/set_chat_event_filter", post(handlers::chat_channel::set_chat_event_filter)) + .route("/get_chat_message_language", post(handlers::chat_channel::get_chat_message_language)) + .route("/set_chat_message_language", post(handlers::chat_channel::set_chat_message_language)) // ─── Terminal ─── .route("/terminal_spawn", post(handlers::terminal::terminal_spawn)) .route("/terminal_write", post(handlers::terminal::terminal_write)) diff --git a/src/components/settings/channel-other-tab.tsx b/src/components/settings/channel-other-tab.tsx new file mode 100644 index 0000000..4e524d3 --- /dev/null +++ b/src/components/settings/channel-other-tab.tsx @@ -0,0 +1,91 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" +import { Loader2 } from "lucide-react" +import { useTranslations } from "next-intl" +import { toast } from "sonner" + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { getChatMessageLanguage, setChatMessageLanguage } from "@/lib/api" + +const SUPPORTED_LANGUAGES = [ + "en", + "zh-cn", + "zh-tw", + "ja", + "ko", + "es", + "de", + "fr", + "pt", + "ar", +] as const + +export function ChannelOtherTab() { + const t = useTranslations("ChatChannelSettings.language") + const [language, setLanguage] = useState("en") + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + + useEffect(() => { + getChatMessageLanguage() + .then((lang) => setLanguage(lang)) + .catch(() => {}) + .finally(() => setLoading(false)) + }, []) + + const handleLanguageChange = useCallback( + async (value: string) => { + setSaving(true) + try { + await setChatMessageLanguage(value) + setLanguage(value) + toast.success(t("saved")) + } catch { + toast.error(t("saveFailed")) + } finally { + setSaving(false) + } + }, + [t] + ) + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+
+

{t("title")}

+

{t("description")}

+ +
+
+ ) +} diff --git a/src/components/settings/chat-channel-settings.tsx b/src/components/settings/chat-channel-settings.tsx index eb9358c..ac51f69 100644 --- a/src/components/settings/chat-channel-settings.tsx +++ b/src/components/settings/chat-channel-settings.tsx @@ -6,6 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { ChannelListTab } from "./channel-list-tab" import { ChannelCommandsTab } from "./channel-commands-tab" import { ChannelEventsTab } from "./channel-events-tab" +import { ChannelOtherTab } from "./channel-other-tab" export function ChatChannelSettings() { const t = useTranslations("ChatChannelSettings") @@ -24,6 +25,7 @@ export function ChatChannelSettings() { {t("tabs.channels")} {t("tabs.commands")} {t("tabs.events")} + {t("tabs.other")} @@ -36,6 +38,9 @@ export function ChatChannelSettings() { + + + ) diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index b5f3c35..c171571 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "القنوات", "commands": "الأوامر", - "events": "الأحداث" + "events": "الأحداث", + "other": "أخرى" }, "commands": { "title": "الأوامر المدمجة", @@ -1740,6 +1741,22 @@ "errorDesc": "عندما يواجه الوكيل خطأ", "saved": "تم تحديث فلتر الأحداث.", "saveFailed": "فشل حفظ فلتر الأحداث." + }, + "language": { + "title": "لغة الرسائل", + "description": "اللغة المستخدمة لإشعارات الأحداث واستجابات الأوامر والتقارير اليومية المرسلة إلى قنوات الدردشة.", + "saved": "تم حفظ لغة الرسائل.", + "saveFailed": "فشل حفظ لغة الرسائل.", + "en": "الإنجليزية", + "zh-cn": "الصينية المبسطة", + "zh-tw": "الصينية التقليدية", + "ja": "اليابانية", + "ko": "الكورية", + "es": "الإسبانية", + "de": "الألمانية", + "fr": "الفرنسية", + "pt": "البرتغالية", + "ar": "العربية" } } } diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index eabf52b..9a24d0d 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "Kanäle", "commands": "Befehle", - "events": "Ereignisse" + "events": "Ereignisse", + "other": "Sonstiges" }, "commands": { "title": "Integrierte Befehle", @@ -1740,6 +1741,22 @@ "errorDesc": "Wenn ein Agent einen Fehler feststellt", "saved": "Ereignisfilter aktualisiert.", "saveFailed": "Fehler beim Speichern des Ereignisfilters." + }, + "language": { + "title": "Nachrichtensprache", + "description": "Sprache für Ereignisbenachrichtigungen, Befehlsantworten und tägliche Berichte, die an Chat-Kanäle gesendet werden.", + "saved": "Nachrichtensprache gespeichert.", + "saveFailed": "Fehler beim Speichern der Nachrichtensprache.", + "en": "Englisch", + "zh-cn": "Vereinfachtes Chinesisch", + "zh-tw": "Traditionelles Chinesisch", + "ja": "Japanisch", + "ko": "Koreanisch", + "es": "Spanisch", + "de": "Deutsch", + "fr": "Französisch", + "pt": "Portugiesisch", + "ar": "Arabisch" } } } diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 73159a2..ae94ffe 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1714,7 +1714,8 @@ "tabs": { "channels": "Channels", "commands": "Commands", - "events": "Events" + "events": "Events", + "other": "Other" }, "commands": { "title": "Built-in Commands", @@ -1741,6 +1742,22 @@ "errorDesc": "When an agent encounters an error", "saved": "Event filter updated.", "saveFailed": "Failed to save event filter." + }, + "language": { + "title": "Message Language", + "description": "Language used for event notifications, command responses, and daily reports sent to chat channels.", + "saved": "Message language saved.", + "saveFailed": "Failed to save message language.", + "en": "English", + "zh-cn": "Simplified Chinese", + "zh-tw": "Traditional Chinese", + "ja": "Japanese", + "ko": "Korean", + "es": "Spanish", + "de": "German", + "fr": "French", + "pt": "Portuguese", + "ar": "Arabic" } } } diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 5c03009..e99f98e 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "Canales", "commands": "Comandos", - "events": "Eventos" + "events": "Eventos", + "other": "Otros" }, "commands": { "title": "Comandos integrados", @@ -1740,6 +1741,22 @@ "errorDesc": "Cuando un agente encuentra un error", "saved": "Filtro de eventos actualizado.", "saveFailed": "Error al guardar el filtro de eventos." + }, + "language": { + "title": "Idioma de mensajes", + "description": "Idioma utilizado para las notificaciones de eventos, respuestas de comandos e informes diarios enviados a los canales de chat.", + "saved": "Idioma de mensajes guardado.", + "saveFailed": "Error al guardar el idioma de mensajes.", + "en": "Inglés", + "zh-cn": "Chino simplificado", + "zh-tw": "Chino tradicional", + "ja": "Japonés", + "ko": "Coreano", + "es": "Español", + "de": "Alemán", + "fr": "Francés", + "pt": "Portugués", + "ar": "Árabe" } } } diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 91073d4..161f2f6 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "Canaux", "commands": "Commandes", - "events": "Événements" + "events": "Événements", + "other": "Autres" }, "commands": { "title": "Commandes intégrées", @@ -1740,6 +1741,22 @@ "errorDesc": "Lorsqu'un agent rencontre une erreur", "saved": "Filtre d'événements mis à jour.", "saveFailed": "Échec de l'enregistrement du filtre d'événements." + }, + "language": { + "title": "Langue des messages", + "description": "Langue utilisée pour les notifications d'événements, les réponses aux commandes et les rapports quotidiens envoyés aux canaux de chat.", + "saved": "Langue des messages enregistrée.", + "saveFailed": "Échec de l'enregistrement de la langue des messages.", + "en": "Anglais", + "zh-cn": "Chinois simplifié", + "zh-tw": "Chinois traditionnel", + "ja": "Japonais", + "ko": "Coréen", + "es": "Espagnol", + "de": "Allemand", + "fr": "Français", + "pt": "Portugais", + "ar": "Arabe" } } } diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 20196d8..36d281e 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "チャンネル", "commands": "コマンド", - "events": "イベント" + "events": "イベント", + "other": "その他" }, "commands": { "title": "組み込みコマンド", @@ -1740,6 +1741,22 @@ "errorDesc": "エージェントがエラーに遭遇した時", "saved": "イベントフィルターを更新しました。", "saveFailed": "イベントフィルターの保存に失敗しました。" + }, + "language": { + "title": "メッセージ言語", + "description": "イベント通知、コマンド応答、日次レポートをチャットチャンネルに送信する際に使用する言語。", + "saved": "メッセージ言語を保存しました。", + "saveFailed": "メッセージ言語の保存に失敗しました。", + "en": "英語", + "zh-cn": "簡体字中国語", + "zh-tw": "繁体字中国語", + "ja": "日本語", + "ko": "韓国語", + "es": "スペイン語", + "de": "ドイツ語", + "fr": "フランス語", + "pt": "ポルトガル語", + "ar": "アラビア語" } } } diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index ae040c0..de600b8 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "채널", "commands": "명령어", - "events": "이벤트" + "events": "이벤트", + "other": "기타" }, "commands": { "title": "내장 명령어", @@ -1740,6 +1741,22 @@ "errorDesc": "에이전트에 오류가 발생했을 때", "saved": "이벤트 필터가 업데이트되었습니다.", "saveFailed": "이벤트 필터 저장에 실패했습니다." + }, + "language": { + "title": "메시지 언어", + "description": "이벤트 알림, 명령 응답, 일일 보고서를 채팅 채널로 전송할 때 사용하는 언어입니다.", + "saved": "메시지 언어가 저장되었습니다.", + "saveFailed": "메시지 언어 저장에 실패했습니다.", + "en": "영어", + "zh-cn": "중국어 간체", + "zh-tw": "중국어 번체", + "ja": "일본어", + "ko": "한국어", + "es": "스페인어", + "de": "독일어", + "fr": "프랑스어", + "pt": "포르투갈어", + "ar": "아랍어" } } } diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 47cfa7e..7b1d17a 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "Canais", "commands": "Comandos", - "events": "Eventos" + "events": "Eventos", + "other": "Outros" }, "commands": { "title": "Comandos integrados", @@ -1740,6 +1741,22 @@ "errorDesc": "Quando um agente encontra um erro", "saved": "Filtro de eventos atualizado.", "saveFailed": "Falha ao salvar o filtro de eventos." + }, + "language": { + "title": "Idioma das mensagens", + "description": "Idioma utilizado para notificações de eventos, respostas de comandos e relatórios diários enviados aos canais de chat.", + "saved": "Idioma das mensagens salvo.", + "saveFailed": "Falha ao salvar o idioma das mensagens.", + "en": "Inglês", + "zh-cn": "Chinês simplificado", + "zh-tw": "Chinês tradicional", + "ja": "Japonês", + "ko": "Coreano", + "es": "Espanhol", + "de": "Alemão", + "fr": "Francês", + "pt": "Português", + "ar": "Árabe" } } } diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 8e7f959..5e61727 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1714,7 +1714,8 @@ "tabs": { "channels": "渠道", "commands": "指令", - "events": "事件" + "events": "事件", + "other": "其他" }, "commands": { "title": "内置指令", @@ -1741,6 +1742,22 @@ "errorDesc": "代理遇到错误时", "saved": "事件过滤已更新。", "saveFailed": "保存事件过滤失败。" + }, + "language": { + "title": "消息语言", + "description": "事件通知、指令响应和每日报告推送到消息渠道时使用的语言。", + "saved": "消息语言已保存。", + "saveFailed": "保存消息语言失败。", + "en": "英语", + "zh-cn": "简体中文", + "zh-tw": "繁体中文", + "ja": "日语", + "ko": "韩语", + "es": "西班牙语", + "de": "德语", + "fr": "法语", + "pt": "葡萄牙语", + "ar": "阿拉伯语" } } } diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index b9d07f5..b781849 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1713,7 +1713,8 @@ "tabs": { "channels": "頻道", "commands": "指令", - "events": "事件" + "events": "事件", + "other": "其他" }, "commands": { "title": "內建指令", @@ -1740,6 +1741,22 @@ "errorDesc": "代理遇到錯誤時", "saved": "事件篩選已更新。", "saveFailed": "儲存事件篩選失敗。" + }, + "language": { + "title": "訊息語言", + "description": "事件通知、指令回應和每日報告推送到訊息頻道時使用的語言。", + "saved": "訊息語言已儲存。", + "saveFailed": "儲存訊息語言失敗。", + "en": "英語", + "zh-cn": "簡體中文", + "zh-tw": "繁體中文", + "ja": "日語", + "ko": "韓語", + "es": "西班牙語", + "de": "德語", + "fr": "法語", + "pt": "葡萄牙語", + "ar": "阿拉伯語" } } } diff --git a/src/lib/api.ts b/src/lib/api.ts index 5ea1dff..d196089 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1418,3 +1418,11 @@ export async function setChatEventFilter( ): Promise { return getTransport().call("set_chat_event_filter", { filter }) } + +export async function getChatMessageLanguage(): Promise { + return getTransport().call("get_chat_message_language") +} + +export async function setChatMessageLanguage(language: string): Promise { + return getTransport().call("set_chat_message_language", { language }) +}