Skip to content

Design Internationalization

MeowLynxSea edited this page Jun 18, 2026 · 8 revisions

Internationalization

The i18n framework provides multi-language support, plural forms, number / date / currency formatting, and RTL helpers. Everything lives in crates/yororen-ui-core/src/i18n/ plus three optional locale crates.

crates/yororen-ui-core/src/i18n/
├── format/       → NumberFormatter, DateTimeFormatter, …
├── loader.rs     → parse_translation_value
├── locale.rs     → Locale, TextDirection, SupportedLocale
├── mod.rs        → re-exports the public surface
├── placeholder.rs → PlaceholderResolver
├── runtime.rs    → I18n, Translate, TranslationMap
└── translate.rs  → PluralCategory, TranslatedString

Install a locale

Three locale crates ship — yororen-ui-locale-en, yororen-ui-locale-zh-CN, yororen-ui-locale-ar. Each exposes translation_map() and install(cx).

yororen_ui::locale_en::install(cx);        // English
// or: yororen_ui::locale_zh_cn::install(cx);
// or: yororen_ui::locale_ar::install(cx);   // Arabic (RTL)

Or use the longer form for finer control:

use yororen_ui::i18n::{I18n, Locale};

let mut i18n = I18n::with_locale_fallback(
    Locale::new("zh-CN").unwrap(),
    Locale::new("en").unwrap(),
);
i18n.load_translations(Locale::new("zh-CN").unwrap(), yororen_ui::locale_zh_cn::translation_map());
i18n.load_translations(Locale::new("en").unwrap(),    yororen_ui::locale_en::translation_map());

// Layer app strings on top:
i18n.merge_translations(Locale::new("zh-CN").unwrap(), my_app_zh_cn_map);
cx.set_global(i18n);

cx.t(key) — string lookup

The Translate trait is implemented for gpui::App and Context<'_, T>:

pub trait Translate {
    fn t(&self, key: &str) -> SharedString;
    fn t_with_args(&self, key: &str, args: &[&str]) -> SharedString;
    fn lookup(&self, key: &str) -> Option<SharedString>;
}
let save: SharedString = cx.t("common.save");

Lookup order: current locale → fallback locale (if set) → raw key path with a one-shot warning.

Templates use positional {} slots: "greeting": "Hello {}, you have {} items".

let text = cx.t_with_args("greeting", &["World", "5"]);
// → "Hello World, you have 5 items"

Plural forms

TranslationMap::tn(key, n) selects the language-appropriate PluralCategory for n and looks up key.<category>. Falls back to key.other.

Category Example languages
Zero / One / Two / Few / Many / Other Arabic (six forms)
One / Other English, German, Dutch
Other only Chinese, Japanese, Korean, Vietnamese, Thai
{
  "items": {
    "one":  "{} item",
    "other": "{} items"
  }
}
let cat = PluralCategory::for_number(5, &Locale::new("en").unwrap());  // Other
let cat = PluralCategory::for_number(1, &Locale::new("en").unwrap());  // One

Runtime language switching

cx.i18n().set_locale(Locale::new("zh-CN").unwrap());

let locale    = cx.i18n().locale();
let is_rtl    = cx.i18n().is_rtl();
let direction = cx.i18n().text_direction();

cx.i18n().set_fallback_locale(Some(Locale::new("en").unwrap()));
cx.i18n().set_fallback_locale(None);

Locale::text_direction() returns TextDirection::Ltr or TextDirection::Rtl. RTL languages: ar, he, fa, ur, yi, ps.

Number / date / currency formatting

use yororen_ui::i18n::{NumberFormatter, Locale};

let formatter = NumberFormatter::new(Locale::new("en").unwrap());
formatter.format_decimal(1234.5);                  // "1,234.5"
formatter.format_currency(99.99, "USD");           // "$ 99.99"

let ar = NumberFormatter::new(Locale::new("ar").unwrap());
ar.format_decimal(1234567.89);                     // "١٬٢٣٤٫٥٦٧٫٨٩"

Date formatting:

Locale format_date(2024-01-01)
en 2024-01-01
zh / ja 2024年01月01日
de 01.01.2024
fr 01/01/2024

RTL support

crates/yororen-ui-core/src/rtl.rs flips common "start / end" concepts for RTL locales:

use yororen_ui::rtl;

let direction = cx.i18n().text_direction();
let align     = rtl::text_align_start(direction);   // LTR: Left,  RTL: Right
let flex      = rtl::flex_row(direction);            // LTR: Row,    RTL: RowReverse

rtl::padding_start(&mut style, direction, px(8.));
rtl::padding_end(&mut style,   direction, px(8.));
rtl::corner_radius_start(&mut style, direction, px(4.));

If your custom layout uses .left_* / .right_* directly instead of these helpers (or gpui's built-in .start_* / .end_*), it will render wrong in RTL locales.

Best practices

  1. Always provide explicit placeholders on components that default to one. The framework's bundled strings may not match your intent.
  2. Use positional {} placeholders, not {name}-style. The runtime parser is non-greedy and ignores anything between { and }, treating each {} as the next positional slot.
  3. Always pair a non-en locale with an en fallback so a partial catalog doesn't leak raw common.cancel keys into the UI.
  4. Use the rtl::* helpers for any direction-sensitive style. Never call .left_* / .right_* directly on a custom div.
  5. Layer app strings on top of framework strings via I18n::merge_translations(locale, app_map) so the framework's common.save continues to resolve.

See also

  • Layout rules — RTL-aware flex / padding / radius.
  • Theming — themes are JSON; locales are JSON.

Clone this wiki locally