i18n: a locale per app, catalogs of messages, mirrored stacks

i18n::I18n is a gpui global holding the shown locale and one catalog of
messages per locale. text fills each {name} from its arguments; a
missing key, argument or catalog fails, and every catalog holds the
first one's keys. Locale writes numbers with its separators and dates
in CLDR's short numeric form; Hebrew replaces Arabic, whose Egyptian
digits Ely does not write. Direction::row mirrors a stack and
Direction::align sets text against the reading side. The gallery's
chapter 40 shows the messages in seven locales, every locale's number
and date, and a file list that mirrors under Hebrew.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-27 18:59:45 -04:00
1 parent 17e3e19fc9
commit ae5f2334f5
9 files changed
+616 -4

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
use gpui::Styled;
use jiff::{Timestamp, tz::TimeZone};
use crate::typography::format::{self, Separators};
/// Which way a line of text runs.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Ltr,
Rtl,
}
impl Direction {
/// A flex row that starts at the reading side.
pub fn row<E: Styled>(self, element: E) -> E {
match self {
Direction::Ltr => element.flex().flex_row(),
Direction::Rtl => element.flex().flex_row_reverse(),
}
}
/// Text set against the reading side.
pub fn align<E: Styled>(self, element: E) -> E {
match self {
Direction::Ltr => element.text_left(),
Direction::Rtl => element.text_right(),
}
}
}
/// A language as it writes numbers, dates and lines.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Locale {
pub tag: &'static str,
pub name: &'static str,
pub separators: Separators,
/// A numeric date, as `strftime` reads it.
pub date: &'static str,
pub direction: Direction,
}
const fn locale(
tag: &'static str,
name: &'static str,
(group, decimal): (char, char),
date: &'static str,
direction: Direction,
) -> Locale {
Locale {
tag,
name,
separators: Separators { group, decimal },
date,
direction,
}
}
/// The locales Ely formats for, with CLDR's short numeric dates.
pub const LOCALES: [Locale; 7] = [
locale(
"en-US",
"English (US)",
(',', '.'),
"%-m/%-d/%Y",
Direction::Ltr,
),
locale(
"en-GB",
"English (UK)",
(',', '.'),
"%d/%m/%Y",
Direction::Ltr,
),
locale("de-DE", "Deutsch", ('.', ','), "%d.%m.%Y", Direction::Ltr),
locale(
"fr-FR",
"Français",
('\u{202f}', ','),
"%d/%m/%Y",
Direction::Ltr,
),
locale("ja-JP", "日本語", (',', '.'), "%Y/%m/%d", Direction::Ltr),
locale("zh-CN", "中文", (',', '.'), "%Y/%-m/%-d", Direction::Ltr),
locale("he-IL", "עברית", (',', '.'), "%-d.%-m.%Y", Direction::Rtl),
];
impl Locale {
/// The locale with this tag; fails on one Ely does not list.
pub fn of(tag: &str) -> Locale {
*LOCALES
.iter()
.find(|locale| locale.tag == tag)
.unwrap_or_else(|| panic!("no locale {tag:?}"))
}
/// `value` with this locale's separators.
pub fn number(self, value: f64, decimals: usize) -> String {
format::number(value, decimals, self.separators)
}
/// The day `at` falls on in `zone`, written as this locale writes dates.
pub fn date(self, at: Timestamp, zone: &TimeZone) -> String {
format::datetime(at, zone, self.date)
.unwrap_or_else(|error| panic!("{}'s date pattern: {error}", self.tag))
}
}