From cd5b175cc111d911d5e27826a63a4fa51c175ac5 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 13 Jul 2026 15:29:26 +0800 Subject: [PATCH 1/2] feat(i18n): extend component locales in story Demonstrate rust-i18n 4.2 locale extensions with Story-provided French calendar translations while preserving the component's built-in locales. Co-authored-by: Codex --- Cargo.lock | 23 ++++++++---------- Cargo.toml | 2 +- crates/story/locales/ui.yml | 44 +++++++++++++++++++++++++++++++++++ crates/story/src/app_menus.rs | 1 + crates/story/src/lib.rs | 20 ++++++++++++++++ 5 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 crates/story/locales/ui.yml diff --git a/Cargo.lock b/Cargo.lock index 13b40505da..f64959d0c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ "bitflags 2.11.1", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -2847,7 +2847,7 @@ dependencies = [ "log", "presser", "thiserror 2.0.18", - "windows 0.58.0", + "windows 0.62.2", ] [[package]] @@ -3732,7 +3732,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core 0.62.2", ] [[package]] @@ -5084,7 +5084,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -6864,9 +6864,9 @@ dependencies = [ [[package]] name = "rust-i18n" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21031bf5e6f2c0ae745d831791c403608e99a8bd3776c7e5e5535acd70c3b7ba" +checksum = "752c95174ef2555cd95873d6f12d0bee9f0a1dcbf1b631dde847053a29174996" dependencies = [ "globwalk", "regex", @@ -6877,9 +6877,9 @@ dependencies = [ [[package]] name = "rust-i18n-macro" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fe5295763b358606f7ca26a564e20f4469775a57ec1f09431249a33849ff52" +checksum = "cc99fd028c912b59841bd745ec11c2dda112549ae6de25a850f384bb59d1a8f8" dependencies = [ "glob", "proc-macro2", @@ -6893,18 +6893,15 @@ dependencies = [ [[package]] name = "rust-i18n-support" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69bcc115c8eea2803aa3d85362e339776f4988a0349f2f475af572e497443f6f" +checksum = "3dfb30689b70c7fd955e8e5c709fa055a778ae024ef6a382c4eb425dd2c2fabb" dependencies = [ "arc-swap", "base62", "globwalk", "itertools 0.11.0", - "lazy_static", "normpath", - "proc-macro2", - "regex", "serde", "serde_json", "serde_yaml", diff --git a/Cargo.toml b/Cargo.toml index 0bc3fabb25..a84336f118 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ ropey = { version = "=2.0.0-beta.1", features = [ "metric_lines_lf", "metric_utf16", ] } -rust-i18n = "4" +rust-i18n = "4.2.0" schemars = "1" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1" diff --git a/crates/story/locales/ui.yml b/crates/story/locales/ui.yml new file mode 100644 index 0000000000..e005931a12 --- /dev/null +++ b/crates/story/locales/ui.yml @@ -0,0 +1,44 @@ +_version: 2 +gpui_component: + Calendar: + week.0: + fr: Di + week.1: + fr: Lu + week.2: + fr: Ma + week.3: + fr: Me + week.4: + fr: Je + week.5: + fr: Ve + week.6: + fr: Sa + month.January: + fr: Janvier + month.February: + fr: Février + month.March: + fr: Mars + month.April: + fr: Avril + month.May: + fr: Mai + month.June: + fr: Juin + month.July: + fr: Juillet + month.August: + fr: Août + month.September: + fr: Septembre + month.October: + fr: Octobre + month.November: + fr: Novembre + month.December: + fr: Décembre + DatePicker: + placeholder: + fr: Sélectionner une date diff --git a/crates/story/src/app_menus.rs b/crates/story/src/app_menus.rs index 4792a47be4..7b1387c26e 100644 --- a/crates/story/src/app_menus.rs +++ b/crates/story/src/app_menus.rs @@ -126,6 +126,7 @@ fn language_menu(_: &App) -> MenuItem { items: vec![ MenuItem::action("English", SelectLocale("en".into())).checked(locale == "en"), MenuItem::action("简体中文", SelectLocale("zh-CN".into())).checked(locale == "zh-CN"), + MenuItem::action("Français", SelectLocale("fr".into())).checked(locale == "fr"), ], disabled: false, }) diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 8fdd6e9a87..8bda171daa 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -29,6 +29,8 @@ pub use crate::title_bar::AppTitleBar; pub use gallery::Gallery; pub use stories::*; +rust_i18n::i18n!("locales", fallback = "en"); + #[derive(Action, Clone, PartialEq, Eq, Deserialize)] #[action(namespace = story, no_json)] pub struct SelectScrollbarShow(ScrollbarShow); @@ -180,6 +182,7 @@ pub fn init(cx: &mut App) { .try_init(); } + rust_i18n::extend!(gpui_component); gpui_component::init(cx); AppState::init(cx); themes::init(cx); @@ -716,3 +719,20 @@ impl Render for StoryRoot { ) } } + +#[cfg(test)] +mod tests { + #[test] + fn extends_component_translations_with_story_locales() { + rust_i18n::extend!(gpui_component); + + assert_eq!( + gpui_component::_rust_i18n_try_translate("fr", "Calendar.month.January"), + Some("Janvier".into()) + ); + assert_eq!( + gpui_component::_rust_i18n_try_translate("en", "Calendar.month.January"), + Some("January".into()) + ); + } +} From 8fa9410949406faeea1aa1c92cbef8b776b2ff97 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 13 Jul 2026 15:32:52 +0800 Subject: [PATCH 2/2] docs: explain custom component translations Document how applications add or override component locales with rust-i18n extend!, including namespace and lookup priority. Co-authored-by: Codex --- docs/docs/i18n.md | 92 +++++++++++++++++++++++++++++++++++++++++ docs/zh-CN/docs/i18n.md | 92 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 docs/docs/i18n.md create mode 100644 docs/zh-CN/docs/i18n.md diff --git a/docs/docs/i18n.md b/docs/docs/i18n.md new file mode 100644 index 0000000000..47fe3edf28 --- /dev/null +++ b/docs/docs/i18n.md @@ -0,0 +1,92 @@ +--- +title: Custom I18n +description: Add or override GPUI Component translations from your application. +order: -3 +--- + +# Custom I18n + +GPUI Component includes translations for its components. Applications can add a language or override individual translations without copying the complete built-in locale files. + +This feature requires `rust-i18n` 4.2 or later. + +## Add the dependency + +Add `rust-i18n` to the application crate that owns your locale files: + +```toml +[dependencies] +gpui-component = "0.5" +rust-i18n = "4.2" +``` + +## Create an application locale file + +Create `locales/ui.yml` in your application crate. Put component translations under the `gpui_component` namespace: + +```yaml +_version: 2 +gpui_component: + Calendar: + week.0: + fr: Di + month.January: + fr: Janvier + DatePicker: + placeholder: + fr: Sélectionner une date +``` + +The namespace must be `gpui_component`, matching the Rust crate name `gpui-component` with hyphens converted to underscores. Translation keys can be found in [GPUI Component's built-in locale file](https://github.com/longbridge/gpui-component/blob/main/crates/ui/locales/ui.yml). + +## Register the extension + +Initialize the application's locales at the crate root: + +```rust +rust_i18n::i18n!("locales", fallback = "en"); +``` + +Register them before initializing GPUI Component: + +```rust +app.run(move |cx| { + rust_i18n::extend!(gpui_component); + gpui_component::init(cx); + + // Open windows and initialize the rest of the application. +}); +``` + +Call `extend!` only once during application startup. + +## Lookup priority + +The application's translations take priority over the component's built-in translations: + +```text +Application locales (locales/ui.yml) + │ + │ key not found + ▼ +GPUI Component built-in locales +``` + +This deep-merge behavior means that: + +- A new locale, such as `fr`, can contain only the keys your application needs. +- A translation with the same locale and key overrides the built-in value. +- Keys not supplied by the application continue to use the built-in value. +- Future GPUI Component translations remain available without being copied into the application. + +For example, defining only `gpui_component.Calendar.month.January.en` changes January's English label while all other English calendar labels still come from GPUI Component. + +## Select a locale + +Use rust-i18n to change the active locale: + +```rust +rust_i18n::set_locale("fr"); +``` + +Components will then resolve their translated labels using the selected locale and the priority described above. diff --git a/docs/zh-CN/docs/i18n.md b/docs/zh-CN/docs/i18n.md new file mode 100644 index 0000000000..aba709fa3d --- /dev/null +++ b/docs/zh-CN/docs/i18n.md @@ -0,0 +1,92 @@ +--- +title: 自定义国际化 +description: 在应用中新增或覆盖 GPUI Component 的翻译。 +order: -3 +--- + +# 自定义国际化 + +GPUI Component 为组件提供了内置翻译。应用可以新增语言或覆盖个别翻译,而不需要复制完整的内置 locale 文件。 + +此功能需要 `rust-i18n` 4.2 或更高版本。 + +## 添加依赖 + +在持有 locale 文件的应用 crate 中添加 `rust-i18n`: + +```toml +[dependencies] +gpui-component = "0.5" +rust-i18n = "4.2" +``` + +## 创建应用 locale 文件 + +在应用 crate 中创建 `locales/ui.yml`,并将组件翻译放在 `gpui_component` namespace 下: + +```yaml +_version: 2 +gpui_component: + Calendar: + week.0: + fr: Di + month.January: + fr: Janvier + DatePicker: + placeholder: + fr: Sélectionner une date +``` + +namespace 必须是 `gpui_component`,它对应 Rust crate 名称 `gpui-component`,其中连字符转换为下划线。可在 [GPUI Component 内置 locale 文件](https://github.com/longbridge/gpui-component/blob/main/crates/ui/locales/ui.yml)中查看可用的翻译 key。 + +## 注册扩展 + +首先在应用的 crate root 初始化 locales: + +```rust +rust_i18n::i18n!("locales", fallback = "en"); +``` + +然后在初始化 GPUI Component 之前注册扩展: + +```rust +app.run(move |cx| { + rust_i18n::extend!(gpui_component); + gpui_component::init(cx); + + // 打开窗口并初始化应用的其他部分。 +}); +``` + +应用启动期间只需调用一次 `extend!`。 + +## 查找优先级 + +应用提供的翻译优先于组件的内置翻译: + +```text +应用 locales(locales/ui.yml) + │ + │ 未找到 key + ▼ +GPUI Component 内置 locales +``` + +这种 deep merge 行为意味着: + +- 新语言(例如 `fr`)只需提供应用需要的 key。 +- locale 和 key 都相同时,应用提供的翻译会覆盖内置值。 +- 应用未提供的 key 会继续使用组件内置值。 +- GPUI Component 后续新增的翻译无需复制,也会自动可用。 + +例如,只定义 `gpui_component.Calendar.month.January.en` 会修改一月份的英文标签,其他英文日历标签仍然来自 GPUI Component。 + +## 切换语言 + +使用 rust-i18n 修改当前 locale: + +```rust +rust_i18n::set_locale("fr"); +``` + +组件随后会按照上述优先级,使用当前 locale 查找翻译文本。