Translations report: count repeated menu entries once; list panel strings no catalog has yet (#2252)

* Translations report: count a repeated menu entry once; list panel strings no catalog has yet

* Translations report: scan the dock and dropped-file messages too
This commit is contained in:
aiongg
2026-10-23 19:19:43 -04:00
committed by GitHub
parent ffb147f3ed
commit 049070d65c
2 changed files with 50 additions and 11 deletions
+4 -6
View File
@@ -7435,7 +7435,9 @@ mod tests {
/// whose effect names come from the effect catalog instead. /// whose effect names come from the effect catalog instead.
fn missing_menu_entries(code: &str) -> Vec<String> { fn missing_menu_entries(code: &str) -> Vec<String> {
let catalog = menu_catalog(code).unwrap_or_default(); let catalog = menu_catalog(code).unwrap_or_default();
// A label the tree repeats (one command in two menus) counts once.
let mut missing = Vec::new(); let mut missing = Vec::new();
let mut seen = BTreeSet::new();
for line in TREE.lines() { for line in TREE.lines() {
let line = line.trim().trim_start_matches("[mac] ").trim_start_matches("[!mac] "); let line = line.trim().trim_start_matches("[mac] ").trim_start_matches("[!mac] ");
if line.is_empty() || line == "---" || line.starts_with('@') { if line.is_empty() || line == "---" || line.starts_with('@') {
@@ -7444,7 +7446,7 @@ mod tests {
let mut parts = line.split(" | "); let mut parts = line.split(" | ");
let Some(en) = parts.next() else { continue }; let Some(en) = parts.next() else { continue };
let command = parts.next().and_then(|p| p.split_whitespace().next()).unwrap_or(""); let command = parts.next().and_then(|p| p.split_whitespace().next()).unwrap_or("");
if !catalog.iter().any(|(id, source, _)| *id == command && *source == en) { if !catalog.iter().any(|(id, source, _)| *id == command && *source == en) && seen.insert((command, en)) {
missing.push(en.to_string()); missing.push(en.to_string());
} }
} }
@@ -7459,11 +7461,7 @@ mod tests {
println!("Missing translations (shown in English):"); println!("Missing translations (shown in English):");
let mut total = 0; let mut total = 0;
for code in CATALOG_CODES { for code in CATALOG_CODES {
let catalogs = [ let catalogs = [("menus", missing_menu_entries(code)), ("panels", ui::missing(code)), ("effects", effects::missing(code))];
("menus", missing_menu_entries(code)),
("panels", ui::missing(code).into_iter().map(str::to_string).collect()),
("effects", effects::missing(code)),
];
let count: usize = catalogs.iter().map(|(_, missing)| missing.len()).sum(); let count: usize = catalogs.iter().map(|(_, missing)| missing.len()).sum();
total += count; total += count;
println!("{code}: {count}"); println!("{code}: {count}");
+46 -5
View File
@@ -80,13 +80,53 @@ fn catalog(language: &str) -> Option<&'static Index> {
Some(index.get_or_init(|| rows.iter().copied().collect())) Some(index.get_or_init(|| rows.iter().copied().collect()))
} }
/// The panel strings `language` has no row for (every English source any catalog lists), in /// The panel strings `language` has no row for, for the missing-strings report: every English
/// catalog order, for the missing-strings report. /// source any catalog lists, in catalog order, then the literals the converted panels pass to `tr`
/// or `tr_args` that no catalog lists yet.
#[cfg(test)] #[cfg(test)]
pub(super) fn missing(language: &str) -> Vec<&'static str> { pub(super) fn missing(language: &str) -> Vec<String> {
let table = catalog(language); let table = catalog(language);
let rows = ROWS.iter().flat_map(|(_, rows)| rows.iter().map(|(en, _)| en.to_string()));
let literals = tests::CONVERTED.iter().flat_map(|(_, src)| tr_literals(src));
let mut seen = std::collections::HashSet::new(); let mut seen = std::collections::HashSet::new();
ROWS.iter().flat_map(|(_, rows)| rows.iter().map(|(en, _)| *en)).filter(|en| seen.insert(*en) && !table.is_some_and(|c| c.contains_key(en))).collect() rows.chain(literals).filter(|en| !table.is_some_and(|c| c.contains_key(en.as_str())) && seen.insert(en.clone())).collect()
}
/// The string literals `src` passes to `tr(` and `tr_args(`.
#[cfg(test)]
fn tr_literals(src: &str) -> Vec<String> {
let mut out = Vec::new();
for call in ["tr(\"", "tr_args(\""] {
for (at, _) in src.match_indices(call) {
// `str("` or `attr("` is another call.
if src[..at].chars().next_back().is_some_and(|c| c.is_alphanumeric() || c == '_') {
continue;
}
out.extend(string_literal(&src[at + call.len()..]));
}
}
out
}
/// The text of the string literal `rest` starts inside (just after its opening quote), with the
/// common escapes resolved; `None` for an escape this doesn't resolve.
#[cfg(test)]
fn string_literal(rest: &str) -> Option<String> {
let mut out = String::new();
let mut chars = rest.chars();
while let Some(c) = chars.next() {
match c {
'"' => return Some(out),
'\\' => match chars.next()? {
'n' => out.push('\n'),
't' => out.push('\t'),
c @ ('\\' | '"' | '\'') => out.push(c),
_ => return None,
},
c => out.push(c),
}
}
None
} }
/// Characters required by the fixed panel catalog, for system-font coverage selection. /// Characters required by the fixed panel catalog, for system-font coverage selection.
@@ -5388,9 +5428,10 @@ mod tests {
/// The interface text that has moved into this catalog. Add a panel here when you convert it; /// The interface text that has moved into this catalog. Add a panel here when you convert it;
/// `every_row_is_used_by_a_converted_panel` then looks for its rows' English sources in it. /// `every_row_is_used_by_a_converted_panel` then looks for its rows' English sources in it.
const CONVERTED: &[(&str, &str)] = &[ pub(super) const CONVERTED: &[(&str, &str)] = &[
("color_picker.rs", include_str!("../color_picker.rs")), ("color_picker.rs", include_str!("../color_picker.rs")),
("credits.rs", include_str!("../credits.rs")), ("credits.rs", include_str!("../credits.rs")),
("dock.rs", include_str!("../dock.rs")),
("dock_ui.rs", include_str!("../dock_ui.rs")), ("dock_ui.rs", include_str!("../dock_ui.rs")),
("header.rs", include_str!("../header.rs")), ("header.rs", include_str!("../header.rs")),
("menus.rs", include_str!("../menus.rs")), ("menus.rs", include_str!("../menus.rs")),