i18n: shipped components read their copy from the catalog
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:
1 parent
1b5fb07cda
commit
d05b1b78ca
11 files changed
+86
-15
No files matched your search
+2
-1
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
))
|
||||
|
||||
@@ -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,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")
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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", &[])),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user