#1145: macOS app menu follows the interface language
About FilmCraft and Join the ArtCraft Discord… in the macOS app menu take the translated labels of the same Help menu commands and are updated on a language switch. Hide, Hide Others and Quit get their text from the catalogs instead of muda's English defaults and keep their native actions. menus::NATIVE_APP_MENU lists these native-only labels together with the Settings submenu, so the app and the tests use the same keys. German rows for the three new keys; "Quit FilmCraft" is already translated in es, ja, zh-cn and fr (the app.quit shortcut), the other languages show English until they have translations. The German menu test accepts and requires the native app menu labels. Closes #1145 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0800a8b0aa
commit
4ebcf3aa67
|
|
@ -4,7 +4,7 @@
|
|||
use std::sync::mpsc::{Receiver, channel};
|
||||
|
||||
use filmcraft_ui_egui::FilmcraftApp;
|
||||
use filmcraft_ui_egui::menus::{MENUS, MenuItem as Item, menu_items};
|
||||
use filmcraft_ui_egui::menus::{MENUS, MenuItem as Item, NATIVE_APP_MENU, menu_items};
|
||||
use muda::accelerator::Accelerator;
|
||||
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
|
||||
|
||||
|
|
@ -28,25 +28,39 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Sho
|
|||
let mut titles: Vec<(String, Submenu)> = Vec::new();
|
||||
let bar = Menu::new();
|
||||
let app_menu = Submenu::new("FilmCraft", true);
|
||||
let [settings_label, hide_label, hide_others_label, quit_label] = NATIVE_APP_MENU;
|
||||
// FilmCraft ▸ Settings ▸ <category> (Premiere's app-menu layout; General is Cmd+,)
|
||||
let settings = Submenu::new(app.ui.language.tr("Settings"), true);
|
||||
titles.push(("Settings".into(), settings.clone()));
|
||||
let settings = Submenu::new(app.ui.language.tr(settings_label), true);
|
||||
titles.push((settings_label.into(), settings.clone()));
|
||||
for it in items.iter().filter(|i| i.id.starts_with("app.settings.")) {
|
||||
let label = it.label.trim_end_matches('…').to_string();
|
||||
let mi = MenuItem::with_id(it.id.clone(), label, true, it.shortcut.as_deref().and_then(accel));
|
||||
native.push((it.id.clone(), mi.clone()));
|
||||
let _ = settings.append(&mi);
|
||||
}
|
||||
// About and Discord repeat Help menu commands: same translated labels, updated with them
|
||||
let label = |id: &str, english: &str| items.iter().find(|i| i.id == id).map_or_else(|| english.to_string(), |i| i.label.clone());
|
||||
let about = MenuItem::with_id("app.about", label("app.about", "About FilmCraft"), true, None);
|
||||
let discord = MenuItem::with_id("help.discord", label("help.discord", "Join the ArtCraft Discord…"), true, None);
|
||||
native.push(("app.about".into(), about.clone()));
|
||||
native.push(("help.discord".into(), discord.clone()));
|
||||
// the predefined items keep their native actions; our text lets them follow the language
|
||||
let predefined = [
|
||||
(hide_label, PredefinedMenuItem::hide(Some(app.ui.language.tr(hide_label)))),
|
||||
(hide_others_label, PredefinedMenuItem::hide_others(Some(app.ui.language.tr(hide_others_label)))),
|
||||
(quit_label, PredefinedMenuItem::quit(Some(app.ui.language.tr(quit_label)))),
|
||||
];
|
||||
let [(_, hide), (_, hide_others), (_, quit)] = &predefined;
|
||||
let _ = app_menu.append_items(&[
|
||||
&MenuItem::with_id("app.about", "About FilmCraft", true, None),
|
||||
&MenuItem::with_id("help.discord", "Join the ArtCraft Discord…", true, None),
|
||||
&about,
|
||||
&discord,
|
||||
&PredefinedMenuItem::separator(),
|
||||
&settings,
|
||||
&PredefinedMenuItem::separator(),
|
||||
&PredefinedMenuItem::hide(None),
|
||||
&PredefinedMenuItem::hide_others(None),
|
||||
hide,
|
||||
hide_others,
|
||||
&PredefinedMenuItem::separator(),
|
||||
&PredefinedMenuItem::quit(None),
|
||||
quit,
|
||||
]);
|
||||
let _ = bar.append(&app_menu);
|
||||
for top in MENUS {
|
||||
|
|
@ -115,6 +129,9 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Sho
|
|||
for (title, submenu) in &titles {
|
||||
submenu.set_text(language.tr(title));
|
||||
}
|
||||
for (label, item) in &predefined {
|
||||
item.set_text(language.tr(label));
|
||||
}
|
||||
for (id, mi) in &native {
|
||||
if let Some(it) = items.iter().find(|i| &i.id == id) {
|
||||
mi.set_text(&it.label);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# apps, text addresses the user informally ("du") and commands use the infinitive ("Projekt
|
||||
# öffnen…"). Product, format and feature names (FilmCraft, ArtCraft, Lumetri, Essential Graphics,
|
||||
# EDL, AAF…) keep their spelling. This catalog covers every menu, including the native macOS
|
||||
# Settings menu; anything else is shown in English.
|
||||
# app menu; anything else is shown in English.
|
||||
#
|
||||
# Terms: track = Spur, bin = Ablage, marker = Marker, playhead = Abspielposition, In/Out = In-/Out-
|
||||
# Punkt, transition = Übergang, frame hold = Standbild, panel = Bedienfeld, workspace =
|
||||
|
|
@ -180,6 +180,8 @@
|
|||
Guide Templates Hilfslinienvorlagen
|
||||
Help Hilfe
|
||||
Hide All Caption Tracks Alle Untertitelspuren ausblenden
|
||||
Hide FilmCraft FilmCraft ausblenden
|
||||
Hide Others Andere ausblenden
|
||||
High Quality Playback Wiedergabe in hoher Qualität
|
||||
History Verlauf
|
||||
Import From Importieren aus
|
||||
|
|
@ -269,6 +271,7 @@
|
|||
Properties Eigenschaften
|
||||
Proxy Proxy
|
||||
Purple Lila
|
||||
Quit FilmCraft FilmCraft beenden
|
||||
Reconnect Full Resolution Media… Medien in voller Auflösung neu verknüpfen…
|
||||
Recover Unsaved Changes… Nicht gespeicherte Änderungen wiederherstellen…
|
||||
Rectangle Rechteck
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 45.
|
|
|
@ -931,7 +931,7 @@ mod tests {
|
|||
}
|
||||
|
||||
/// German covers at least the core menus (the Brazilian Portuguese entries, the top-level menus
|
||||
/// and the native macOS Settings menu); every entry must still be a menu label.
|
||||
/// and the native macOS app menu); every entry must still be a menu label.
|
||||
#[test]
|
||||
fn german_entries_cover_the_original_menu_catalog() {
|
||||
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
|
|
@ -939,17 +939,18 @@ mod tests {
|
|||
let known = |text: &str| crate::menus::MENUS.contains(&text) || items.iter().any(|it| it.label == text || it.path.iter().any(|p| p == text));
|
||||
let (entries, _) = catalog::parse_entries(include_str!("de.tsv"));
|
||||
for (_, en, _) in entries {
|
||||
assert!(known(&en) || en == "Settings", "not a menu label: {en}");
|
||||
assert!(known(&en) || crate::menus::NATIVE_APP_MENU.contains(&en.as_str()), "not a menu label: {en}");
|
||||
}
|
||||
let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
for (_, en, _) in portuguese {
|
||||
assert!(Language::De.has(&en), "missing German menu label: {en}");
|
||||
}
|
||||
// "Clip" and "Audio" are the same word in German, so check for an entry, not a change
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio"]).chain(crate::menus::NATIVE_APP_MENU) {
|
||||
assert!(Language::De.has(en), "untranslated German menu: {en}");
|
||||
}
|
||||
assert_eq!(Language::De.tr("Settings"), "Voreinstellungen");
|
||||
assert_eq!(Language::De.tr("Quit FilmCraft"), "FilmCraft beenden");
|
||||
}
|
||||
|
||||
/// French covers the whole interface (the Spanish catalog's entries and the native macOS
|
||||
|
|
|
|||
|
|
@ -622,6 +622,11 @@ pub struct MenuItem {
|
|||
|
||||
pub const MENUS: [&str; 9] = ["File", "Edit", "Clip", "Sequence", "Markers", "Graphics and Titles", "View", "Window", "Help"];
|
||||
|
||||
/// Labels only the native macOS app menu shows, with no command in the menu tree behind them: the
|
||||
/// Settings submenu and the predefined Hide, Hide Others and Quit items. They are translated through
|
||||
/// the catalogs like menu labels (`apps/filmcraft/src/native_menu.rs`).
|
||||
pub const NATIVE_APP_MENU: [&str; 4] = ["Settings", "Hide FilmCraft", "Hide Others", "Quit FilmCraft"];
|
||||
|
||||
pub fn menu_items(app: &FilmcraftApp) -> Vec<MenuItem> {
|
||||
let mut v = menu_items_for(&app.session);
|
||||
for it in &mut v {
|
||||
|
|
|
|||
Loading…
Reference in New Issue