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 支持哪些语言”的组件都从这份列表取数,具体包括:
- Settings 语言选择器:用户切换界面语言的下拉框;
i18n/translations/下的 locale 文件:应用各界面模块的翻译文件集合;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 存储值、翻译命令目标语言的匹配键。代码格式上既有双字母代码(es、de),也有带区域后缀的代码(pt-BR、pt-PT、zh-CN、zh-TW)。
列表使用const fn lang(...)构造,使整个数组成为编译期常量,零运行时分配。
当前支持的 74 种语言
以下是 libs/core/src/constants/mod.rs 中的完整列表(按文件中顺序),以value代码为匹配键:
| value | 语言(en_label) | value | 语言(en_label) |
|---|---|---|---|
| af | Afrikaans | ko | Korean |
| am | Amharic | ku | Kurdish |
| ar | Arabic | lb | Luxembourgish |
| az | Azerbaijani | lo | Lao |
| bg | Bulgarian | lt | Lithuanian |
| bn | Bengali | lv | Latvian |
| bs | Bosnian | mk | Macedonian |
| ca | Catalan | mn | Mongolian |
| cs | Czech | ms | Malay |
| cy | Welsh | mt | Maltese |
| da | Danish | ne | Nepali |
| de | German | nl | Dutch |
| el | Greek | no | Norwegian |
| en | English | pa | Punjabi |
| es | Spanish | pl | Polish |
| et | Estonian | ps | Pashto |
| eu | Basque | pt-BR | Portuguese (Brazil) |
| fa | Persian | pt-PT | Portuguese (Portugal) |
| fi | Finnish | ro | Romanian |
| fr | French | ru | Russian |
| gu | Gujarati | si | Sinhala |
| he | Hebrew | sk | Slovak |
| hi | Hindi | so | Somali |
| hr | Croatian | sr | Serbian |
| hu | Hungarian | sv | Swedish |
| hy | Armenian | sw | Swahili |
| id | Indonesian | ta | Tamil |
| is | Icelandic | te | Telugu |
| it | Italian | tg | Tajik |
| ja | Japanese | th | Thai |
| ka | Georgian | tl | Filipino |
| km | Khmer | tr | Turkish |
| uk | Ukrainian | ur | Urdu |
| uz | Uzbek | vi | Vietnamese |
| yo | Yoruba | zh-CN | Chinese (Simplified) |
| zh-TW | Chinese (Traditional) | zu | Zulu |
注意几个命名约定:
- 中文只有
zh-CN/zh-TW,葡萄牙语只有pt-BR/pt-PT,不存在裸的zh或pt代码。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/core从mod.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:rsbuild: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 的实现看,整个流水线分为五步:
- 清理旧绑定:删除并重建
./gen/types目录(L3-L16); - 生成 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; - 创建入口:为
gen/types下的所有生成文件写mod.ts聚合导出(L44-L69); - 抽取类型文档:用
deno doc --json分别提取gen/types/mod.ts与src/lib.ts的 API 定义,输出gen/doc-types.json与gen/doc-lib.json(L71-L97); - 格式化:依次
cargo fmt与deno 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() }这段代码揭示了系统语言解析的两级匹配算法:
- 精确匹配:系统 locale(如
zh-CN、pt-BR)直接与列表中某个value完全相等则采用; - 基础语言回退:取 locale 按
-分割后的首段(如zh-HK→zh、pt→pt)再匹配一次; - 兜底:都匹配不上则返回
en。
这里也再次印证了第二节的命名约定:由于列表中只有zh-CN/zh-TW而没有裸zh,一个zh-HK的系统 locale 经过两级匹配后都会落到en兜底——这解释了为什么中文用户需要zh-CN或zh-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.yml、am.yml、zh-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::Resource走Direct执行模式——直接在 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/translations、src/ui/svelte/weg/i18n/translations等 14 个界面模块目录以及src/background/i18n(scripts/translate/mod.ts#L46-L64)逐个补齐缺失译文。从源码结构看,应用界面翻译与资源翻译共用同一套“哈希缓存 + 跳过已有译文”策略,只是前者面向应用自身 locale 文件,后者面向第三方资源的i18n/目录。
六、实操工作流:新增一种支持语言
结合上述机制,为 Seelen UI 新增一种语言的完整步骤是:
- 修改单一数据源:在 libs/core/src/constants/mod.rs 的
SUPPORTED_LANGUAGES中按字母序插入一行,例如:lang("Srpski", "Serbian", "sr"), // label 用母语名称,value 用标准 BCP-47 代码若该语言存在区域变体,请沿用
xx-REGION形式(参照pt-BR、zh-CN的先例),不要使用裸代码。 - 重新生成前端绑定:
cd libs/core && deno task build:rs确认 libs/core/src/constants/mod.ts 中出现了对应条目(
enLabel与 Rust 端en_label一致)。 - 补齐应用 locale 文件:在相应的
i18n目录(如src/background/i18n/)新建<code>.yml,可运行 scripts/translate/mod.ts 从en.yml自动补齐,并按惯例人工校对机器翻译。 - 验证消费端自动生效:
- 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),仅供参考