Tauri 开发实践 — Tauri 自定义多语言菜单开发

简介: 本文介绍了如何在 Tauri 应用中实现自定义菜单并支持多语言。首先,通过 `Translator` 类加载和解析多语言 JSON 文件,实现简单的翻译功能。接着,创建包含文件、编辑和窗口子菜单的基本菜单结构,并根据当前语言进行翻译。最后,在主函数中读取语言设置,创建菜单并处理菜单事件,确保应用的国际化和用户体验。源码可在 GitHub 上查看。

本文首发微信公众号:前端徐徐。

前言

在现代桌面应用程序开发中,自定义菜单不仅是用户界面的重要组成部分,还直接影响用户体验和应用功能的可访问性。Tauri,作为一个轻量级的跨平台桌面应用开发框架,提供了强大而灵活的菜单自定义能力。

我们在上一节中实现了主题 & 多语言设置开发,但是多语言和原生菜单并没有联动处理,本文将探讨如何在 Tauri 应用中创建自定义菜单,并实现多语言支持,以满足国际化需求。

背景

Tauri 默认提供了基本的菜单结构,但在许多情况下,开发者需要根据应用的具体需求来定制菜单。自定义菜单允许我们:

  1. 提供与应用功能紧密相关的选项
  2. 实现多语言支持,提高应用的国际化水平
  3. 根据应用状态动态调整菜单项

具体实现

实现多语言支持

我们已经维护了多语言的 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())
}

这段代码主要完成以下任务:

  1. 初始化翻译器:创建一个 Translator 实例并用 Arc 和 Mutex 包装。
  2. 读取语言设置:尝试从配置文件读取语言设置,如果失败则尝试使用系统语言,若仍无效则默认使用英语。
  3. 创建菜单:调用 create_menu 函数,根据翻译器和初始语言创建应用程序菜单。
  4. 处理菜单事件:设置菜单事件处理函数,处理各种菜单项事件,如“关于”、“退出”、“撤销”、“重做”等。

最终,代码通过 Builder 构建应用,并管理翻译器实例。

总结

通过以上步骤,我们成功地在 Tauri 应用中实现了自定义菜单,并支持多语言切换。这种方法不仅提高了应用的可用性,还增强了其国际化能力。关键点包括:

  1. 使用 Tauri 提供的 API 构建菜单结构
  2. 实现简单的翻译器以支持多语言
  3. 在菜单创建过程中应用翻译
  4. 处理菜单事件以响应用户操作

自定义菜单为 Tauri 应用提供了更大的灵活性,允许开发者创建与应用紧密集成、符合用户需求的界面。通过添加多语言支持,我们进一步提升了应用的全球适用性。

在实际开发中,您可能需要根据具体需求进行更复杂的菜单设计和事件处理。此外,考虑使用更强大的国际化库来处理大型应用的翻译需求。

源码

https://github.com/Xutaotaotao/XTools/tree/feature-menu

相关文章
|
1天前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
10 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
1天前
|
存储 前端开发 安全
Tauri 开发实践 — Tauri 原生能力
本文介绍了如何使用 Tauri 框架构建桌面应用,并详细解释了 Tauri 提供的原生能力,包括文件系统访问、系统托盘、本地消息通知等。文章通过一个具体的文件下载示例展示了如何配置 Tauri 来使用文件系统相关的原生能力,并提供了完整的代码实现。最后,文章还提供了 Github 源码链接,方便读者进一步学习和参考。
13 1
Tauri 开发实践 — Tauri 原生能力
|
1天前
|
资源调度 JavaScript 前端开发
如何实现一个类似 vite 的脚手架并发布 npm
本文介绍了如何实现一个类似 Vite 的脚手架工具。通过详细解析和实践,文章分享了从零开始构建脚手架的过程,包括技术选型、开发步骤及发布 NPM 包的完整流程。最终目标是让用户能够通过 `yarn create electron-prokit myapp` 快速搭建 Electron 项目。项目源码可在 GitHub 上获取。
12 5
|
1天前
|
缓存 资源调度 前端开发
前端研发链路之包管理器
本文首发于微信公众号“前端徐徐”。作者徐徐将探讨前端研发链路中的包管理器,分析 Npm、Yarn 和 Pnpm 的特点与应用场景,帮助开发者选择最适合项目的包管理工具,提升开发效率和项目稳定性。文章涵盖包管理器的基本概念、解决的问题、组成部分及各工具的优缺点对比。
10 2
|
1月前
|
存储
CCF推荐A类会议和期刊总结:计算机体系结构/并行与分布计算/存储系统领域
中国计算机学会(CCF)2022年版推荐目录涵盖了计算机体系结构、并行与分布计算、存储系统领域的多个A类会议和期刊。本文汇总了这些顶级资源的全称、出版社、dblp网址及领域。包括《ACM计算机系统汇刊》、《ACM存储汇刊》等期刊,以及ACM PPoPP、USENIX FAST等会议,为研究人员提供了重要学术参考。
CCF推荐A类会议和期刊总结:计算机体系结构/并行与分布计算/存储系统领域
|
5天前
|
测试技术
软件测试区分:条件组合覆盖、语句覆盖、判定覆盖、条件覆盖、路径覆盖
本文解释了软件测试中的不同覆盖标准,包括语句覆盖、判定覆盖、条件覆盖、条件组合覆盖和路径覆盖,并讨论了每种覆盖标准的特点、优点和缺点。
121 62
|
2天前
|
SQL JSON 数据库
DataFrame
【10月更文挑战第15天】
15 7
|
1天前
|
机器学习/深度学习 人工智能 数据挖掘
机器学习基础:使用Python和Scikit-learn入门
【10月更文挑战第6天】在人工智能领域,机器学习已成为核心技术。本文指导初学者使用Python与Scikit-learn入门机器学习,涵盖基本概念、环境搭建、数据处理、模型训练及评估等环节。Python因简洁性及其生态系统成为首选语言,而Scikit-learn则提供了丰富工具,简化数据挖掘与分析流程。通过实践示例,帮助读者快速掌握基础知识,为进一步深入研究奠定坚实基础。
8 4
|
2天前
|
存储 编译器 Python
Python--变量、输出与输入
【10月更文挑战第5天】
|
1天前
|
定位技术 Python
Matplotlib 教程 之 Matplotlib imshow() 方法 5
Matplotlib 的 `imshow()` 方法用于显示图像,包括二维灰度图像和彩色图像。该方法支持多种参数,如颜色映射 (`cmap`)、归一化 (`norm`)、纵横比 (`aspect`) 等,以控制图像的显示效果。示例中展示了如何使用 `imshow()` 显示一个随机生成的矩阵。
9 2