i18n: a right-to-left message leads with its mark

gpui sets no paragraph direction, so CoreText took a Hebrew line whose
first strong character was Latin, or the left-to-right mark of a signed
number, as left to right and swapped its parts. I18n::text now leads a
right-to-left message with U+200F; the number keeps CLDR's U+200E, so
its sign stays at the digits' left. The demo adds a change that opens
with a signed number, in every catalog; under Hebrew it lies at the
line's right end. A test holds the marks on a message that starts with
a number and one that starts with a Latin name.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-27 19:36:12 -04:00
1 parent 83ecacde65
commit 451fdcc7c0
4 files changed
+40 -6

No files matched your search

+7 -4
View File
@@ -71,7 +71,7 @@ impl I18n {
cx.refresh_windows();
}
/// The shown locale's message for `key`, each `{name}` filled from `args`; fails on a missing key or argument.
/// The shown locale's message for `key`, each `{name}` filled from `args`; fails on a missing key or argument. gpui sets no paragraph direction, so a right-to-left message leads with a right-to-left mark: CoreText would lay out one that starts with Latin letters or a signed number left to right.
pub fn text(&self, key: &str, args: &[(&str, &str)]) -> SharedString {
let (_, catalog) = self
.catalogs
@@ -81,9 +81,12 @@ impl I18n {
let message = catalog
.get(key)
.unwrap_or_else(|| panic!("no message {key:?} in {}", self.locale.tag));
fill(message, args)
.unwrap_or_else(|error| panic!("{key:?} in {}: {error}", self.locale.tag))
.into()
let text = fill(message, args)
.unwrap_or_else(|error| panic!("{key:?} in {}: {error}", self.locale.tag));
match self.locale.direction {
Direction::Ltr => text.into(),
Direction::Rtl => format!("\u{200f}{text}").into(),
}
}
}
+24 -1
View File
@@ -25,6 +25,26 @@ fn a_message_fills_its_arguments() {
assert_eq!(fill("{b}", &[("a", "x")]), Err("no argument \"b\"".into()));
}
/// A right-to-left message leads with its mark, so one that starts with Latin letters or a signed number still reads right to left.
#[test]
fn a_right_to_left_message_leads_with_its_mark() {
let he = I18n::new("he-IL").catalog(
"he-IL",
&[("change", "{change}% מאז אתמול"), ("sent", "{name} שלח")],
);
let change = Locale::of("he-IL").number(-3.5, 1);
assert_eq!(
he.text("change", &[("change", &change)]),
"\u{200f}\u{200e}−3.5% מאז אתמול"
);
assert_eq!(he.text("sent", &[("name", "Ada")]), "\u{200f}Ada שלח");
assert_eq!(
i18n().text("hi", &[]),
"Hello",
"a left-to-right message takes no mark"
);
}
#[test]
#[should_panic(expected = "\"files\" in en-US: no argument \"folder\"")]
fn a_missing_argument_fails() {
@@ -160,7 +180,10 @@ mod shown {
assert!(first.left() < second.left(), "left to right in en-US");
cx.update(|_, cx| I18n::set_locale("he-IL", cx));
cx.run_until_parked();
assert_eq!(cx.read(|cx| cx.global::<I18n>().text("hi", &[])), "שלום");
assert_eq!(
cx.read(|cx| cx.global::<I18n>().text("hi", &[])),
"\u{200f}שלום"
);
let first = cx.debug_bounds("first").expect("the first mark");
let second = cx.debug_bounds("second").expect("the second mark");
assert!(