Adds Romanian as an interface language: crates/ui-egui/src/i18n/ro.rs with 970 labels covering
every key of the other catalogs, registered in Language (switcher, app.language.set, ro-* system
locales). Context keys translate without their prefix; the File › Options kittest covers ro.
English formulas and formats; no proofing.
The catalog is an AI-assisted draft written from the meaning of the English labels; a native
speaker has not yet reviewed it line by line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
Page Setup, Insert and the other form dialogs drew their field labels in
English in every interface language, though their titles and OK/Cancel
were translated and most catalogs already carry the labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
The reader took only <x:Visible/> from a note's VML shape and vml_xml
wrote every note with a fixed anchor (two columns right of the cell,
four rows tall) and a 108 x 59.25 pt style, so a plain open and save
moved and resized every note box Excel had stored.
Comment gets an optional NoteBox: the <x:Anchor> corners relative to the
note's cell (so the box follows the note when rows or columns move) and
the shape's width and height. The writer puts it back, clamped to the
sheet; notes made here, and boxes equal to the default, keep the
default box.
Korean Excel saves a cell's font under its Korean name (맑은 고딕 for
Malgun Gothic), but the font scan kept one family name per face, the
English one, so those cells were drawn in the default face. Each face now
keeps its typographic and legacy family names in every language, and a
workbook font is bound under the name the workbook uses, which is the
name cell_family looks up. A name two families share goes to the family
whose English name it is, else to the first face by path, so the scan's
directory order no longer decides it. Names bound under two spellings
share one copy of the font data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Numbers stored as text are skipped by SUM and friends, and GridCraft gave
no hint. Like Excel's background error checking, a constant whose text
would be a number if typed now gets a green triangle in its top-left
corner; with such a cell active, a warning button beside it opens a menu
naming the problem with Convert to Number, which converts every flagged
cell in the selection as one undo step.
- core: `parse::number_stored_as_text` (signs, thousands separators,
currency, percent, exponent, one parenthesised negative). Dates, times
and fractions kept as text are not flagged, as in Excel.
- engine: `cmd::formulas::number_stored_as_text(cell)` (constants only,
not formula results) and `formulas.convertToNumber {range?}`, which
keeps each cell's format.
- ui: the triangle in the grid and the warning button with its menu;
pt-BR and German strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ok_cancel` put the buttons in a right-to-left layout straight in the
window. That layout takes all the height it is offered, so every form
dialog (Greater Than…, New Name, Remove Duplicates, …) grew to the
screen's height with OK and Cancel floating halfway down. The buttons
now sit in a horizontal row as tall as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog was a generic form: one column of fifteen radio buttons and
three check boxes, taller than the window, with English labels (the form
didn't run them through `tr`). It is now laid out as Excel's: Paste and
Operation each in two columns inside a frame, Skip blanks and Transpose
side by side under them, and Paste Link as a button that pastes
references to the copied cells at once. The letter shortcuts keep
working: the options still come from the dialog's fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A data validation alert is the general message box, drawn over the cell
editor, which stays open so the entry can be fixed. The editor read Enter and
Esc first: the first Esc cancelled the edit behind the alert and only the
second closed it, and Enter tried the entry again so the alert came back. The
editor now skips its own key handling while a message is open over it, so Esc
and Enter close the alert (like its OK button). The message box also had the
keyboard focus and didn't give it back, so typing went nowhere until the cell
was clicked; closing it now asks the open editor for focus again.
Tests: ui-egui tests/validation_alert.rs
esc_closes_a_validation_alert_and_leaves_the_entry and
enter_closes_a_validation_alert_and_leaves_the_entry (both fail on main, and
without the focus change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vertical scrollbar's track starts below the column headings (and any
frozen rows), and nothing painted the bar's column above it, so dark mode
showed a white square at the top-right corner of the grid. The whole
column now takes the track colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid's right-click handler never hit-tested objects, so right-clicking
a chart opened the menu of the cell underneath it. And since any button's
drag start grabbed an object, a right press that moved a little started
dragging the chart and no menu opened at all.
Right-clicking a chart, picture, shape or slicer now selects it and opens
its own menu (Delete; for charts also Select Data… and Format Chart
Area…). Only the primary button moves objects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save as Web Page wrote only the cell table, so pictures and charts on a sheet
were dropped. A sheet that has either now keeps its own geometry and draws them:
- The table gets the sheet's column widths and row heights (`table-layout:fixed`,
a `<colgroup>`, a height per row); hidden columns and rows take no room and
merged-cell spans count only visible lines.
- A picture is an `<img>` with a `data:` URI, a chart is inline SVG built from
the chart crate's drawing primitives (titles, axes, legends and data labels
included), each absolutely positioned at its anchor cell plus offset.
- Sheets without pictures or charts write byte-identical HTML.
Adds contributors/people.toml entry for bilalmughal.
Adds a Dutch catalog (`i18n/nl.rs`, 518 labels) covering every key the
other catalogs translate: ribbon, context menus, File view, dialog
titles and the Options page. `nl-NL` and `nl-BE` locale tags resolve
to Dutch. Updates localization parity and the roadmap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
auto_row_height grew a row for every line break in a cell's text whether
or not the cell wrapped. Excel counts breaks only when Wrap Text is on:
with it off a multi-line value displays on one line and AutoFit gives a
one-line row. A file whose unwrapped cells hold pasted multi-line text
(the shape generating libraries write) opened with tall rows and nothing
visibly tall in them.
The fast path no longer treats a line break as a reason to measure, and
the measuring path lays text out over lines only for a wrapping cell.
Typing a break still turns wrap on (the Alt+Enter rule in cell entry) and
grows the row, as before.
Test: an unwrapped multi-line cell from a file keeps the default height
on open; wrap on grows the row; wrap off returns it; a typed break wraps
and grows. The open-time fit tests from #833 are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`=1+` reported "unexpected `)`", although no `)` was typed: entry closes
missing parentheses by appending up to eight `)`, and when every retry
failed it reported the last retry's error. It now reports the error of
the formula as typed ("unexpected end of formula").
`gridcraft_formula::parse_closing_parens` holds the convenience and takes
the parse function, so cell entry and the UI's Spanish formula path share
it instead of each having its own loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The theme watcher (DesktopTheme) reads the desktop's light/dark choice
from the XDG Desktop Portal over a blocking D-Bus call with no timeout.
zbus waits forever by default, so a portal that accepts the call and
never answers stalled the watcher's thread on its first read, and "Use
system setting" stopped following the desktop for the rest of the
session, even after the portal recovered.
The session connection is now built with a 2 s method timeout (REPLY,
shorter than the 4 s POLL). A timed-out call returns InputOutput, which
read() already handles by dropping the connection, so the next poll
asks again on a fresh one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115HyvML9WnuFRdmtMcLP5t
The Japanese catalog still lived inline in i18n.rs with its ribbon and widget
labels, so with the interface set to 日本語 the dialog titles and labels, message
boxes, Backstage/File-view items, template names and native menus showed in
English. This moves it to i18n/ja.rs and adds the 545 keys the Brazilian
Portuguese catalog translates beyond it: 943 keys, the coverage pt-BR has
(~43% of ~2,200 strings), in Excel for Mac 日本語版 wording.
CORE keeps the shared reference set every catalog covers, verbatim and in the
same order (the cross-catalog coverage test still points at it); DIALOGS holds
the new strings, and the lookup checks it first. One test parses pt.rs's source
so the two catalogs cannot drift apart again, one checks that every key is
unique across the two lists and every value is translated, and one spot-checks
the high-value strings.
Docs: the Japanese row and the "Where we are" bullet in
docs/localization-parity.md (943, ~43%), a revision row, the ROADMAP Languages
paragraph and progress log, and gaps.md §11.
xl/sharedStrings.xml can carry phonetic runs (<rPh sb="0" eb="2"><t>ふりがな</t></rPh>)
and a <phoneticPr> beside a string item. GridCraft read only the text and wrote every
string back as <si><t>…</t></si>, so a plain open + save deleted a Japanese workbook's
furigana.
Both are now read — offsets validated against the base text, runs with bad or missing
offsets, sb >= eb or no reading dropped with a warning, and never used to index — and
kept on the workbook as Workbook::phonetics, one entry per string text. The writer
emits them next to the <si> of the same text. The saved table holds each text once, so
two identical texts cannot keep two different readings: the reader keeps the first and
warns. Strings without phonetic data are written byte for byte as before.
Behaviour reference: ISO/IEC 29500-1 §18.4.6 rPh and §18.4.7 phoneticPr (Microsoft
Learn PhoneticRun/PhoneticProperties). Tests: crates/xlsx/src/tests/phonetic_tests.rs
(round trip, plain string, hostile offsets, shared text, unused data, comments and
inline strings). Docs: file-format-parity.md, localization-parity.md.
A theme's <a:fontScheme> carries latin, ea (East Asian) and cs (complex
script) typefaces for each of the major and minor fonts. The model held
only the latin ones and the writer emitted <a:ea typeface=""/> and
<a:cs typeface=""/> unconditionally, so a Japanese workbook's theme fonts
(e.g. 游ゴシック, MS Pゴシック) were silently deleted on open + save.
Theme gains major_font_ea, minor_font_ea, major_font_cs and
minor_font_cs (#[serde(default)], so older GridCraft JSON still loads;
empty means the file did not set one). The XLSX reader and writer
round-trip them escaped, and a theme without them writes exactly the
empty attributes it wrote before.
Rendering and font resolution are unchanged: the UI does not consume ea
yet, and wiring it in is a follow-up.
The French catalog once shipped with over a hundred translations sitting
on their neighbours' keys ("Close" read "Société"); main has since fixed
them. A catalog shifted that way still covers every key, so the existing
coverage tests don't catch it.
A new test checks every language's translations keep the shape of their
English: the same {placeholders}, and no line break in a one-line label
(the two big ribbon buttons that wrap are exempt). Plus spot checks of
French labels that were once misplaced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The left end of a flat operator chain is not nesting, so (((...1)))+1 is
accepted whenever (((...1))) is, in Spanish entry and display.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
along: =1+1+... with 4,096 operands (8,192 characters, exactly Excel's
limit, which Excel accepts and calculates) overflowed the stack when
entered, because the derived Clone of Expr recursed once per operand
down the chain's left spine. Saving hit the same in the XLSX writer.
- Expr: Clone, PartialEq, walk and map follow the left spine in a loop
and recurse only into right operands and real nesting.
- XLSX: _xlpm. prefixing and the implicit-intersection scan follow the
spine too. The scan's depth limit of 64 wrote any chain of more than
about 64 operands as a dynamic-array formula; such sums are plain
formulas again (ranges in the chain still get array evaluation).
- Evaluate Formula borrows sub-expressions instead of cloning each
operator's subtree (quadratic memory on a long chain).
- Spanish formula entry and display no longer refuse chains of more
than 256 operands; the left spine does not count as nesting.
Tests run on a 1 MiB stack (the Windows main thread) with Excel's
observed results: formula (clone/eq/walk/map/adjust at 4,096), calc
(401 and 4,096 operands, -, *, /, ^, &, %, comparisons, 1,500 refs),
xlsx (long chains written as scalar formulas), engine (cell.set, fill,
copy/paste, insert rows/columns, undo/redo, Evaluate Formula, XLSX save
and reopen), Spanish entry. Each fails without the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sheet names starting with '.' or reading as R1C1 (R, C, RC, R1C1...) were saved
unquoted, so Excel could not open the file; PivotTable sources and ADDRESS left
cell-like names (A1) bare. The quoting rule, unquoting and sheet splitting now
live once in gridcraft_core::addr and replace the copies in the formula printer,
pivot, ADDRESS, chart, xlsx, Solver, Go To and the Name Box (which now reads ''
as ').
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lower-casing then upper-casing made dotless ı equal to i (both become I),
which Unicode's default case folding keeps apart; LET(i,1,ı,2,i) could
return the ı binding. same_name now lower-cases and adds the folds lower
case misses (ß/ẞ -> ss, ſ -> s, ς -> σ). Tests cover ı/i and the Greek
final sigma.
app.language.set's engine.commands docs and its unknown-language error
were written by hand and had fallen behind: the docs missed es, fr and
sr, the error missed de, fr and sr. Both are now built from
Language::ALL (Language::code_list), and tests check every language is
listed in each.
iter_range walks the sparse store's bands for the area's row range
only, so a chain of N formulas no longer rescans every stored cell of
the sheet per area.
Co-Authored-By: Claude <noreply@anthropic.com>
- gridcraft_formula::same_name lower-cases then upper-cases each
character, so ß matches ẞ (to_uppercase alone maps ß to SS).
- Formula-bar input uses it too, so a binding such as ſi is not
translated to IF while the evaluator binds it.
- Tests: ISOMITTED with a case variant, ß/ẞ, and the Spanish input case.
- The region already starts at the header row when headers are on, so
the dropdown label reads start.row for both cases (was showing the
first data value, e.g. B: 30 instead of B: Revenue).
- + Add Level picks the first region column no level uses yet instead
of columns.get(levels.len()), which re-seeded an already-used column.
Co-Authored-By: Claude <noreply@anthropic.com>
Excel matches LET and LAMBDA names case-insensitively in every script,
so =LET(dług,100,DŁUG*2) is 200. The evaluator compared them ASCII-only
and returned #NAME?. The three lookups (name evaluation, ISOMITTED, a
LET variable shadowing a function name) now fold Unicode case.