Seelen UI 支持语言机制详解:SUPPORTED_LANGUAGES 单一数据源、类型绑定生成与 slu 翻译工作流
2026/9/13 1:39:52 网站建设 项目流程

Seelen UI 支持语言机制详解:SUPPORTED_LANGUAGES 单一数据源、类型绑定生成与 slu 翻译工作流

【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI

本文围绕 Seelen UI 仓库中documentation/supported-languages.md文档展开,讲解“Seelen UI 支持哪些语言”这一问题的权威列表是如何定义、生成与消费的:从 Rust 常量SUPPORTED_LANGUAGES这一唯一数据源,到deno task build:rs生成前端 TypeScript 绑定,再到设置界面语言选择器与slu resource translate命令如何从同一份列表派生行为。读完后你将掌握 Seelen UI 语言体系的完整架构、当前支持的 74 种语言清单、绑定再生成的完整命令,以及为新语言扩展或翻译资源文本的实操流程。

一、设计原则:一份列表,三处消费

Seelen UI 在应用范围内维护一份单一权威语言列表,所有需要回答“Seelen UI 支持哪些语言”的组件都从这份列表取数,具体包括:

  1. Settings 语言选择器:用户切换界面语言的下拉框;
  2. i18n/translations/下的 locale 文件:应用各界面模块的翻译文件集合;
  3. slu resource translateCLI 命令:资源作者翻译ResourceText字段时自动补齐所有目标语言。

这样做的好处在 documentation/supported-languages.md 中说得非常直接:因为前端与 CLI 都从同一份 Rust 源码派生,所以“UI 提供的语言”与“资源被翻译成的语言”永远不会出现漂移(drift)。文档因此也给出了一条维护约定——不要在文档里另存一份语言清单副本,需要当前准确列表时直接查mod.rs

二、列表定义在哪里:Rust 常量SUPPORTED_LANGUAGES

语言列表的唯一定义位置是 Rust 侧的 libs/core/src/constants/mod.rs:

pub const SUPPORTED_LANGUAGES: &[SupportedLanguage] = &[ lang("Afrikaans", "Afrikaans", "af"), lang("አማርኛ", "Amharic", "am"), lang("العربية", "Arabic", "ar"), // ... 共 74 条 lang("isiZulu", "Zulu", "zu"), ];

结构定义在 libs/core/src/constants/mod.rs#L76-L92:

pub struct SupportedLanguage { pub label: &'static str, // 语言的母语名称,如 "Deutsch"、"日本語" pub en_label: &'static str, // 语言的英文名称,如 "German"、"Japanese" pub value: &'static str, // 语言代码,如 "es"、"pt-BR"、"zh-CN" } const fn lang( label: &'static str, en_label: &'static str, value: &'static str, ) -> SupportedLanguage { SupportedLanguage { label, en_label, value, } }

三个字段各有用途:

  • label:母语名称,用于 UI 中以用户自己的语言展示语言名(如德语界面里显示 “Deutsch” 而不是 “German”);
  • en_label:英文名称,作为排序、调试输出的稳定参照;
  • value:语言代码,是贯穿 locale 文件名、Settings 存储值、翻译命令目标语言的匹配键。代码格式上既有双字母代码(esde),也有带区域后缀的代码(pt-BRpt-PTzh-CNzh-TW)。

列表使用const fn lang(...)构造,使整个数组成为编译期常量,零运行时分配。

当前支持的 74 种语言

以下是 libs/core/src/constants/mod.rs 中的完整列表(按文件中顺序),以value代码为匹配键:

value语言(en_label)value语言(en_label)
afAfrikaanskoKorean
amAmharickuKurdish
arArabiclbLuxembourgish
azAzerbaijaniloLao
bgBulgarianltLithuanian
bnBengalilvLatvian
bsBosnianmkMacedonian
caCatalanmnMongolian
csCzechmsMalay
cyWelshmtMaltese
daDanishneNepali
deGermannlDutch
elGreeknoNorwegian
enEnglishpaPunjabi
esSpanishplPolish
etEstonianpsPashto
euBasquept-BRPortuguese (Brazil)
faPersianpt-PTPortuguese (Portugal)
fiFinnishroRomanian
frFrenchruRussian
guGujaratisiSinhala
heHebrewskSlovak
hiHindisoSomali
hrCroatiansrSerbian
huHungariansvSwedish
hyArmenianswSwahili
idIndonesiantaTamil
isIcelandicteTelugu
itItaliantgTajik
jaJapanesethThai
kaGeorgiantlFilipino
kmKhmertrTurkish
ukUkrainianurUrdu
uzUzbekviVietnamese
yoYorubazh-CNChinese (Simplified)
zh-TWChinese (Traditional)zuZulu

注意几个命名约定:

  • 中文只有zh-CN/zh-TW,葡萄牙语只有pt-BR/pt-PT,不存在裸的zhpt代码。documentation/resource-text.md 特别警告:手动写zh:pt:键永远不会与运行中应用的语言匹配,会被静默忽略。
  • en(English)也在列表内,它是ResourceText的强制兜底语言。

三、TypeScript 绑定如何生成:deno task build:rs

documentation/supported-languages.md 强调了一条硬性规则:绝不手改libs/core/src/constants/mod.ts,它是生成产物。该文件由libs/coremod.rs自动生成,导出前端使用的SupportedLanguages常量,见 libs/core/src/constants/mod.ts:

export type SupportedLanguagesCode = (typeof SupportedLanguages)[number]["value"]; export interface SupportedLanguage { label: string; enLabel: string; /** language code @example 'de' 'es' 'zh' 'en-US' 'en-UK' */ value: string; } export const SupportedLanguages = [ { label: "Afrikaans", enLabel: "Afrikaans", value: "af" }, { label: "አማርኛ", enLabel: "Amharic", value: "am" }, // ... 与 mod.rs 逐条一一对应 { label: "isiZulu", enLabel: "Zulu", value: "zu" }, ] as const;

对照两边的源码可以看到一一对应关系:Rust 端的en_label字段在 TS 端按camelCase约定变为enLabel;Rust 的&[SupportedLanguage]静态数组变为 TS 的as const只读元组;SupportedLanguagesCode类型则把全部合法语言代码收敛成一个字面量联合类型,供前端做穷尽性检查。

要增加、删除或重命名一个受支持语言,标准流程是:

# 1. 编辑 libs/core/src/constants/mod.rs 中的 SUPPORTED_LANGUAGES # 2. 重新生成绑定: cd libs/core && deno task build:rs

build:rs任务的定义在 libs/core/deno.json 中,它执行 libs/core/scripts/rust_bindings.ts:

"tasks": { "build:npm": "deno run -A ./scripts/build_npm.ts", "build:rs": "deno run -A ./scripts/rust_bindings.ts", "build": "deno task build:rs && deno task build:npm" }

从 libs/core/scripts/rust_bindings.ts 的实现看,整个流水线分为五步:

  1. 清理旧绑定:删除并重建./gen/types目录(L3-L16);
  2. 生成 TS 绑定:运行cargo test --features gen-binds——绑定生成被实现在cargo test里而非独立二进制中(脚本注释原话是 "cargo test generates the typescript bindings, why? ask to @aleph-alpha/ts-rs xd"),这也解释了为什么libs/core的 Rust 结构体上普遍挂着#[cfg_attr(all(feature = "gen-binds", not(feature = "salvo")), derive(ts_rs::TS))]这类条件 derive;
  3. 创建入口:为gen/types下的所有生成文件写mod.ts聚合导出(L44-L69);
  4. 抽取类型文档:用deno doc --json分别提取gen/types/mod.tssrc/lib.ts的 API 定义,输出gen/doc-types.jsongen/doc-lib.json(L71-L97);
  5. 格式化:依次cargo fmtdeno fmt(L99-L109)。

因此mod.ts中的语言数组是每次构建的确定性产物——手改它只会在下次build:rs时被动机覆盖,这正是文档要求“永不手改”的原因。

四、Settings 如何消费这份列表:系统语言回退匹配

设置结构体中的语言字段定义在 libs/core/src/state/settings/mod.rs#L478-L479:

/// language to use, if null the system locale is used pub language: String,

而真正与SUPPORTED_LANGUAGES交互的逻辑是同文件的Settings::get_app_language()(libs/core/src/state/settings/mod.rs#L558-L577):

pub fn get_app_language() -> String { use crate::constants::SUPPORTED_LANGUAGES; let Some(sys_locale) = sys_locale::get_locale() else { return "en".to_string(); }; if SUPPORTED_LANGUAGES.iter().any(|l| l.value == sys_locale) { return sys_locale; } let Some(base) = sys_locale.split('-').next() else { return "en".to_string(); }; if SUPPORTED_LANGUAGES.iter().any(|l| l.value == base) { return base.to_string(); } "en".to_string() }

这段代码揭示了系统语言解析的两级匹配算法

  1. 精确匹配:系统 locale(如zh-CNpt-BR)直接与列表中某个value完全相等则采用;
  2. 基础语言回退:取 locale 按-分割后的首段(如zh-HKzhptpt)再匹配一次;
  3. 兜底:都匹配不上则返回en

这里也再次印证了第二节的命名约定:由于列表中只有zh-CN/zh-TW而没有裸zh,一个zh-HK的系统 locale 经过两级匹配后都会落到en兜底——这解释了为什么中文用户需要zh-CNzh-TW这类带区域代码的语言包。

sanitize()(libs/core/src/state/settings/mod.rs#L612-L629)中还有一处兜底:当持久化的language为空字符串时,重新调用get_app_language()从系统 locale 推导,保证语言字段永远非空。

应用级的翻译文件就按这些value代码组织。以 scripts/translate/mod.ts#L64 处理的src/background/i18n目录为例,其中的af.ymlam.ymlzh-CN.yml等 68 个文件与SUPPORTED_LANGUAGES中的语言代码一一对应(另有en.yml作为源语言、hash.yml记录译文哈希)。

五、资源侧消费:slu resource translate与内部翻译脚本

5.1slu resource translate命令

资源作者使用的翻译命令由 src/slu/resources.rs 实现。在sluCLI 的入口 src/slu/main.rs#L36-L43 中可以看到AppCommand::ResourceDirect执行模式——直接在 CLI 进程内完成,不需要把命令转发给主实例:

async fn process_direct(cli: AppCli) -> Result<()> { match cli.command { AppCommand::Art(cmd) => art::process(cmd), AppCommand::Resource(cmd) => resources::process(cmd).await?, _ => return Err("Command does not support direct execution".into()), } Ok(()) }

命令用法(详见 documentation/resource-text.md 第 4 节):

slu resource translate <path/to/file.yml> [source_lang]
  • <path/to/file.yml>——包含ResourceText值(纯字符串或语言映射)的 YAML 文件;
  • [source_lang]——源文本的语言代码,默认en

工作流:先确认文件中存在source_lang条目(没有则报错);然后遍历 Seelen UI 支持的每种语言,已有译文的语言跳过、绝不覆盖,缺失的调用 Google Translate API 补齐;最后原地写回文件。示例输出:

slu resource translate i18n/display_name.yml
[01/68] English (Afrikaans) => "Speel Rekenaar" [02/68] Amharic => "ጨዋታ ኮምፒተር" ... [14/68] English => Skipped ...

非英语源文本需显式传语言代码:

slu resource translate i18n/description.yml es

由于“目标语言”直接来自SUPPORTED_LANGUAGES,在mod.rs中新增语言后,slu resource translate会自动为每个资源的每个ResourceText字段多翻译这一种语言——资源作者侧无需任何改动。这也是原文档“Why this matters for resources”一节的核心结论。

5.2 内部 i18n 补齐脚本

仓库自身界面文本的翻译补齐在 scripts/translate/mod.ts,它展示了前端绑定SupportedLanguages的真实消费方式:

import { GoogleTranslator, ObjectTranslator } from "@seelen/translation-toolkit"; import { SupportedLanguages } from "@seelen-ui/lib"; const targetLanguages = SupportedLanguages.filter((lang) => lang.value !== "en");

脚本以en.yml为源、hash.yml中的缓存哈希表避免重复翻译,遍历除en外的全部目标语言,对src/ui/react/settings/i18n/translationssrc/ui/svelte/weg/i18n/translations等 14 个界面模块目录以及src/background/i18n(scripts/translate/mod.ts#L46-L64)逐个补齐缺失译文。从源码结构看,应用界面翻译与资源翻译共用同一套“哈希缓存 + 跳过已有译文”策略,只是前者面向应用自身 locale 文件,后者面向第三方资源的i18n/目录。

六、实操工作流:新增一种支持语言

结合上述机制,为 Seelen UI 新增一种语言的完整步骤是:

  1. 修改单一数据源:在 libs/core/src/constants/mod.rs 的SUPPORTED_LANGUAGES中按字母序插入一行,例如:
    lang("Srpski", "Serbian", "sr"), // label 用母语名称,value 用标准 BCP-47 代码

    若该语言存在区域变体,请沿用xx-REGION形式(参照pt-BRzh-CN的先例),不要使用裸代码。

  2. 重新生成前端绑定
    cd libs/core && deno task build:rs

    确认 libs/core/src/constants/mod.ts 中出现了对应条目(enLabel与 Rust 端en_label一致)。

  3. 补齐应用 locale 文件:在相应的i18n目录(如src/background/i18n/)新建<code>.yml,可运行 scripts/translate/mod.ts 从en.yml自动补齐,并按惯例人工校对机器翻译。
  4. 验证消费端自动生效
    • Settings 语言选择器出现新语言(前提是第 3 步的 locale 文件存在,文档明确指出“once a matching locale file exists”);
    • 对既有资源执行slu resource translate,新语言会作为缺失项被自动翻译,已有语言保持 Skipped。

七、约束与注意事项

综合 documentation/supported-languages.md 与源码,维护语言体系时有几条硬约束:

  • mod.ts是生成文件,禁止手改;一切变更走mod.rs+deno task build:rs
  • 文档不复制清单:任何文档需要列举语言时,以 libs/core/src/constants/mod.rs 为准,避免多份清单漂移;
  • en是强制键ResourceText的映射形式中en缺失会导致资源校验失败、无法发布(见 documentation/resource-text.md);
  • 区域代码是精确匹配:系统 locale 匹配采用“精确 → 基础语言 →en兜底”的三级策略,手写zh:/pt:这类列表外代码会被渲染层静默忽略;
  • 机器翻译只是初稿slu resource translate基于 Google Translate,对短小的 UI 标签容易产生生硬或错误译法,发布前应人工复核,且该命令按文件执行、不递归目录,应作为打包前的一次性步骤而非例行操作。

从架构上看,Seelen UI 把“支持哪些语言”收敛为一个 Rust 编译期常量,并通过ts-rs风格的测试期绑定生成把它无漂移地投射到前端与 CLI 两侧,是一份“单一数据源 + 生成绑定 + 双端消费”的典型国际化设计样本。

【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询