mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
i18n: the History panel translates the step names that contain a style's name
The History panel and Edit › Undo and Redo did not translate the steps
that apply or redefine a character or paragraph style, or redefine a
graphic style. The engine built their names with format! ("Apply Body",
"Redefine Body", "Redefine Graphic Style “S”"), and the panel looks a
name up as a whole, so no catalog row matched a name with a style's
name in it. A name cannot be matched against template rows instead: a
character style named Brush gives "Apply Brush", which is also the name
of the brush step.
The engine now names these steps with Session::edit_named, and each
such step keeps its template and the style's name
(HistoryEntry::named) through undo and redo. The History panel and
Edit › Undo and Redo show the template's row with the name in it
(panels::history::entry_name). The name stays as the document holds
it, and the Normal styles' names are shown in the interface language.
Every complete catalog has rows for Apply {name} and Redefine {name},
and Redefine Graphic Style “{name}” had one.
every_undo_label_is_translated also requires a row for each template,
and the only step name it allows to be built with format! is the
Liquify tools' "{} Tool".
Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -380,7 +380,9 @@ fn undo(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let doc = std::mem::replace(&mut st.doc, e.doc);
|
||||
let selection = std::mem::replace(&mut st.selection, Arc::unwrap_or_clone(e.selection));
|
||||
let selection = shared_selection(selection, st.history.redo.last());
|
||||
st.history.redo.push(crate::HistoryEntry::turned(e.label, doc, selection, &st.doc, costs));
|
||||
let mut turned = crate::HistoryEntry::turned(e.label, doc, selection, &st.doc, costs);
|
||||
turned.named = e.named;
|
||||
st.history.redo.push(turned);
|
||||
st.history.trim();
|
||||
st.revision += 1;
|
||||
st.selection.prune(&st.doc);
|
||||
@@ -403,7 +405,9 @@ fn redo(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let doc = std::mem::replace(&mut st.doc, e.doc);
|
||||
let selection = std::mem::replace(&mut st.selection, Arc::unwrap_or_clone(e.selection));
|
||||
let selection = shared_selection(selection, st.history.undo.last());
|
||||
st.history.undo.push(crate::HistoryEntry::turned(e.label, doc, selection, &st.doc, costs));
|
||||
let mut turned = crate::HistoryEntry::turned(e.label, doc, selection, &st.doc, costs);
|
||||
turned.named = e.named;
|
||||
st.history.undo.push(turned);
|
||||
st.history.trim();
|
||||
st.revision += 1;
|
||||
st.selection.prune(&st.doc);
|
||||
|
||||
@@ -456,7 +456,7 @@ fn style_redefine(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let look = GraphicStyle::of(String::new(), captured(n), n);
|
||||
let name = d.graphic_styles[i].name.clone();
|
||||
let chars = s.prefs.override_char_color;
|
||||
s.edit(&format!("Redefine Graphic Style \u{201c}{name}\u{201d}"), |d, _| {
|
||||
s.edit_named("Redefine Graphic Style “{name}”", &name, false, |d, _| {
|
||||
let id = d.graphic_style_id(i);
|
||||
let old = std::mem::replace(&mut d.graphic_styles[i], GraphicStyle { name: name.clone(), id, ..look });
|
||||
// Objects still linked follow the new definition; edited ones are unlinked (they keep
|
||||
|
||||
@@ -442,7 +442,7 @@ fn new(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
return Err(bad(&c, format!("a style named `{name}` already exists")));
|
||||
}
|
||||
let n2 = name.clone();
|
||||
s.edit(&format!("New {base}"), |d, _| {
|
||||
s.edit(if kind == Kind::Char { "New Character Style" } else { "New Paragraph Style" }, |d, _| {
|
||||
kind.defs_mut(d).push(TextStyleDef { name: n2, attrs });
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -478,7 +478,7 @@ fn apply(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
Ok(st)
|
||||
};
|
||||
let count = ids.len();
|
||||
s.edit(&format!("Apply {name}"), |d, _| {
|
||||
s.edit_named("Apply {name}", &name, name == kind.normal(), |d, _| {
|
||||
for id in &ids {
|
||||
let Some(t) = text_mut(d, *id) else { continue };
|
||||
match kind {
|
||||
@@ -528,7 +528,7 @@ fn redefine_to(s: &mut Session, kind: Kind, name: &str, new_attrs: Map<String, V
|
||||
let d = &s.doc()?.doc;
|
||||
let old = kind.attrs(d, name).ok_or_else(|| bad(&kind.cmd(verb), format!("no style `{name}`")))?;
|
||||
let name = name.to_string();
|
||||
s.edit(&format!("Redefine {name}"), |d, _| {
|
||||
s.edit_named("Redefine {name}", &name, name == kind.normal(), |d, _| {
|
||||
match kind.defs_mut(d).iter_mut().find(|s| s.name == name) {
|
||||
Some(def) => def.attrs = new_attrs.clone(),
|
||||
None => kind.defs_mut(d).push(TextStyleDef { name: name.clone(), attrs: new_attrs.clone() }),
|
||||
@@ -664,6 +664,37 @@ mod tests {
|
||||
assert_eq!(list["styles"][1]["uses"], 1);
|
||||
}
|
||||
|
||||
/// Applying and redefining a style record a step named from a template with the style's name
|
||||
/// in it (`HistoryEntry::named`), and undo and redo keep it. The Normal styles' names are
|
||||
/// marked as names VectorCraft gives. A new style's step has a name of its own.
|
||||
#[test]
|
||||
fn style_steps_are_named_from_a_template() {
|
||||
let (mut s, id) = session_with_text("Hello");
|
||||
let last = |s: &Session| {
|
||||
let e = s.doc().unwrap().history.undo.last().unwrap();
|
||||
(e.label.clone(), e.named().map(|n| (n.template, n.name.clone(), n.built_in)))
|
||||
};
|
||||
let named = |template, name: &str, built_in| Some((template, name.to_string(), built_in));
|
||||
s.execute("charStyle.new", &json!({"name": "Body"})).unwrap();
|
||||
assert_eq!(last(&s), ("New Character Style".into(), None));
|
||||
s.execute("charStyle.apply", &json!({"name": "Body", "id": id})).unwrap();
|
||||
assert_eq!(last(&s), ("Apply Body".into(), named("Apply {name}", "Body", false)));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
let redo = s.doc().unwrap().history.redo.last().unwrap().named().cloned();
|
||||
assert_eq!(redo.map(|n| n.label()).as_deref(), Some("Apply Body"));
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
assert_eq!(last(&s), ("Apply Body".into(), named("Apply {name}", "Body", false)));
|
||||
s.execute("charStyle.apply", &json!({"name": NORMAL_CHAR, "id": id})).unwrap();
|
||||
assert_eq!(last(&s), (format!("Apply {NORMAL_CHAR}"), named("Apply {name}", NORMAL_CHAR, true)));
|
||||
s.execute("paraStyle.new", &json!({"name": "Lead"})).unwrap();
|
||||
assert_eq!(last(&s), ("New Paragraph Style".into(), None));
|
||||
s.execute("paraStyle.setAttrs", &json!({"name": "Lead", "attrs": {"direction": "rightToLeft"}})).unwrap();
|
||||
assert_eq!(last(&s), ("Redefine Lead".into(), named("Redefine {name}", "Lead", false)));
|
||||
// A step renamed since it was recorded is no longer named from the template.
|
||||
s.doc_mut().unwrap().history.undo.last_mut().unwrap().label = "Typing".into();
|
||||
assert_eq!(last(&s), ("Typing".into(), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_from_selection_rename_delete_and_undo() {
|
||||
let (mut s, id) = session_with_text("Title");
|
||||
|
||||
@@ -91,6 +91,27 @@ pub struct HistoryEntry {
|
||||
next_bytes: usize,
|
||||
/// Which document the figures were taken against (its address): the next state.
|
||||
next: usize,
|
||||
/// The template and name `label` was made from ([`Session::edit_named`]).
|
||||
named: Option<NamedStep>,
|
||||
}
|
||||
|
||||
/// A step name made by filling the `{name}` placeholder of a template with a name
|
||||
/// ([`Session::edit_named`]).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NamedStep {
|
||||
/// The English template, with one `{name}` placeholder (`Apply {name}`).
|
||||
pub template: &'static str,
|
||||
/// The name in the placeholder, as the document holds it (a style's name).
|
||||
pub name: String,
|
||||
/// The name is one VectorCraft gives (`[Normal Character Style]`), not one the user typed.
|
||||
pub built_in: bool,
|
||||
}
|
||||
|
||||
impl NamedStep {
|
||||
/// The step name: the template with the name in its placeholder.
|
||||
pub fn label(&self) -> String {
|
||||
self.template.replace("{name}", &self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl HistoryEntry {
|
||||
@@ -98,7 +119,14 @@ impl HistoryEntry {
|
||||
pub(crate) fn kept(label: String, doc: Arc<Document>, selection: Arc<Selection>, next: &Document) -> Self {
|
||||
doc.drop_lookup_cache();
|
||||
let (bytes, next_bytes) = doc.retained_bytes_each(next);
|
||||
HistoryEntry { label, doc, selection, bytes, next_bytes, next: std::ptr::from_ref(next) as usize }
|
||||
HistoryEntry { label, doc, selection, bytes, next_bytes, next: std::ptr::from_ref(next) as usize, named: None }
|
||||
}
|
||||
|
||||
/// The template and name the step's label was made from, while the label is still the one
|
||||
/// they make: `None` for a step named any other way, and for one whose label was changed
|
||||
/// since it was recorded.
|
||||
pub fn named(&self) -> Option<&NamedStep> {
|
||||
self.named.as_ref().filter(|n| n.label() == self.label)
|
||||
}
|
||||
|
||||
/// The step that undoing (redoing) the step `costs` came from makes: it keeps `doc`, the
|
||||
@@ -112,7 +140,7 @@ impl HistoryEntry {
|
||||
return Self::kept(label, doc, selection, now);
|
||||
}
|
||||
doc.drop_lookup_cache();
|
||||
HistoryEntry { label, doc, selection, bytes: costs.next_bytes, next_bytes: costs.bytes, next: Arc::as_ptr(now) as usize }
|
||||
HistoryEntry { label, doc, selection, bytes: costs.next_bytes, next_bytes: costs.bytes, next: Arc::as_ptr(now) as usize, named: None }
|
||||
}
|
||||
|
||||
/// The figures [`HistoryEntry::turned`] reuses.
|
||||
@@ -1739,6 +1767,33 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Session::edit`] with the step named `template`, its `{name}` placeholder filled with `name`
|
||||
/// (a style's name). The undo step it records keeps the template and the name
|
||||
/// ([`HistoryEntry::named`]), and a frontend shows the template's translation with the name in
|
||||
/// it. An edit that records no step of its own (inside an interaction, or a later edit of an
|
||||
/// undo group) keeps neither, and neither does a step an undo group names.
|
||||
pub fn edit_named<T>(
|
||||
&mut self,
|
||||
template: &'static str,
|
||||
name: &str,
|
||||
built_in: bool,
|
||||
f: impl FnOnce(&mut Document, &mut Selection) -> Result<T>,
|
||||
) -> Result<T> {
|
||||
let named = NamedStep { template, name: name.to_string(), built_in };
|
||||
let step = named.label();
|
||||
// The step this edit records keeps the document as it is now.
|
||||
let now = Arc::as_ptr(&self.doc()?.doc);
|
||||
let v = self.edit(&step, f)?;
|
||||
if let Ok(st) = self.doc_mut()
|
||||
&& let Some(e) = st.history.undo.last_mut()
|
||||
&& Arc::as_ptr(&e.doc) == now
|
||||
&& e.label == step
|
||||
{
|
||||
e.named = Some(named);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Change only the selection (not an undo step).
|
||||
pub fn select(&mut self, f: impl FnOnce(&Document, &mut Selection)) -> Result<()> {
|
||||
self.active_appearance_item = None;
|
||||
|
||||
@@ -175,6 +175,10 @@ fn redefine_updates_every_linked_object() {
|
||||
let blue = Paint::solid(Color::from_hex("#0000ff").unwrap());
|
||||
assert_eq!((fill(&s, b), fill(&s, c)), (blue.clone(), blue));
|
||||
assert_eq!(linked(&mut s, "S"), vec![a.0, b.0, c.0]);
|
||||
// The step is named from a template with the style's name in it.
|
||||
let step = s.doc().unwrap().history.undo.last().unwrap().clone();
|
||||
let named = step.named().map(|n| (n.template, n.name.as_str(), n.built_in));
|
||||
assert_eq!((step.label.as_str(), named), ("Redefine Graphic Style “S”", Some(("Redefine Graphic Style “{name}”", "S", false))));
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
assert_eq!(fill(&s, b), Paint::solid(Color::WHITE));
|
||||
}
|
||||
|
||||
@@ -2398,6 +2398,8 @@
|
||||
Redefine Character Style Предефиниране на стил на знаци
|
||||
Redefine Graphic Style Предефиниране на графичен стил
|
||||
Redefine Graphic Style “{name}” Предефиниране на графичен стил „{name}“
|
||||
Apply {name} Прилагане на стил „{name}“
|
||||
Redefine {name} Предефиниране на стил „{name}“
|
||||
Redefine Paragraph Style Предефиниране на стил на абзац
|
||||
Redefine Symbol Предефиниране на символ
|
||||
Redefine {kind} Style Предефиниране на стил {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1328,6 +1328,8 @@ lighting Light Světlo
|
||||
Opacity: {value} Krytí: {value}
|
||||
Rectangle: Sunshine Obdélník: Sluneční svit
|
||||
Redefine Graphic Style “{name}” Předefinovat grafický styl „{name}“
|
||||
Apply {name} Použít styl „{name}“
|
||||
Redefine {name} Předefinovat styl „{name}“
|
||||
Show Thumbnail Zobrazit miniaturu
|
||||
Delete Artboards Odstranit pracovní plochy
|
||||
Delete Empty Artboards Odstranit prázdné pracovní plochy
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1249,6 +1249,8 @@ lighting Light Lys
|
||||
Opacity: {value} Opacitet: {value}
|
||||
Rectangle: Sunshine Rektangel: Sunshine
|
||||
Redefine Graphic Style “{name}” Omdefinér grafisk typografi »{name}«
|
||||
Apply {name} Anvend stilen »{name}«
|
||||
Redefine {name} Omdefinér stilen »{name}«
|
||||
Show Thumbnail Vis miniatur
|
||||
Artboard Options: edit with the Artboard tool Tegnebrætindstillinger: redigér med tegnebrætværktøjet
|
||||
Delete Artboards Slet tegnebrætter
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2423,6 +2423,8 @@ mark-style Japanese Japanisch
|
||||
Redefine Character Style Zeichenformat neu definieren
|
||||
Redefine Graphic Style Grafikstil neu definieren
|
||||
Redefine Graphic Style “{name}” Grafikstil „{name}“ neu definieren
|
||||
Apply {name} Format „{name}“ anwenden
|
||||
Redefine {name} Format „{name}“ neu definieren
|
||||
Redefine Paragraph Style Absatzformat neu definieren
|
||||
Redefine Symbol Symbol neu definieren
|
||||
Redefine {kind} Style {kind}format neu definieren
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2968,6 +2968,8 @@ theme Appearance Εμφάνιση
|
||||
Redefine Character Style Επαναπροσδιορισμός στυλ χαρακτήρων
|
||||
Redefine Graphic Style Επαναπροσδιορισμός στυλ γραφικών
|
||||
Redefine Graphic Style “{name}” Επαναπροσδιορισμός στυλ γραφικών «{name}»
|
||||
Apply {name} Εφαρμογή στυλ «{name}»
|
||||
Redefine {name} Επαναπροσδιορισμός στυλ «{name}»
|
||||
Redefine Paragraph Style Επαναπροσδιορισμός στυλ παραγράφου
|
||||
Redefine Symbol Επαναπροσδιορισμός συμβόλου
|
||||
Redefine {kind} Style Επαναπροσδιορισμός στυλ {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2005 and column 19.
|
@@ -2444,6 +2444,8 @@
|
||||
Redefine Character Style Redefinir estilo de carácter
|
||||
Redefine Graphic Style Redefinir estilo gráfico
|
||||
Redefine Graphic Style “{name}” Redefinir estilo gráfico “{name}”
|
||||
Apply {name} Aplicar estilo “{name}”
|
||||
Redefine {name} Redefinir estilo “{name}”
|
||||
Redefine Paragraph Style Redefinir estilo de párrafo
|
||||
Redefine Symbol Redefinir símbolo
|
||||
Redefine {kind} Style Redefinir estilo de {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1302,6 +1302,8 @@ lighting Light Valo
|
||||
Opacity: {value} Peittävyys: {value}
|
||||
Rectangle: Sunshine Suorakulmio: Auringonpaiste
|
||||
Redefine Graphic Style “{name}” Määritä grafiikkatyyli ”{name}” uudelleen
|
||||
Apply {name} Käytä tyyliä ”{name}”
|
||||
Redefine {name} Määritä tyyli ”{name}” uudelleen
|
||||
Show Thumbnail Näytä pikkukuva
|
||||
Artboard Options: edit with the Artboard tool Piirtoalustan asetukset: muokkaa piirtoalustatyökalulla
|
||||
Delete Artboards Poista piirtoalustat
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2441,6 +2441,8 @@
|
||||
Redefine Character Style Redéfinir le style de caractère
|
||||
Redefine Graphic Style Redéfinir le style graphique
|
||||
Redefine Graphic Style “{name}” Redéfinir le style graphique « {name} »
|
||||
Apply {name} Appliquer le style « {name} »
|
||||
Redefine {name} Redéfinir le style « {name} »
|
||||
Redefine Paragraph Style Redéfinir le style de paragraphe
|
||||
Redefine Symbol Redéfinir le symbole
|
||||
Redefine {kind} Style Redéfinir le style de {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2979,6 +2979,8 @@ mark-style Japanese Japán
|
||||
Redefine Character Style Karakterstílus újradefiniálása
|
||||
Redefine Graphic Style Grafikai stílus újradefiniálása
|
||||
Redefine Graphic Style “{name}” Grafikai stílus újradefiniálása: „{name}”
|
||||
Apply {name} Stílus alkalmazása: „{name}”
|
||||
Redefine {name} Stílus újradefiniálása: „{name}”
|
||||
Redefine Paragraph Style Bekezdésstílus újradefiniálása
|
||||
Redefine Symbol Szimbólum újradefiniálása
|
||||
Redefine {kind} Style Stílus újradefiniálása: {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1249,6 +1249,8 @@ lighting Light Cahaya
|
||||
Opacity: {value} Opasitas: {value}
|
||||
Rectangle: Sunshine Persegi panjang: Sunshine
|
||||
Redefine Graphic Style “{name}” Definisikan ulang gaya grafis “{name}”
|
||||
Apply {name} Terapkan gaya “{name}”
|
||||
Redefine {name} Definisikan ulang gaya “{name}”
|
||||
Show Thumbnail Tampilkan thumbnail
|
||||
Artboard Options: edit with the Artboard tool Opsi artboard: edit dengan alat artboard
|
||||
Delete Artboards Hapus artboard
|
||||
|
||||
|
@@ -2474,6 +2474,8 @@ mark-style Japanese Giapponese
|
||||
Redefine Character Style Ridefinisci stile carattere
|
||||
Redefine Graphic Style Ridefinisci stile grafico
|
||||
Redefine Graphic Style “{name}” Ridefinisci stile grafico “{name}”
|
||||
Apply {name} Applica stile “{name}”
|
||||
Redefine {name} Ridefinisci stile “{name}”
|
||||
Redefine Paragraph Style Ridefinisci stile paragrafo
|
||||
Redefine Symbol Ridefinisci simbolo
|
||||
Redefine {kind} Style Ridefinisci stile {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2456,6 +2456,8 @@ mark-style Japanese 日本式
|
||||
Redefine Character Style 文字スタイルを再定義
|
||||
Redefine Graphic Style グラフィックスタイルを再定義
|
||||
Redefine Graphic Style “{name}” グラフィックスタイル「{name}」を再定義
|
||||
Apply {name} スタイル「{name}」を適用
|
||||
Redefine {name} スタイル「{name}」を再定義
|
||||
Redefine Paragraph Style 段落スタイルを再定義
|
||||
Redefine Symbol シンボルを再定義
|
||||
Redefine {kind} Style {kind}スタイルを再定義
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2450,6 +2450,8 @@ mark-style Japanese 일본식
|
||||
Redefine Character Style 문자 스타일 재정의
|
||||
Redefine Graphic Style 그래픽 스타일 재정의
|
||||
Redefine Graphic Style “{name}” 그래픽 스타일 “{name}” 재정의
|
||||
Apply {name} 스타일 “{name}” 적용
|
||||
Redefine {name} 스타일 “{name}” 재정의
|
||||
Redefine Paragraph Style 단락 스타일 재정의
|
||||
Redefine Symbol 심볼 재정의
|
||||
Redefine {kind} Style {kind} 스타일 재정의
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1263,6 +1263,8 @@ lighting Light Licht
|
||||
Opacity: {value} Dekking: {value}
|
||||
Rectangle: Sunshine Rechthoek: Sunshine
|
||||
Redefine Graphic Style “{name}” Grafische stijl „{name}” opnieuw definiëren
|
||||
Apply {name} Stijl „{name}” toepassen
|
||||
Redefine {name} Stijl „{name}” opnieuw definiëren
|
||||
Show Thumbnail Miniatuur tonen
|
||||
Artboard Options: edit with the Artboard tool Opties voor tekengebied: bewerken met het Tekengebied-gereedschap
|
||||
Delete Artboards Tekengebieden verwijderen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1242,6 +1242,8 @@ lighting Light Światło
|
||||
Opacity: {value} Krycie: {value}
|
||||
Rectangle: Sunshine Prostokąt: Słońce
|
||||
Redefine Graphic Style “{name}” Zdefiniuj ponownie styl graficzny „{name}”
|
||||
Apply {name} Zastosuj styl „{name}”
|
||||
Redefine {name} Zdefiniuj ponownie styl „{name}”
|
||||
Show Thumbnail Pokaż miniaturę
|
||||
Delete Artboards Usuń obszary kompozycji
|
||||
Delete Empty Artboards Usuń puste obszary kompozycji
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2031,6 +2031,8 @@
|
||||
Redefine Character Style Redefinir Estilo de Caractere
|
||||
Redefine Graphic Style Redefinir Estilo Gráfico
|
||||
Redefine Graphic Style “{name}” Redefinir Estilo Gráfico “{name}”
|
||||
Apply {name} Aplicar Estilo “{name}”
|
||||
Redefine {name} Redefinir Estilo “{name}”
|
||||
Redefine Paragraph Style Redefinir Estilo de Parágrafo
|
||||
Redefine Symbol Redefinir Símbolo
|
||||
Redefine {kind} Style Redefinir Estilo de {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2470,6 +2470,8 @@ mark-style Japanese Японский
|
||||
Redefine Character Style Переопределить стиль символа
|
||||
Redefine Graphic Style Переопределить графический стиль
|
||||
Redefine Graphic Style “{name}” Переопределить графический стиль «{name}»
|
||||
Apply {name} Применить стиль «{name}»
|
||||
Redefine {name} Переопределить стиль «{name}»
|
||||
Redefine Paragraph Style Переопределить стиль абзаца
|
||||
Redefine Symbol Переопределить символ
|
||||
Redefine {kind} Style Переопределить стиль {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2398,6 +2398,8 @@
|
||||
Redefine Character Style Definiera om teckenstil
|
||||
Redefine Graphic Style Definiera om grafikstil
|
||||
Redefine Graphic Style “{name}” Definiera om grafikstilen ”{name}”
|
||||
Apply {name} Använd stilen ”{name}”
|
||||
Redefine {name} Definiera om stilen ”{name}”
|
||||
Redefine Paragraph Style Definiera om styckestil
|
||||
Redefine Symbol Definiera om symbol
|
||||
Redefine {kind} Style Definiera om {kind}stil
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -440,18 +440,28 @@ fn every_tl_literal_is_translated() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every undo step name the engine, the tools and the shell record ([`undo_labels`]), and every
|
||||
/// effect's name without its ellipsis, has a row in each language marked `complete_menus`: its plain
|
||||
/// row, or the row of the same name with an ellipsis (`panels::history::step_name`).
|
||||
/// Every undo step name the engine, the tools and the shell record ([`undo_labels`]), every
|
||||
/// effect's name without its ellipsis, and the Liquify tools' step names (`{} Tool`), has a row in
|
||||
/// each language marked `complete_menus`: its plain row, or the row of the same name with an
|
||||
/// ellipsis (`panels::history::step_name`). Every template a step name is made from with
|
||||
/// `Session::edit_named` has a plain row in each, which `panels::history::entry_name` fills with the
|
||||
/// name. No other step name is built with `format!`.
|
||||
#[test]
|
||||
fn every_undo_label_is_translated() {
|
||||
let mut labels = undo_labels();
|
||||
let scan = undo_scan();
|
||||
let mut labels = scan.labels;
|
||||
assert!(labels.len() > 300, "scan found only {} undo labels", labels.len());
|
||||
labels.extend(vectorcraft_effects::effect_catalog().iter().map(|e| e.label.trim_end_matches('…').to_string()));
|
||||
let liquify = ["warp", "twirl", "pucker", "bloat", "scallop", "crystallize", "wrinkle"];
|
||||
labels.extend(liquify.iter().filter_map(|k| vectorcraft_tools::distort::liquify::LiquifyKind::parse(k)).map(|k| format!("{} Tool", k.label())));
|
||||
assert_eq!(scan.formatted.iter().collect::<Vec<_>>(), ["{} Tool"], "step names built with format! (make them with Session::edit_named)");
|
||||
assert!(scan.templates.len() >= 3, "scan found only the templates {:?}", scan.templates);
|
||||
for l in LANGUAGES.iter().filter(|l| l.complete_menus) {
|
||||
let lang = Lang::from_code(l.code).expect("registered");
|
||||
let missing: Vec<_> = labels.iter().filter(|s| crate::panels::history::step_name(lang, s).is_none()).collect();
|
||||
assert!(missing.is_empty(), "{}: {} untranslated undo labels: {missing:#?}", l.code, missing.len());
|
||||
let missing: Vec<_> = scan.templates.iter().filter(|t| l.catalog().plain(t).is_none()).collect();
|
||||
assert!(missing.is_empty(), "{}: {} untranslated undo label templates: {missing:#?}", l.code, missing.len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +504,9 @@ const UNDO_CRATES: &[&str] = &["engine", "tools", "ui-egui"];
|
||||
/// Calls whose first argument names an undo step.
|
||||
const UNDO_MARKERS: &[&str] = &[".edit(", "begin_interaction(", "Action::Begin(", "begin_named_undo_group(Some("];
|
||||
|
||||
/// The call whose first argument is the template of an undo step's name (`Session::edit_named`).
|
||||
const NAMED_MARKER: &str = ".edit_named(";
|
||||
|
||||
/// A function in a source file, as [`undo_labels`] reads it.
|
||||
struct SourceFn<'a> {
|
||||
file: usize,
|
||||
@@ -513,8 +526,26 @@ struct SourceFn<'a> {
|
||||
/// `…label` (`pf_label(op)`, `kind.label()`) gives the literals that function returns in the same
|
||||
/// file. A name built with `format!` or taken from data (an effect's, a plug-in's) is not read.
|
||||
pub fn undo_labels() -> std::collections::BTreeSet<String> {
|
||||
undo_scan().labels
|
||||
}
|
||||
|
||||
/// What [`undo_scan`] finds in [`UNDO_CRATES`].
|
||||
struct UndoScan {
|
||||
/// The step names written as literals ([`undo_labels`]).
|
||||
labels: std::collections::BTreeSet<String>,
|
||||
/// The templates step names are made from with `Session::edit_named` (its first argument).
|
||||
templates: std::collections::BTreeSet<String>,
|
||||
/// The format strings of the step names built with `format!` where [`undo_labels`] reads a
|
||||
/// name.
|
||||
formatted: std::collections::BTreeSet<String>,
|
||||
}
|
||||
|
||||
/// The undo step names and templates in [`UNDO_CRATES`] (see [`undo_labels`]).
|
||||
fn undo_scan() -> UndoScan {
|
||||
let crates = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
|
||||
let mut out = std::collections::BTreeSet::new();
|
||||
let mut templates = std::collections::BTreeSet::new();
|
||||
let mut formatted = std::collections::BTreeSet::new();
|
||||
for krate in UNDO_CRATES {
|
||||
let files = undo_sources(&crates.join(krate).join("src"));
|
||||
let fns: Vec<SourceFn> = files.iter().enumerate().flat_map(|(i, t)| source_fns(i, t)).collect();
|
||||
@@ -551,10 +582,14 @@ pub fn undo_labels() -> std::collections::BTreeSet<String> {
|
||||
}
|
||||
}
|
||||
for (file, e) in exprs {
|
||||
label_literals(e, file, &fns, &mut out);
|
||||
label_literals(e, file, &fns, &mut out, &mut formatted);
|
||||
}
|
||||
for t in &files {
|
||||
let named = t.match_indices(NAMED_MARKER).filter_map(|(at, _)| arguments(&t[at + NAMED_MARKER.len()..]).first().copied());
|
||||
templates.extend(named.filter_map(|a| a.strip_prefix('"').and_then(string_literal)));
|
||||
}
|
||||
}
|
||||
out
|
||||
UndoScan { labels: out, templates, formatted }
|
||||
}
|
||||
|
||||
/// The sources of a crate's `src` directory, test modules and files and `//` comments aside.
|
||||
@@ -740,10 +775,18 @@ fn bare(e: &str) -> &str {
|
||||
e
|
||||
}
|
||||
|
||||
/// Add the step names expression `e` in source file `file` gives (see [`undo_labels`]).
|
||||
fn label_literals(e: &str, file: usize, fns: &[SourceFn], out: &mut std::collections::BTreeSet<String>) {
|
||||
/// Add the step names expression `e` in source file `file` gives (see [`undo_labels`]) to `out`,
|
||||
/// and the format string of a name it builds with `format!` to `formatted`.
|
||||
fn label_literals(
|
||||
e: &str,
|
||||
file: usize,
|
||||
fns: &[SourceFn],
|
||||
out: &mut std::collections::BTreeSet<String>,
|
||||
formatted: &mut std::collections::BTreeSet<String>,
|
||||
) {
|
||||
let mut e = bare(e);
|
||||
if e.starts_with("format!(") {
|
||||
if let Some(args) = e.strip_prefix("format!(") {
|
||||
formatted.extend(args.trim_start().strip_prefix('"').and_then(string_literal));
|
||||
return;
|
||||
}
|
||||
if let Some(body) = e.strip_prefix('|').and_then(|r| r.find('|').map(|n| &r[n + 1..])) {
|
||||
@@ -798,6 +841,7 @@ fn dump_source_strings() {
|
||||
let mut all = crate::menus::menu_strings();
|
||||
all.extend(tl_literals());
|
||||
all.extend(undo_labels());
|
||||
all.extend(undo_scan().templates);
|
||||
let text: String = all.iter().map(|s| format!("{}\n", s.replace('\\', "\\\\").replace('\n', "\\n").replace('\t', "\\t"))).collect();
|
||||
std::fs::write(path, text).expect("write dump");
|
||||
}
|
||||
|
||||
@@ -2466,6 +2466,8 @@ mark-style Japanese Японський
|
||||
Redefine Character Style Перевизначити стиль символу
|
||||
Redefine Graphic Style Перевизначити графічний стиль
|
||||
Redefine Graphic Style “{name}” Перевизначити графічний стиль «{name}»
|
||||
Apply {name} Застосувати стиль «{name}»
|
||||
Redefine {name} Перевизначити стиль «{name}»
|
||||
Redefine Paragraph Style Перевизначити стиль абзацу
|
||||
Redefine Symbol Перевизначити символ
|
||||
Redefine {kind} Style Перевизначити стиль {kind}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 725 and column 40.
|
@@ -2756,6 +2756,8 @@ mark-style Japanese Kiểu Nhật
|
||||
Redefine Character Style Xác định lại kiểu ký tự
|
||||
Redefine Graphic Style Xác định lại kiểu đồ họa
|
||||
Redefine Graphic Style “{name}” Xác định lại kiểu đồ họa “{name}”
|
||||
Apply {name} Áp dụng kiểu “{name}”
|
||||
Redefine {name} Xác định lại kiểu “{name}”
|
||||
Redefine Paragraph Style Xác định lại kiểu đoạn văn
|
||||
Redefine Symbol Xác định lại ký hiệu
|
||||
Redefine {kind} Style Xác định lại kiểu {kind}
|
||||
|
||||
|
@@ -2362,6 +2362,8 @@ mark-style Japanese 日式
|
||||
Redefine Character Style 重新定义字符样式
|
||||
Redefine Graphic Style 重新定义图形样式
|
||||
Redefine Graphic Style “{name}” 重新定义图形样式“{name}”
|
||||
Apply {name} 应用样式“{name}”
|
||||
Redefine {name} 重新定义样式“{name}”
|
||||
Redefine Paragraph Style 重新定义段落样式
|
||||
Redefine Symbol 重新定义符号
|
||||
Redefine {kind} Style 重新定义{kind}样式
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2448,6 +2448,8 @@ mark-style Japanese 日式
|
||||
Redefine Character Style 重新定義字元樣式
|
||||
Redefine Graphic Style 重新定義繪圖樣式
|
||||
Redefine Graphic Style “{name}” 重新定義繪圖樣式「{name}」
|
||||
Apply {name} 套用樣式「{name}」
|
||||
Redefine {name} 重新定義樣式「{name}」
|
||||
Redefine Paragraph Style 重新定義段落樣式
|
||||
Redefine Symbol 重新定義符號
|
||||
Redefine {kind} Style 重新定義{kind}樣式
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1909,8 +1909,8 @@ pub fn display_label(app: &VectorcraftApp, id: &str, label: &str) -> String {
|
||||
// The step's name as the History panel lists it.
|
||||
let name = crate::panels::history::shown;
|
||||
match last {
|
||||
Some(h) if id == "edit.undo" => fmt(tl!("Undo {name}"), &[("name", &name(&h.label))]),
|
||||
Some(h) => fmt(tl!("Redo {name}"), &[("name", &name(&h.label))]),
|
||||
Some(h) if id == "edit.undo" => fmt(tl!("Undo {name}"), &[("name", &name(h))]),
|
||||
Some(h) => fmt(tl!("Redo {name}"), &[("name", &name(h))]),
|
||||
None => tl!(if id == "edit.undo" { "Undo" } else { "Redo" }).to_string(),
|
||||
}
|
||||
}
|
||||
@@ -4295,6 +4295,22 @@ mod tests {
|
||||
app
|
||||
}
|
||||
|
||||
/// Edit › Undo and Edit › Redo name a step named after a style as the History panel lists it:
|
||||
/// the template's translation with the style's name in it.
|
||||
#[test]
|
||||
fn undo_and_redo_name_a_style_step_around_the_style_name() {
|
||||
let mut app = app_with_a_rectangle();
|
||||
let text = app.run("text.create", json!({"x": 0, "y": 40, "text": "Hello"})).unwrap()["id"].clone();
|
||||
app.run("charStyle.new", json!({"name": "Brush"})).unwrap();
|
||||
app.run("charStyle.apply", json!({"name": "Brush", "id": text})).unwrap();
|
||||
crate::i18n::set_current(crate::i18n::Lang::from_code("ja").expect("ja registered"));
|
||||
assert_eq!(display_label(&app, "edit.undo", "Undo"), "スタイル「Brush」を適用を取り消す");
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(display_label(&app, "edit.redo", "Redo"), "スタイル「Brush」を適用をやり直す");
|
||||
crate::i18n::set_current(crate::i18n::Lang::EN);
|
||||
assert_eq!(display_label(&app, "edit.redo", "Redo"), "Redo Apply Brush");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_selection_asks_for_a_name() {
|
||||
let mut app = app_with_a_rectangle();
|
||||
|
||||
@@ -24,10 +24,23 @@ pub fn step_name(lang: crate::i18n::Lang, label: &str) -> Option<String> {
|
||||
has(lang, &dotted).then(|| tr(lang, &dotted).trim_end_matches('…').to_string())
|
||||
}
|
||||
|
||||
/// A step's name in `lang`. A name made from a template (`Apply {name}`,
|
||||
/// [`vectorcraft_engine::HistoryEntry::named`]) takes the template's plain row with the name in its
|
||||
/// placeholder. A name VectorCraft gives is shown in `lang`, and any other name is shown as the
|
||||
/// document holds it. It returns `None` when the catalog has no row for the template. Any other step
|
||||
/// takes [`step_name`] of its label.
|
||||
pub fn entry_name(lang: crate::i18n::Lang, h: &vectorcraft_engine::HistoryEntry) -> Option<String> {
|
||||
use crate::i18n::{fmt, has, label_or_name, tr};
|
||||
match h.named() {
|
||||
Some(n) => has(lang, n.template).then(|| fmt(tr(lang, n.template), &[("name", label_or_name(lang, &n.name, n.built_in))])),
|
||||
None => step_name(lang, &h.label),
|
||||
}
|
||||
}
|
||||
|
||||
/// A step's name as the panel lists it: in the UI language, else as recorded. Edit › Undo and Redo
|
||||
/// name the step the same way.
|
||||
pub(crate) fn shown(label: &str) -> String {
|
||||
step_name(crate::i18n::current(), label).unwrap_or_else(|| label.to_string())
|
||||
pub(crate) fn shown(h: &vectorcraft_engine::HistoryEntry) -> String {
|
||||
entry_name(crate::i18n::current(), h).unwrap_or_else(|| h.label.clone())
|
||||
}
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
@@ -37,9 +50,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
return;
|
||||
};
|
||||
let mut rows: Vec<(String, bool)> = vec![(tl!("Open").to_string(), false)];
|
||||
rows.extend(st.history.undo.iter().map(|h| (shown(&h.label), false)));
|
||||
rows.extend(st.history.undo.iter().map(|h| (shown(h), false)));
|
||||
let current = rows.len() - 1;
|
||||
rows.extend(st.history.redo.iter().rev().map(|h| (shown(&h.label), true)));
|
||||
rows.extend(st.history.redo.iter().rev().map(|h| (shown(h), true)));
|
||||
let mut steps: Option<i64> = None;
|
||||
crate::widgets::list_box(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
@@ -100,6 +113,61 @@ mod tests {
|
||||
assert_eq!(steps_to(3, 3), 0);
|
||||
}
|
||||
|
||||
/// The step names the panel paints, top to bottom. Without an image loader, each row's icon
|
||||
/// paints a `⚠` instead, which is left out.
|
||||
fn listed(app: &mut VectorcraftApp) -> Vec<String> {
|
||||
let ctx = egui::Context::default();
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
out.shapes
|
||||
.iter()
|
||||
.filter_map(|c| match &c.shape {
|
||||
egui::Shape::Text(t) if t.galley.text() != "⚠" => Some(t.galley.text().to_string()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A step named after a style lists the template's translation with the style's name in it as
|
||||
/// the document holds it, also when the name looks like a template, like a step with a row of
|
||||
/// its own (`Apply Brush`) or like another template's name; the Normal style's name is
|
||||
/// translated as the Character Styles panel shows it. Undone steps keep their names.
|
||||
#[test]
|
||||
fn steps_named_after_a_style_list_the_template_around_the_name() {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
let mut run = |id: &str, p: serde_json::Value| app.session.execute(id, &p).unwrap_or_else(|e| panic!("{id} {p}: {e}"));
|
||||
run("file.new", json!({"width": 200, "height": 150}));
|
||||
let rect = run("shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20}))["id"].clone();
|
||||
run("graphicStyle.new", json!({"name": "S", "id": rect}));
|
||||
run("graphicStyle.redefine", json!({"name": "S", "id": rect}));
|
||||
let text = run("text.create", json!({"x": 10, "y": 100, "text": "Hello"}))["id"].clone();
|
||||
for name in ["Body", "{name}", "Apply", "Brush", "Graphic Style “X”"] {
|
||||
run("charStyle.new", json!({"name": name}));
|
||||
}
|
||||
for name in ["Body", "{name}", "Apply", "Brush", "[Normal Character Style]"] {
|
||||
run("charStyle.apply", json!({"name": name, "id": text}));
|
||||
}
|
||||
run("charStyle.setAttrs", json!({"name": "Graphic Style “X”", "attrs": {"size": 30.0}}));
|
||||
let ja = crate::i18n::Lang::from_code("ja").expect("ja registered");
|
||||
crate::i18n::set_current(ja);
|
||||
let rows = listed(&mut app);
|
||||
let want = [
|
||||
"グラフィックスタイル「S」を再定義",
|
||||
"スタイル「Body」を適用",
|
||||
"スタイル「{name}」を適用",
|
||||
"スタイル「Apply」を適用",
|
||||
"スタイル「Brush」を適用",
|
||||
"スタイル「[標準文字スタイル]」を適用",
|
||||
"スタイル「Graphic Style “X”」を再定義",
|
||||
];
|
||||
assert!(rows.contains(&want[0].to_string()), "{rows:#?}");
|
||||
assert_eq!(rows[rows.len() - 6..], want[1..], "{rows:#?}");
|
||||
app.session.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(listed(&mut app).last().map(String::as_str), Some(want[6]), "an undone step");
|
||||
crate::i18n::set_current(crate::i18n::Lang::EN);
|
||||
assert_eq!(listed(&mut app)[rows.len() - 6..rows.len() - 4], ["Apply Body", "Apply {name}"]);
|
||||
}
|
||||
|
||||
/// A step named after an effect whose catalog row has only the menu label with its ellipsis
|
||||
/// takes that row's translation without the ellipsis; a name with no row has none.
|
||||
#[test]
|
||||
|
||||
@@ -461,6 +461,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Panels undock (#495): a dock tab, a panel icon or a popped-out panel's title dragged out of the dock floats that panel inside the window, and the strip right of the dock's tabs floats the whole tabbed group. A floating group moves by its title bar, a tab dragged out of it floats on its own, and dropped on another group's title bar or tabs it stacks with that group; dropped on the dock, which lights up under the pointer, its panels go back where they live (the tabbed group or the icon column), as its × and the panel menus' Float Panel / Dock Panel do (`window.panel.float`, `window.panel.dock`). The Tools panel floats by its title bar and docks again on the window's left edge. Floating groups stack into sets: dropped on the upper or lower half of another group (a line shows where), a group joins it right above or below it, and the set's groups stack top to bottom, each showing one panel, moving together by the top group's title bar, whose × docks them all; a group dragged by its tab strip leaves the set (`window.panel.float {below, above}`). A double-click on a tab collapses a floating group to its tabs (`collapsed`). Sets dropped on the left edge of the icon column or of another dock column dock as dock columns (a multi-column dock, `window.panel.dock {column}`), whose groups stack, tab and tear out as a floating set's do, and whose top strip floats the column again. Floating panels, their sets and the Tools panel's position are kept with the preferences and in user workspaces, and clamped into a smaller window. Not done: panels in separate OS windows, docking a panel into the default tabbed group it doesn't belong to, reordering tabs by dragging, collapsing a set or dock column to icons, dock columns right of the default dock, resizing floating panels and the groups of a set.
|
||||
- The dock's double arrow collapses Properties, Layers and Libraries to icons at the top of the icon column, popping out like the other icon panels, and expands them again (`window.collapseDock`, kept with the preferences and in saved workspaces). The icon column's own arrow (expanding the icon panels into a full column) is not done.
|
||||
- Czech render-site labels: desktop/web file filters, diagnostics, suggested preset names and contributor tooltips/plurals now use catalog lookups; gradients have Czech adjective agreement and printer marks have a separate context (PDF/Print and Summary). Search, Home, toolbar/panel tooltips and Preferences already look up their labels. Every complete catalog has rows for these labels and for the Roman and Japanese printer mark styles. Evidence: `i18n::tests::czech_render_site_follow_up`, `i18n::tests::complete_catalogs_translate_render_site_labels`, `dialogs::tests_modal::printer_mark_labels_are_distinct_from_language_names`; see [localization-parity.md](localization-parity.md).
|
||||
- The History panel, Edit › Undo and Edit › Redo translate the names of the steps that apply or redefine a character or paragraph style and of the step that redefines a graphic style: each such step keeps its template (`Apply {name}`, `Redefine {name}`, `Redefine Graphic Style “{name}”`) and the style's name, every complete catalog has a row for each template, and the panel and the menu show the style's name as the document holds it, except the Normal styles' names, which they show in the interface language (`steps_named_after_a_style_list_the_template_around_the_name`, `undo_and_redo_name_a_style_step_around_the_style_name`, `every_undo_label_is_translated`).
|
||||
- Localised UI: menus (in-window and native), panels, dialogs, toolbar and Preferences read from per-language catalogs (`crates/ui-egui/src/i18n`, see `docs/development.md`); Traditional Chinese (Taiwan), Simplified Chinese (mainland), Japanese, Korean, Vietnamese, German, Spanish, French, Italian, Polish, Brazilian Portuguese, Russian and Ukrainian ship complete, and Czech covers every menu label (and more), with system-locale detection on macOS, Windows, Linux and the web (the browser's languages, or `?lang=`), a Language preference and, on macOS, a VectorCraft ▸ Language menu. Names are never translated (fonts and their styles, layers, artboards, saved presets, user libraries, custom workspaces, recent files, plug-ins).
|
||||
- Edit › Undo and Edit › Redo, in the in-window and native menus, name the step in the interface language as the History panel lists it (`panels::history::step_name`), so when the catalog has no plain row for a step named after an effect, such as Feather, the menu shows the translation of the effect's menu row without its ellipsis (`undo_and_redo_name_the_step_as_the_history_panel_does`).
|
||||
- The History panel translates the undo step names the engine, the tools and the shell write as literals: each has a row in every complete catalog (`every_undo_label_is_translated`), and a step whose catalog row exists only with an ellipsis (such as an effect's menu name) shows that row without the ellipsis.
|
||||
|
||||
Reference in New Issue
Block a user