i18n: shipped components read their copy from the catalog
CI / platforms (windows-latest) (push) Failing after 16s
CI / site (push) Successful in 25s
CI / crate (push) Failing after 10m59s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

Select's default placeholder, the clear, close, remove and unpin tooltips,
the completion menu's empty line and the size sample showed English under
zh-CN. A source test now fails on any fixed English literal in them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-14 19:53:00 -04:00
1 parent 1b5fb07cda
commit d05b1b78ca
11 files changed
+86 -15

No files matched your search

+2 -1
View File
@@ -5,6 +5,7 @@ use gpui::{
div, prelude::*,
};
use crate::i18n;
use crate::{
buttons::{Button, ButtonVariant, IconButton, ToggleButton, ToggleItem},
forms::{Input, TextInput},
@@ -295,7 +296,7 @@ impl RenderOnce for FindWidget {
IconButton::new((self.id.clone(), "close"), IconName::X)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip("Close")
.tooltip(i18n::text(cx, "dialog.close", &[]))
.on_click(move |_, window, cx| close(window, cx))
})),
)
+2 -1
View File
@@ -7,6 +7,7 @@ use gpui::{
};
use super::{symbols::SymbolKind, syntax};
use crate::i18n;
use crate::{
buttons::{ButtonVariant, IconButton},
navigation::{fuzzy, marked},
@@ -185,7 +186,7 @@ impl RenderOnce for CompletionMenu {
.px_2()
.py_1()
.text_color(colors.fg_subtle)
.child("No suggestions"),
.child(i18n::text(cx, "completion.empty", &[])),
)
})
.children(rows),
+2 -1
View File
@@ -4,6 +4,7 @@ use gpui::{
};
use super::TextInput;
use crate::i18n;
use crate::{
buttons::IconButton,
primitives::{Icon, IconName},
@@ -148,7 +149,7 @@ impl RenderOnce for Input {
frame.child(
IconButton::new(("input-clear", clear_id), IconName::CircleX)
.size(ControlSize::Sm)
.tooltip("Clear")
.tooltip(i18n::text(cx, "input.clear", &[]))
.on_click(move |_, window, cx| {
state.update(cx, |input, cx| input.set_text("", cx));
window.focus(&state.read(cx).focus().clone(), cx);
+7 -5
View File
@@ -251,7 +251,7 @@ pub struct Select {
id: ElementId,
choices: Vec<Choice>,
selected: Option<SharedString>,
placeholder: SharedString,
placeholder: Option<SharedString>,
label: Option<SharedString>,
size: ControlSize,
disabled: bool,
@@ -264,7 +264,7 @@ impl Select {
id: id.into(),
choices: choices.into_iter().collect(),
selected: None,
placeholder: SharedString::from("Choose…"),
placeholder: None,
label: None,
size: ControlSize::default(),
disabled: false,
@@ -284,7 +284,7 @@ impl Select {
}
pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
self.placeholder = text.into();
self.placeholder = Some(text.into());
self
}
@@ -326,6 +326,8 @@ impl RenderOnce for Select {
.as_ref()
.and_then(|value| choices.iter().find(|choice| choice.value == *value))
.cloned();
let placeholder =
(self.placeholder.clone()).unwrap_or_else(|| i18n::text(cx, "select.placeholder", &[]));
let colors = &cx.theme().colors;
let trigger = field_button(
self.id.clone(),
@@ -336,7 +338,7 @@ impl RenderOnce for Select {
cx,
)
.role(Role::ComboBox)
.aria_placeholder(self.placeholder.clone())
.aria_placeholder(placeholder.clone())
.when_some(self.label.clone(), |trigger, label| {
trigger.aria_label(label)
})
@@ -361,7 +363,7 @@ impl RenderOnce for Select {
)
.child(field_text(
shown.map(|choice| choice.label),
self.placeholder,
placeholder,
self.disabled,
cx,
))
+20
View File
@@ -115,6 +115,16 @@ const EN: &[(&str, &str)] = &[
("theme.light", "Light"),
("theme.dark", "Dark"),
("theme.system", "System"),
("select.placeholder", "Choose…"),
("input.clear", "Clear"),
("completion.empty", "No suggestions"),
("builder.remove", "Remove"),
("tabs.unpin", "Unpin"),
("theme.size.reset", "Back to the default size"),
(
"theme.size.sample",
"The quick brown fox jumps over the lazy dog.",
),
];
const ZH_CN: &[(&str, &str)] = &[
@@ -228,6 +238,16 @@ const ZH_CN: &[(&str, &str)] = &[
("theme.light", "浅色"),
("theme.dark", "深色"),
("theme.system", "跟随系统"),
("select.placeholder", "请选择…"),
("input.clear", "清除"),
("completion.empty", "没有建议"),
("builder.remove", "移除"),
("tabs.unpin", "取消固定"),
("theme.size.reset", "恢复默认大小"),
(
"theme.size.sample",
"天地玄黄,宇宙洪荒。The quick brown fox.",
),
];
/// Ely's message for `key`; English if unshipped, fails if unknown.
+2 -1
View File
@@ -2,8 +2,9 @@ use jiff::{Timestamp, tz::TimeZone};
use super::{Direction, I18n, LOCALES, Locale, fill};
#[cfg(feature = "test-support")]
mod a11y;
#[cfg(feature = "test-support")]
mod shipped;
fn i18n() -> I18n {
I18n::new("en-US")
+44
View File
@@ -0,0 +1,44 @@
//! Components Ely ships say nothing in fixed English.
/// Sources of components the Ely apps draw.
const SHIPPED: &[(&str, &str)] = &[
("editor/find.rs", include_str!("../../editor/find.rs")),
("editor/hints.rs", include_str!("../../editor/hints.rs")),
("forms/input.rs", include_str!("../../forms/input.rs")),
("forms/select.rs", include_str!("../../forms/select.rs")),
(
"tables/builders.rs",
include_str!("../../tables/builders.rs"),
),
(
"navigation/editor_tabs.rs",
include_str!("../../navigation/editor_tabs.rs"),
),
(
"settings/appearance.rs",
include_str!("../../settings/appearance.rs"),
),
];
#[test]
fn shipped_components_show_no_fixed_english() {
let shown = [".tooltip(\"", ".child(\"", "SharedString::from(\""];
let mut found = Vec::new();
for (path, source) in SHIPPED {
let code = source.split("#[cfg(test)]").next().unwrap_or(source);
for (no, line) in code.lines().enumerate() {
for call in shown {
let Some(at) = line.find(call) else { continue };
let rest = &line[at + call.len()..];
if rest.starts_with(|ch: char| ch.is_ascii_alphabetic()) {
found.push(format!("{path}:{}: {}", no + 1, line.trim()));
}
}
}
}
assert!(
found.is_empty(),
"English outside the catalog:\n{}",
found.join("\n")
);
}
+1 -1
View File
@@ -195,7 +195,7 @@ impl RenderOnce for EditorTabs {
IconButton::new(("pin", ix), IconName::Pin)
.size(ControlSize::Sm)
.variant(ButtonVariant::Ghost)
.tooltip("Unpin")
.tooltip(i18n::text(cx, "tabs.unpin", &[]))
.on_click(move |_, window, cx| {
cx.stop_propagation();
run(&pin, "unpin", &slot_id, window, cx);
+2 -2
View File
@@ -223,7 +223,7 @@ impl RenderOnce for FontSizeControl {
)
.child(
IconButton::new((self.id, "reset"), IconName::RotateCcw)
.tooltip("Back to the default size")
.tooltip(i18n::text(cx, "theme.size.reset", &[]))
.disabled(self.size == default)
.on_click(move |_, window, cx| {
reset(default, window, cx);
@@ -237,7 +237,7 @@ impl RenderOnce for FontSizeControl {
.min_w_0()
.text_size(Pixels::from(self.size))
.text_color(theme.colors.fg)
.child("The quick brown fox jumps over the lazy dog."),
.child(i18n::text(cx, "theme.size.sample", &[])),
),
)
}
+3 -2
View File
@@ -6,6 +6,7 @@ use gpui::{
};
use super::{FilterRule, SortKey, Test};
use crate::i18n;
use crate::{
buttons::{Button, ButtonVariant, IconButton, SegmentedControl},
forms::{Choice, Input, InputEvent, Select, TextInput},
@@ -205,7 +206,7 @@ impl RenderOnce for FilterBuilder {
IconButton::new((id.clone(), format!("remove-{ix}")), IconName::X)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip("Remove")
.tooltip(i18n::text(cx, "builder.remove", &[]))
.on_click(move |_, window, cx| {
let mut next = for_remove.clone();
next.remove(ix);
@@ -377,7 +378,7 @@ impl RenderOnce for SortBuilder {
IconButton::new((id.clone(), format!("sort-remove-{ix}")), IconName::X)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip("Remove")
.tooltip(i18n::text(cx, "builder.remove", &[]))
.on_click(move |_, window, cx| {
let mut next = for_remove.clone();
next.remove(ix);