本文首发微信公众号:前端徐徐。
前言
在现代桌面应用程序开发中,自定义菜单不仅是用户界面的重要组成部分,还直接影响用户体验和应用功能的可访问性。Tauri,作为一个轻量级的跨平台桌面应用开发框架,提供了强大而灵活的菜单自定义能力。
我们在上一节中实现了主题 & 多语言设置开发,但是多语言和原生菜单并没有联动处理,本文将探讨如何在 Tauri 应用中创建自定义菜单,并实现多语言支持,以满足国际化需求。
背景
Tauri 默认提供了基本的菜单结构,但在许多情况下,开发者需要根据应用的具体需求来定制菜单。自定义菜单允许我们:
- 提供与应用功能紧密相关的选项
- 实现多语言支持,提高应用的国际化水平
- 根据应用状态动态调整菜单项
具体实现
实现多语言支持
我们已经维护了多语言的 JSON 文件,要想在原生菜单中使用我们需要写一个工具类 Translator,具体代码如下:
use std::{env, fs}; use std::collections::HashMap; use std::path::PathBuf; use serde_json::Value; #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] pub enum Language { English, Chinese, } pub struct Translator { translations: HashMap<Language, Value>, } impl Translator { pub fn new() -> Self { let mut translations = HashMap::new(); let resource_path = Self::get_resource_path(); let en_us: Value = serde_json::from_str( &fs::read_to_string(resource_path.join("locales/en-us.json")) .expect("Unable to read en-us.json") ).expect("Invalid JSON"); let zh_cn: Value = serde_json::from_str( &fs::read_to_string(resource_path.join("locales/zh-cn.json")) .expect("Unable to read zh-cn.json") ).expect("Invalid JSON"); translations.insert(Language::English, en_us); translations.insert(Language::Chinese, zh_cn); Translator { translations } } fn get_resource_path() -> PathBuf { PathBuf::from("./") } pub fn translate(&self, key: &str, lang: &Language) -> String { self.translations .get(lang) .and_then(|json| json.get(key)) .and_then(|v| v.as_str()) .unwrap_or(key) .to_string() } }
这段代码实现了一个简单的多语言翻译器 Translator。它使用 Rust 的标准库和 serde_json 库来加载和解析 JSON 文件,将不同语言的翻译文本存储在 HashMap 中。通过 translate 方法,可以根据指定的语言和键值获取对应的翻译文本,如果找不到对应的翻译文本,则返回原始的键值。该翻译器适用于简单的应用程序,能够根据运行环境动态加载正确的翻译资源。
创建基本菜单结构
有了多语言类,我就可以创建菜单的基本结构了,可以写一个创建 menu 的方法,具体代码如下:
use tauri::{Menu, Submenu, CustomMenuItem}; use crate::translator::{Translator, Language}; pub fn create_menu(translator: &Translator, lang: &Language) -> Menu { // File Menu let file_menu = Submenu::new(translator.translate("file", lang), Menu::new() .add_item(CustomMenuItem::new("about".to_string(), translator.translate("about", lang))) .add_item(CustomMenuItem::new("quit".to_string(), translator.translate("quit", lang))) ); // Edit Menu let edit_menu = Submenu::new(translator.translate("edit", lang), Menu::new() .add_item(CustomMenuItem::new("undo".to_string(), translator.translate("undo", lang))) .add_item(CustomMenuItem::new("redo".to_string(), translator.translate("redo", lang))) .add_item(CustomMenuItem::new("cut".to_string(), translator.translate("cut", lang))) .add_item(CustomMenuItem::new("copy".to_string(), translator.translate("copy", lang))) .add_item(CustomMenuItem::new("paste".to_string(), translator.translate("paste", lang))) .add_item(CustomMenuItem::new("selectAll".to_string(), translator.translate("selectAll", lang))) ); // Window Menu let window_menu = Submenu::new(translator.translate("window", lang), Menu::new() .add_item(CustomMenuItem::new("minimize".to_string(), translator.translate("minimize", lang))) .add_item(CustomMenuItem::new("zoom".to_string(), translator.translate("zoom", lang))) .add_item(CustomMenuItem::new("close".to_string(), translator.translate("close", lang))) ); Menu::new() .add_submenu(file_menu) .add_submenu(edit_menu) .add_submenu(window_menu) }
这段代码定义了一个函数 create_menu
,用于创建一个应用程序菜单。该菜单包含三个子菜单:文件菜单、编辑菜单和窗口菜单。每个子菜单通过 translator
对象根据指定语言进行翻译,并包含多个菜单项,如“关于”、“退出”、“撤销”、“重做”等。最终,这些子菜单被添加到主菜单中。
在主函数中创建和处理菜单事件
在这个之前我们需要做一下准备工作,即找一个文件存储多语言配置,到底是 zh 还是 en 我们需要读取文件,因为之前的数据库插件在主进程中是没办法使用的,所以我们需要同步一下两边的设置。需要先写三个函数来辅助我们,如下:
fn get_data_file_path(config: &tauri::Config) -> PathBuf { app_data_dir(config).unwrap().join("lang.data") } fn write_data_to_file(config: &tauri::Config, data: &str) -> Result<(), io::Error> { let file_path = get_data_file_path(config); fs::write(file_path, data) } fn read_data_from_file(config: &tauri::Config) -> Result<String, io::Error> { let file_path = get_data_file_path(config); fs::read_to_string(file_path) }
这三个函数分别是获取数据文件路径,写数据,读数据,有了这三个函数,我们就可以在 main 函数中自由读取多语言的配置,并根据情况创建菜单了,代码如下:
fn main() { let translator = Arc::new(Mutex::new(Translator::new())); let mut ctx = generate_context!(); let config = ctx.config().clone(); let mut initial_lang: Option<Language> = None; // 首先尝试从文件读取语言设置 match read_data_from_file(&config) { Ok(data) => { initial_lang = match data.as_str() { "zh" => Some(Language::Chinese), "en" => Some(Language::English), // 可以添加其他语言 _ => None, }; } Err(e) => { eprintln!("Error reading file: {}", e); } } // 如果文件中没有有效的语言设置,则尝试使用系统语言 if initial_lang.is_none() { if let Some(locale) = tauri::api::os::locale() { initial_lang = if locale.starts_with("zh") { Some(Language::Chinese) } else if locale.starts_with("en") { Some(Language::English) } else { None }; } } // 如果仍然没有设置语言,使用默认语言(这里设置为英语) let initial_lang = initial_lang.unwrap_or(Language::English); let menu = create_menu(&translator.lock().unwrap(), &initial_lang); Builder::default() .menu(menu) .on_menu_event(|event| { let window = event.window(); match event.menu_item_id() { "about" => { tauri::api::dialog::message(Some(&window), "关于", "XTools, 完全本地化工具。"); } "quit" => { process::exit(0); } "undo" => { window.eval("document.execCommand('undo')").unwrap(); } "redo" => { window.eval("document.execCommand('redo')").unwrap(); } "cut" => { window.eval("document.execCommand('cut')").unwrap(); } "copy" => { window.eval("document.execCommand('copy')").unwrap(); } "paste" => { window.eval("document.execCommand('paste')").unwrap(); } "selectAll" => { window.eval("document.execCommand('selectAll')").unwrap(); } "close" => { window.close().unwrap(); } "minimize" => { window.minimize().unwrap(); } "zoom" => { if window.is_maximized().unwrap() { window.unmaximize().unwrap(); } else { window.maximize().unwrap(); } } _ => {} } }) .manage(translator.clone()) }
这段代码主要完成以下任务:
- 初始化翻译器:创建一个 Translator 实例并用 Arc 和 Mutex 包装。
- 读取语言设置:尝试从配置文件读取语言设置,如果失败则尝试使用系统语言,若仍无效则默认使用英语。
- 创建菜单:调用 create_menu 函数,根据翻译器和初始语言创建应用程序菜单。
- 处理菜单事件:设置菜单事件处理函数,处理各种菜单项事件,如“关于”、“退出”、“撤销”、“重做”等。
最终,代码通过 Builder 构建应用,并管理翻译器实例。
总结
通过以上步骤,我们成功地在 Tauri 应用中实现了自定义菜单,并支持多语言切换。这种方法不仅提高了应用的可用性,还增强了其国际化能力。关键点包括:
- 使用 Tauri 提供的 API 构建菜单结构
- 实现简单的翻译器以支持多语言
- 在菜单创建过程中应用翻译
- 处理菜单事件以响应用户操作
自定义菜单为 Tauri 应用提供了更大的灵活性,允许开发者创建与应用紧密集成、符合用户需求的界面。通过添加多语言支持,我们进一步提升了应用的全球适用性。
在实际开发中,您可能需要根据具体需求进行更复杂的菜单设计和事件处理。此外,考虑使用更强大的国际化库来处理大型应用的翻译需求。