Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
4f69f67
i18n: share locale data and adapt Russian formulas and errors
reeeji Oct 29, 2026
a4148f1
i18n: deduplicate catalog keys introduced by current main
reeeji Oct 29, 2026
dc81f4e
docs: document localized formula boundaries and regression tests
reeeji Oct 29, 2026
6862c2f
i18n: sync main and preserve localized reference highlighting (#2880)
reeeji Oct 31, 2026
1e77ab5
i18n: preserve command context in untranslated parameter errors
reeeji Oct 31, 2026
26b1d30
i18n: sync main and expand Russian round-trip review coverage (#2880)
reeeji Nov 2, 2026
ba78375
i18n: translate new page-order labels (#2880)
reeeji Nov 2, 2026
a088a0b
i18n: preserve stored text on Ctrl+Enter without quoting (#2880)
reeeji Nov 2, 2026
f67dc29
i18n: complete interface localization and all-language function help …
reeeji Nov 2, 2026
27f6c26
i18n: finish function dialog labels and financial terminology (#2880)
reeeji Nov 2, 2026
49a7bfd
i18n: sync Python and equation main while preserving localized editin…
reeeji Nov 2, 2026
72cd3b7
i18n: audit all function help and fix localization review findings
reeeji Nov 2, 2026
81aaf65
i18n: preserve typed input and address full localization review
reeeji Nov 3, 2026
a59362a
Merge upstream main for GridCraft 0.16.0
reeeji Nov 3, 2026
289ff4b
Exercise scoped name round trips and retain delete command defaults
reeeji Nov 3, 2026
f49aabc
Confirm scoped-name dialog with its keyboard action
reeeji Nov 3, 2026
bcd174c
Merge remote-tracking branch 'origin/main' into russian-localization-pr
reeeji Nov 3, 2026
cfbfcad
Preserve draft literals and formula-view filter labels
reeeji Nov 3, 2026
4ce84ed
Merge remote-tracking branch 'origin/main' into russian-localization-pr
reeeji Nov 3, 2026
fe9f6e6
Validate literal fill destinations and localize formula filter labels
reeeji Nov 3, 2026
c3c56cf
Reuse a scratch workbook for literal fill validation
reeeji Nov 3, 2026
09ba002
Localize page labels in Page Break Preview
reeeji Nov 3, 2026
d060fc6
Disambiguate Add and Comma in localized dialogs
nihilist-narwhal Nov 3, 2026
f81f028
Extend the de interface catalog from #3034
DomiLuebben Nov 3, 2026
1c772f7
Extend the de interface catalog from #3232
MrChocolate12 Nov 3, 2026
d0bb8a9
Extend the fr interface catalog from #2965
Belenos-Toutatis Nov 3, 2026
d6c3d81
Extend the es interface catalog from #2646
nihilist-narwhal Nov 3, 2026
af05909
Extend the zh interface catalog from #3150
rahul05ranjan Nov 3, 2026
aeb3a14
Extend the pl interface catalog from #2391
webglobepl Nov 3, 2026
502d8e4
Extend the tr interface catalog from #2753
erolsavas19 Nov 3, 2026
26df3de
Extend the it interface catalog from #2460
carminezac Nov 3, 2026
046d5c6
Show tooltips for workbook and interface options
TheRenegadeDev Nov 3, 2026
40a3dbb
Translate context keys in the expanded catalogs
reeeji Nov 3, 2026
503669e
Localize the name picker and show the target theme on hover
reeeji Nov 3, 2026
14f66e4
Translate DrawingML and XLSB import warnings in the UI
eduardokimmel Nov 3, 2026
d98f90a
Localize Turkish chart controls and selection statistics
suleymantaha Nov 3, 2026
7da0c6a
Apply reviewed Italian interface terminology
carminezac Nov 3, 2026
bab7b3d
Translate native macOS menu titles and refresh them on language changes
DerKarussellbremser Nov 3, 2026
802b2fa
Format native menu integration
reeeji Nov 3, 2026
2c7db96
Localize the shared update prompt and Russian native menu labels
reeeji Nov 3, 2026
e5fc03b
Translate remaining border, table and spelling controls
Belenos-Toutatis Nov 3, 2026
5484213
Use valid Rust literals for Russian menu keys
reeeji Nov 3, 2026
902a7a6
Confirm the chart test with the localized button
reeeji Nov 3, 2026
7b42a5d
Extend localization for existing controls from #2969
Belenos-Toutatis Nov 3, 2026
1bfb671
Extend localization for existing controls from #2734
Antxis77 Nov 3, 2026
5e31d7d
Format the localized macro notice
reeeji Nov 3, 2026
4125f93
Adapt active interface translations from #2969
Belenos-Toutatis Nov 3, 2026
0388cfd
Adapt active interface translations from #2734
Antxis77 Nov 3, 2026
6f1c341
Adapt active interface translations from #2647
Nov 3, 2026
c683a5b
Use French command-search examples and paste-link wording
Belenos-Toutatis Nov 3, 2026
e9a43b4
Clarify Chinese table banding and chart terms
reeeji Nov 3, 2026
614a028
Check Portuguese minimum and maximum labels in the status bar
reeeji Nov 3, 2026
b35d7bf
Read regional built-in XLSX date, time and numeric formats
tisca26 Nov 3, 2026
d6a43da
Render German number and date previews with invariant format codes
AndreasHink Nov 3, 2026
35eff58
Support Polish formula entry and display with shared locale data
webglobepl Nov 3, 2026
8f7391a
Add fr formula locale data
blaspheme42 Nov 3, 2026
ee239a4
Add it formula locale data
Alftakeaway Nov 3, 2026
8eb9bcb
Add pt formula locale data
Nov 3, 2026
9afed9d
Verify localized array syntax, errors and XLSX round trips
reeeji Nov 3, 2026
b6d0fa1
Keep canonical function help distinct from localized aliases
reeeji Nov 3, 2026
a02ee1c
Localize generated chart series names in every interface language
reeeji Nov 3, 2026
ab801d1
Add Traditional Chinese interface and function help
Nov 3, 2026
95a84fc
Add German formula names and error spellings
Nov 3, 2026
7bc1b46
Refine Portuguese worksheet help terminology
quantforge-br Nov 3, 2026
4b177fa
Translate remaining hard-coded labels on matching UI paths
Belenos-Toutatis Nov 3, 2026
169a640
Localize Solver objective choices and report options
Belenos-Toutatis Nov 3, 2026
cbc81ab
Verify German XLSX round trips and register Traditional Chinese packa…
reeeji Nov 3, 2026
8bc7805
Translate contextual paste and delimiter options in Traditional Chinese
reeeji Nov 3, 2026
97a8110
Localize structured formula references through LocaleData
quantmind-br Nov 3, 2026
bcbc4c4
Expand Brazilian Portuguese formula names
paulohsconceicao Nov 3, 2026
36289e9
Prefer Traditional Chinese system glyphs for zh-TW
tommy1616 Nov 3, 2026
fb63996
Add Turkish formula input and error spellings
yeniufuklar-com Nov 3, 2026
ab4361a
Add Danish, Swedish, Georgian and Lithuanian interface catalogs
reeeji Nov 3, 2026
46df604
Verify Turkish formula editing after enabling its dialect
reeeji Nov 3, 2026
311717b
Translate page order for the new interface catalogs
reeeji Nov 3, 2026
8f8fc87
Verify printed page labels for all four added languages
reeeji Nov 3, 2026
6ae266a
Merge upstream main into Russian localization
reeeji Nov 3, 2026
45a4e0d
Merge remote-tracking branch 'upstream/sync-2880-main' into sync-pr2880
reeeji Nov 3, 2026
2f15b36
Merge branch 'localization-followups' into sync-pr2880
reeeji Nov 3, 2026
3de343d
Document Turkish formulas and Spanish structured references
reeeji Nov 3, 2026
185c934
Deduplicate the Simplified Chinese hyperlink label after merging main
reeeji Nov 3, 2026
56712d3
Address multilingual localization review findings
reeeji Nov 3, 2026
ef6dc79
Merge main and preserve localized formula bindings and number formats
reeeji Nov 4, 2026
abfd339
Merge main and preserve localization in credits tables and chart panes
reeeji Nov 5, 2026
7889f6c
i18n: fix full-review findings in catalogs and regional formats
reeeji Nov 5, 2026
177edbe
i18n: align localized grid previews and correct follow-up translations
reeeji Nov 5, 2026
9d5effe
i18n: keep localized defaults in UI paths and fix regional review fin…
reeeji Nov 5, 2026
053b13b
i18n: fix startup names, sheet comparisons, and table totals
reeeji Nov 5, 2026
23f6691
i18n: complete Czech catalogs and address localized UI review findings
reeeji Nov 5, 2026
66b8ce7
i18n: merge main and correct localization review findings
reeeji Nov 6, 2026
220948b
i18n: refine Greek, Ukrainian, and Danish catalog labels
reeeji Nov 6, 2026
45493f1
i18n: merge main and keep localized chart series names
reeeji Nov 8, 2026
4c42e4f
i18n: fold sheet names in reference rewrites
reeeji Nov 8, 2026
e7d4011
i18n: correct catalog entries and locale notes from review
reeeji Nov 8, 2026
d1ccc71
i18n: drop the stale "Salvare" key from the inventory
reeeji Nov 8, 2026
6a038d9
i18n: the header menu's Insert in the new catalogs
reeeji Nov 8, 2026
f7fd0a0
Merge upstream/main
reeeji Nov 9, 2026
0f1ea35
formula: a sheet rename splits the qualified name at the sheet separator
reeeji Nov 9, 2026
1545bec
Merge remote-tracking branch 'upstream/main' into russian-localization
reeeji Nov 9, 2026
0f70857
i18n: Greek clause punctuation and one Ukrainian word for a cell
reeeji Nov 9, 2026
8f5563a
i18n: one Boolean-alias check, generated prefixes tested in every lan…
reeeji Nov 9, 2026
ffdb698
docs: localization counts, the Languages table and array separators
reeeji Nov 9, 2026
7196a1c
formula: keep the French array test in the tests module
reeeji Nov 9, 2026
514e3e6
Merge main and fix localization review findings
reeeji Nov 10, 2026
f1b9eb3
Merge main into localization expansion PR
reeeji Nov 10, 2026
40afeb7
Merge latest main into localization expansion PR
reeeji Nov 10, 2026
9de007a
Merge latest main into russian localization
reeeji Nov 11, 2026
0eb8952
Merge latest main release workflow
reeeji Nov 11, 2026
c55e6db
i18n: use workbook region for display formatting
reeeji Nov 11, 2026
6d66cce
i18n: address review findings
reeeji Nov 11, 2026
ff7b656
Preserve draft formula syntax and correct localized display details
reeeji Nov 11, 2026
344c5e5
Fix localization review findings and keep locale-dependent XLSX mappi…
reeeji Nov 12, 2026
d2a57a0
Merge current main and address localization review regressions
reeeji Nov 12, 2026
168565b
Preserve sheet identity in sparkline and pivot source rewrites
reeeji Nov 12, 2026
75344b1
Validate complete literal fill states and correct scope picker confir…
reeeji Nov 12, 2026
23827b2
Preserve explicit UDF identity and finish localization review fixes
reeeji Nov 12, 2026
a7a5edd
Preflight simple validation rules and group matching localized filter…
reeeji Nov 12, 2026
2799976
Preserve localized callback names, format literals and empty text val…
reeeji Nov 12, 2026
4849db2
Exercise actual literal cells in the filter identity regression
reeeji Nov 12, 2026
0645790
i18n: merge main and fix array validation, filter groups and chart la…
reeeji Nov 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ craft-csv = { git = "https://github.com/storytold/craft-libs", rev = "bb57d422d0
# Excel 97-2003 (.xls, BIFF8) workbooks; `crates/xlsx/src/xls.rs` maps them onto the model.
craft-xls = { git = "https://github.com/storytold/craft-libs", rev = "bb57d422d0bc842cbde430955825553ab7b976f4", version = "0.1.0" }

craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "1b34eba41e62138fc2438d2734523b302840b45d", version = "=0.1.0" }

# foundation
thiserror = "2"
serde = { version = "1", features = ["derive", "rc"] }
Expand Down
26 changes: 26 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,29 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


Polish formula names (crates/formula/src/input/pl.rs)
Source: https://github.com/PHPOffice/PhpSpreadsheet/tree/master/src/PhpSpreadsheet/Calculation/locale/pl

MIT License

Copyright (c) 2019-2026 PhpSpreadsheet Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ cargo run --release -p gridcraft -- --sample sales --control 7979 # + JSON con
cargo xtask ci # fmt, clippy, tests, assets, layering, wasm
```

Choose Español under View → Interface language for [Spanish formula names, decimal commas, and semicolon arguments](docs/formula-input.md).
Under View → Interface language, choose Spanish, Russian, Polish, French, German, Italian, Brazilian Portuguese, or Turkish for [localized formula names, decimal commas, and semicolon arguments](docs/formula-input.md).
Workbooks, clipboard exchange, and programmatic APIs retain canonical English syntax.

### Web
Expand Down
2 changes: 1 addition & 1 deletion apps/gridcraft-cli/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ fn collect_inputs(inputs: &[String]) -> Discovery {
/// Returns false only when the global file budget stops traversal. `seen` holds the shallowest
/// depth each directory was scanned from; `rescan` is set below a directory reached again from a
/// shallower path (its depth-limited subtree may now fit), where already collected files are skipped.
#[cfg_attr(not(unix), allow(unused_mut))]
#[cfg_attr(not(unix), allow(unused_mut, clippy::only_used_in_recursion))]
fn walk(dir: &Path, depth: usize, mut rescan: bool, out: &mut Discovery, seen: &mut std::collections::HashMap<(u64, u64), usize>) -> bool {
// A directory is visited once by identity (dev+inode on unix) unless reached from a shallower
// path: several links to the same directory are aliases, not exponential branches of the scan
Expand Down
1 change: 1 addition & 0 deletions apps/gridcraft-cli/tests/it/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ fn a_confined_program_reads_only_what_it_is_given() {
}

#[test]
#[cfg(unix)]
fn a_launch_whose_check_fails_runs_nothing() {
// A run that says it is already confined and isn't finds the canary readable and must refuse.
// On macOS that is the second stage started without `sandbox-exec`; on Linux the launcher never
Expand Down
5 changes: 4 additions & 1 deletion apps/gridcraft-web/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ pub fn start() {
let browser = gridcraft_ui_egui::i18n::Language::system();
if !matches!(
browser,
gridcraft_ui_egui::i18n::Language::Zh | gridcraft_ui_egui::i18n::Language::Ja | gridcraft_ui_egui::i18n::Language::Ko
gridcraft_ui_egui::i18n::Language::Zh
| gridcraft_ui_egui::i18n::Language::ZhTw
| gridcraft_ui_egui::i18n::Language::Ja
| gridcraft_ui_egui::i18n::Language::Ko
) {
app.ui.language = browser;
}
Expand Down
42 changes: 37 additions & 5 deletions apps/gridcraft/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl eframe::App for App {
#[cfg(target_os = "macos")]
{
if self.2.is_none() && std::env::var_os("GRIDCRAFT_NO_NATIVE_MENU").is_none() {
self.2 = Some(native_menu::NativeMenu::install(ctx));
self.2 = Some(native_menu::NativeMenu::install(ctx, self.0.ui.language));
}
if let Some(m) = &mut self.2 {
m.poll(&mut self.0, ctx, self.1.focused_viewport());
Expand Down Expand Up @@ -240,6 +240,17 @@ fn icon() -> Option<egui::IconData> {
Some(egui::IconData { width: img.width(), height: img.height(), rgba: img.into_raw() })
}

/// Creates the desktop's initial blank book after preferences load; supplied documents are kept.
fn ensure_startup_workbook(app: &mut SheetApp) {
let language = app.ui.language;
if app.session.documents().is_empty()
&& let Err(error) = app.run("file.new", json!({"bookPrefix": language.tr("Book"), "sheetPrefix": language.tr("Sheet")}))
{
log::warn!("new workbook: {error}");
}
}

/// Starts the desktop application with persisted preferences, localized new-document names and native services.
fn main() -> eframe::Result<()> {
// A confined launcher run (`gridcraft_sandbox::launch_command`) comes before everything else:
// it opens no window, writes no log, and becomes the program it confines.
Expand Down Expand Up @@ -329,10 +340,8 @@ fn main() -> eframe::Result<()> {
app.start_update_check();
let control_port = control_port;
load_prefs(&mut app);
// Stamp the startup workbook's author from the persisted user name.
if app.session.documents().is_empty() {
app.session.new_workbook();
}
// Stamp the localized startup workbook's author from the persisted user name.
ensure_startup_workbook(&mut app);
app.after_engine(); // Show warnings from the sample.
// Files named on the command line (a double-click on Windows and Linux) open as File › Open
// opens them: a password-protected one asks for its password, and errors are shown, not
Expand Down Expand Up @@ -462,3 +471,26 @@ mod packaging_tests {
}
}
}

#[cfg(test)]
mod startup_tests {
/// Saved interface language applies on launch, while existing documents are never renamed or replaced.
#[test]
fn startup_workbook_uses_loaded_language_and_keeps_existing_documents() {
let mut app = gridcraft_ui_egui::SheetApp::new(gridcraft_engine::Session::new(), Default::default());
app.ui.language = gridcraft_ui_egui::i18n::Language::Ru;
super::ensure_startup_workbook(&mut app);
assert_eq!(app.session.active().unwrap().title, "Книга1");
assert_eq!(app.session.active().unwrap().wb.active().unwrap().name, "Лист1");
super::ensure_startup_workbook(&mut app);
assert_eq!(app.session.documents().len(), 1);
let mut session = gridcraft_engine::Session::new();
session.new_workbook_named("Existing", "Data");
let mut app = gridcraft_ui_egui::SheetApp::new(session, Default::default());
app.ui.language = gridcraft_ui_egui::i18n::Language::Ru;
super::ensure_startup_workbook(&mut app);
assert_eq!(app.session.documents().len(), 1);
assert_eq!(app.session.active().unwrap().title, "Existing1");
assert_eq!(app.session.active().unwrap().wb.active().unwrap().name, "Data1");
}
}
94 changes: 77 additions & 17 deletions apps/gridcraft/src/native_menu.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! macOS: the native menu bar (File, Edit, View, Insert, Format, Tools, Data, Window, Help), as
//! a desktop spreadsheet has on the Mac. Items run the same commands as the ribbon.
//! a desktop spreadsheet has on the Mac. Items run the same commands as the ribbon. Titles are in
//! the interface language and follow it when it changes.

use std::cell::Cell;
use std::str::FromStr;
use std::sync::mpsc::{Receiver, channel};

use gridcraft_ui_egui::i18n::Language;
use gridcraft_ui_egui::{OpenWindow, SheetApp};
use muda::accelerator::Accelerator;
use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
Expand Down Expand Up @@ -84,7 +87,7 @@ fn tree() -> Vec<(&'static str, Vec<Entry>)> {
],
),
(
"Insert",
"Tab|Insert",
vec![
("Cells…", "dialog:insertCells", None),
("Rows", "home.insertRows", None),
Expand Down Expand Up @@ -129,7 +132,8 @@ fn tree() -> Vec<(&'static str, Vec<Entry>)> {
],
),
(
"Tools",
// Excel for Mac's name for it differs from the ribbon's Tools in some languages.
"menu|Tools",
vec![
("Spelling…", "dialog:spelling", None),
("Workbook Statistics", "dialog:statistics", None),
Expand Down Expand Up @@ -199,6 +203,29 @@ pub struct NativeMenu {
/// The Window menu's window items and what they last showed.
window_items: Vec<CheckMenuItem>,
windows: Vec<OpenWindow>,
/// Every entry with a title, and its English text (the catalog key).
titled: Vec<(Titled, &'static str)>,
/// The language the titles are in.
language: Cell<Language>,
}

/// A menu entry whose title follows the interface language.
enum Titled {
Item(MenuItem),
Check(CheckMenuItem),
Sub(Submenu),
Predefined(PredefinedMenuItem),
}

impl Titled {
fn set_text(&self, text: &str) {
match self {
Titled::Item(i) => i.set_text(text),
Titled::Check(i) => i.set_text(text),
Titled::Sub(i) => i.set_text(text),
Titled::Predefined(i) => i.set_text(text),
}
}
}

/// Menu id of the Window menu item that brings workbook `doc` forward.
Expand All @@ -220,49 +247,65 @@ fn select_theme(app: &mut SheetApp, id: &str) -> bool {
}

impl NativeMenu {
pub fn install(ctx: &egui::Context) -> NativeMenu {
pub fn install(ctx: &egui::Context, language: Language) -> NativeMenu {
let tr = |key: &str| language.tr(key).to_owned();
let mut titled: Vec<(Titled, &'static str)> = Vec::new();
let menu = Menu::new();
let app_menu = Submenu::new("GridCraft", true);
let about = MenuItem::with_id("dialog:about", "About GridCraft", true, None);
let about = MenuItem::with_id("dialog:about", tr("About GridCraft"), true, None);
let services = PredefinedMenuItem::services(Some(&tr("Services")));
let hide = PredefinedMenuItem::hide(Some(&tr("Hide GridCraft")));
let hide_others = PredefinedMenuItem::hide_others(Some(&tr("Hide Others")));
let show_all = PredefinedMenuItem::show_all(Some(&tr("Show All")));
let quit = MenuItem::with_id(QUIT, tr("Quit GridCraft"), true, Accelerator::from_str("CMD+Q").ok());
let _ = app_menu.append_items(&[
&about,
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::services(None),
&services,
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::hide(None),
&PredefinedMenuItem::hide_others(None),
&PredefinedMenuItem::show_all(None),
&hide,
&hide_others,
&show_all,
&PredefinedMenuItem::separator(),
&MenuItem::with_id(QUIT, "Quit GridCraft", true, Accelerator::from_str("CMD+Q").ok()),
&quit,
]);
titled.push((Titled::Item(about), "About GridCraft"));
titled.push((Titled::Item(quit), "Quit GridCraft"));
for (item, key) in [(services, "Services"), (hide, "Hide GridCraft"), (hide_others, "Hide Others"), (show_all, "Show All")] {
titled.push((Titled::Predefined(item), key));
}
let _ = menu.append(&app_menu);
let mut theme_items = Vec::new();
let mut window_menu = None;
for (title, entries) in tree() {
let sub = Submenu::new(title, true);
let sub = Submenu::new(tr(title), true);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
for (label, id, acc) in entries {
if label == "-" {
let _ = sub.append(&PredefinedMenuItem::separator());
continue;
}
if id == "view.theme" {
let themes = Submenu::new(label, true);
let themes = Submenu::new(tr(label), true);
for (mode, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] {
let item = CheckMenuItem::with_id(format!("theme:{mode}"), label, true, false, None);
let item = CheckMenuItem::with_id(format!("theme:{mode}"), tr(label), true, false, None);
let _ = themes.append(&item);
theme_items.push((mode, item));
theme_items.push((mode, item.clone()));
titled.push((Titled::Check(item), label));
}
let _ = sub.append(&themes);
titled.push((Titled::Sub(themes), label));
continue;
}
let accel = acc.and_then(|a| Accelerator::from_str(a).ok());
let item = MenuItem::with_id(id, label, true, accel);
let item = MenuItem::with_id(id, tr(label), true, accel);
let _ = sub.append(&item);
titled.push((Titled::Item(item), label));
}
let _ = menu.append(&sub);
if title == "Window" {
window_menu = Some(sub);
window_menu = Some(sub.clone());
}
titled.push((Titled::Sub(sub), title));
}
menu.init_for_nsapp();
// NSApp's windowsMenu is where macOS adds Move & Resize, Fill and Center, the targets of the
Expand All @@ -277,7 +320,16 @@ impl NativeMenu {
let _ = tx.send(e);
ctx.request_repaint();
}));
NativeMenu { _menu: menu, events: rx, theme_items, window_menu, window_items: Vec::new(), windows: Vec::new() }
NativeMenu {
_menu: menu,
events: rx,
theme_items,
window_menu,
window_items: Vec::new(),
windows: Vec::new(),
titled,
language: Cell::new(language),
}
}

/// Keeps the Window menu's list of open windows (title, unsaved bullet, active check) in
Expand Down Expand Up @@ -315,6 +367,14 @@ impl NativeMenu {
/// Runs the chosen menu items. Commands go to the active window, the most recently focused;
/// `viewport` is its egui viewport, for the items that act on its input.
pub fn poll(&mut self, app: &mut SheetApp, ctx: &egui::Context, viewport: egui::ViewportId) {
// The interface language changed (View › Interface Language): retitle the menus.
let language = app.ui.language;
if language != self.language.get() {
self.language.set(language);
for (entry, key) in &self.titled {
entry.set_text(language.tr(key));
}
}
while let Ok(ev) = self.events.try_recv() {
let id = ev.id.as_ref().to_string();
if id == QUIT {
Expand Down
10 changes: 9 additions & 1 deletion apps/gridcraft/tests/mac_menu_appkit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn main() {
nsapp.finishLaunching();

let ctx = egui::Context::default();
let mut menu = native_menu::NativeMenu::install(&ctx);
let mut menu = native_menu::NativeMenu::install(&ctx, gridcraft_ui_egui::i18n::Language::En);

let bar = nsapp.mainMenu().expect("a main menu");
let tops: Vec<String> = (0..bar.numberOfItems()).filter_map(|i| bar.itemAtIndex(i)?.submenu()).map(|m| m.title().to_string()).collect();
Expand All @@ -37,6 +37,14 @@ fn main() {
let last = windows_menu.itemAtIndex(windows_menu.numberOfItems() - 1).expect("a Window menu item");
assert_eq!(last.title().to_string(), open[0].label(), "the Window menu lists the open workbook");

// A contextual menu title remains a noun on a language switch, and refreshes back to English.
for (language, expected) in [("ru", "Вставка"), ("en", "Insert")] {
app.run("app.language.set", serde_json::json!({"language": language})).unwrap();
menu.poll(&mut app, &ctx, egui::ViewportId::ROOT);
let insert = bar.itemAtIndex(4).unwrap().submenu().unwrap();
assert_eq!(insert.title().to_string(), expected);
}

println!("mac_menu_appkit: ok");
}

Expand Down
3 changes: 3 additions & 0 deletions contributors/people.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,8 @@ sync_github_name = true
[people.maxm11]
sync_github_name = true

[people.reeeji]
sync_github_name = true

[people.RockerHX]
sync_github_name = true
Loading