* M12.AR2: Arabic font fallback in cells and the interface (#229)
* M12.AR2: reserve the preferred Arabic file only once it has loaded
load_fallback treated fontconfig's Arabic file as already loaded even
when load_preferred_arabic could not load it, so a system Arabic face
from the same file was skipped and the chains could end up with no
Arabic system face. The file is now reserved only when the preferred
face is in the font set; the candidate loop moves to load_faces so a
test can drive it with a fixture file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12.AR2: say where fontconfig's Arabic choice applies
fontconfig's Arabic face leads the Arabic fallbacks, so it draws Arabic
where the UI or cell font has none. A UI font with Arabic of its own,
such as DejaVu Sans in the Flatpak, still draws interface Arabic (#229),
which is left for a follow-up. The docs and the test's doc comment no
longer say the choice comes first in every chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* M12.AR6: honour the B1/B2 calendar prefixes in number formats (Hijri for B2)
* M12.AR6: match Excel's B1/B2 behaviour measured in review
Changes from a reviewer's Excel run (TEXT and Range.NumberFormat):
- A [$-...] locale tag before B2 keeps the date Gregorian (prefix still
hidden); B2[$-401] stays Hijri.
- Any other unquoted B and digit (B3dd, dd/mm/yyyyB2, dd B2 mm), and B2
before a number format (B20.00), make the code invalid: TEXT gives
#VALUE!, a cell shows General, as for a code over 255 characters.
- Under B2, ddd and dddd show the Arabic weekday name (full name for both).
- Serial 60 has no Hijri date: TEXT gives #VALUE!, a cell shows ####.
- Serial 0 shows as serial 1 (29/08/1317).
Also fixes a break from merging main: days_from_civil now returns 0 for
day 0 and 29 February 1900, which sent serials 0 and 60 to 1970
(23/10/1389) under B2. The Hijri day count no longer passes those dates.
Docs: localization-parity states the behaviour, including the one
measured case not matched (mmmm next to dddd prints the weekday in Excel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12.AR6: Hijri leap year in cycle year 15, not 16, as Excel shows
A reviewer compared Excel's B2yyyy-mm-dd on 43,512 serials with this
formula: it disagreed by one day on 1,372, all in cycle years 15 and 16,
and moving the leap year from 16 to 15 left no disagreement. The leap
years are now 2, 5, 7, 10, 13, 15, 18, 21, 24, 26 and 29 (still 10,631
days a cycle).
The count of leap years before a year becomes (4 + 11y) div 30; it
differs from the old (3 + 11y) div 30 only when y = 16 (mod 30), which is
exactly the move from 16 to 15. The inverse's year estimate can now be one
year off at a boundary and is corrected by one step.
Tests: Excel values for 2 March 2005 (1426-01-21) and 29 January 2006
(1426-12-29) plus the 1425/1426 year boundaries they imply, the reviewer's
other anchors, and exhaustive checks: year, month and 30-year cycle
lengths for Hijri years 1 to 10,000; day -> Hijri -> day round trip and
day-by-day succession for every day from 1 Muharram 1 AH to one cycle past
31 December 9999; every 1900-system serial 1 to 2,958,465 (except 60).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12.AR6: match B1/B2 details checked in Excel 16.0 build 20527
Compared GridCraft with Excel 16.0 (build 20527, en-US) through COM: TEXT
with B2dd/mm/yyyy on every serial from 1 to 2,958,465 (no difference), and
about 1,750 format/value pairs through both a cell's NumberFormat and TEXT.
Fixed what differed and is in scope:
- Two-word Hijri month names use a no-break space (U+00A0), as Excel does.
- B1 before a number format is rejected like B2 (B10.00, B1#,##0).
- Prefix runs: a repeated prefix is accepted (B2B2, B2b2, [Red]B2B2, B1B1);
a mixed one (B1B2, B2B1), more digits (B12, B21, B02), a prefix after a
tag or space (B2[Red]B2, B2 B2) and an escaped \B2 are rejected.
- Serial 60 under B2 is #VALUE! for any date or clock part (hh:mm, ss,
AM/PM), not only date parts; [h] and literal-only sections still show.
Regression tests use the Excel values (all 12 month and 7 weekday names,
the accepted and rejected codes, serial 60). Not changed, listed in the
docs: Excel's weekday/month-name mix-ups when ddd(d) and mmm(m) share a B2
section, weekday-only B2 sections (real weekday for serials 0-59, #VALUE!
for the last days of 9999), and Buddhist-year b/bb/bbbb tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* M12.AR12: estimate Arabic text width from measured glyph widths in AutoFit
* M12.AR12: treat U+0897–089F as marks and U+08E2 as visible; size the honorific ligatures (review)
* M12.AR12: size the remaining honorific ligatures like their siblings (review)
U+FD4C-FD4F (Unicode 14) and U+FDC8-FDCF (FDCF Unicode 14, FDC8-FDCE
Unicode 17) are honorific word ligatures, not letters, but fell into
the 0.48 em letter arm. Give them the 1.6 em of the measured
U+FD40-FD4B honorifics and assert each range's endpoints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12.AR12: size Arabic-Indic digits like ASCII digits and the remaining honorific ligatures (review)
Arabic-Indic (U+0660-0669) and Extended Arabic-Indic (U+06F0-06F9)
digits measure 0.53-0.57 em in Noto Sans Arabic, Arial, Tahoma and
Times New Roman, the same as each font's ASCII digits, so they get
the ASCII digit estimate (0.56) instead of the letter estimate.
Arabic punctuation measures 0.17-0.50 em, so it keeps 0.48 rather
than falling back to 1 em.
U+FBC3-FBD2 and U+FD90-FD91 are honorific word ligatures like
U+FD40-FD4F and get the same 1.6 em.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* M12.AR7: sort Arabic text by its letters, not its vowel marks or digit script
* M12.AR7: order marked Arabic text the way Excel does
Checked compare_text against Excel 16.0 build 20527 (en-US) via COM:
Range.Sort, =SORT() and the < / = operators over ~300 strings. Fixes the
mismatches inside this change's scope:
- The tie-break compared raw code points, so an RLM-prefixed word sorted
after every vowelled form of it, "12" after "١٢", and a tatweel
form after a shadda form. Ignorable characters are now skipped at the
tie-break too.
- Marks are compared letter by letter (an unmarked letter first), so a
mark on an earlier letter outranks the digit or letter after it, as in
Excel ("ب١" < "بَ1", "بپ" < "بَپ").
- Marks rank in Excel's measured order (sukun before shadda), and the
Quranic marks Excel skips (U+0610–061A, U+06D8–06ED except 06DD/06DE/
06DF/06E0/06E5/06E6/06E9) are skipped by letter order.
- Excel also ignores bidi embeddings/overrides/isolates, BOM, soft hyphen,
CGJ and word joiner; they now are ignored too (ZWSP is not, as in Excel).
Equality is unchanged. Regression tests use the orders Excel produced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12.AR7: compare the mark level of Arabic text without allocating
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
A string a catalog lacks shows in English. Coverage, key-parity and
per-language count tests and tests of one language's exact wording are
removed; UI wiring tests take their expected text from the catalog.
cargo xtask i18n-report lists the missing strings per language and
always passes; cargo xtask ci runs it first.
Conflicts:
- crates/ui-egui/src/i18n.rs: language registration lines kept from both sides (Language::ALL has 18 entries).
- crates/ui-egui/tests/file_menu.rs: both language rows kept.
- docs/localization-parity.md: one Last updated line naming all changes; both revision rows kept, newest first.
Conflicts:
- crates/ui-egui/src/i18n.rs: language registration lines kept from both sides (Language::ALL has 17 entries).
- ROADMAP.md: one Last updated line naming both changes; both progress-log rows kept.
- docs/localization-parity.md: one Last updated line naming both changes; both revision rows kept.
Conflicts:
- crates/ui-egui/src/i18n.rs: language registration lines kept from both sides (Language::ALL has 16 entries).
- docs/localization-parity.md: both revision rows kept.
Conflicts:
- crates/ui-egui/src/i18n.rs: mod it and mod ja both kept; Language::Ja dispatches to ja::translate; the shape test (PR #831) iterates ja::CORE, which replaces JAPANESE.
- docs/localization-parity.md: both revision rows kept, newest first.
Conflicts:
- contributors/people.toml: both new contributors kept
- crates/ui-egui/src/theme.rs: LINUX_CJK_DIRS has the Flatpak mounts plus the chromeos entry (22 entries)
- docs/localization-parity.md: both status lines, text and history rows kept
The Japanese catalog's ribbon labels the review flagged are already in the head
("Save (⌘S)" is 上書き保存 (⌘S), "Insert\nSlicer" is スライサーの\n挿入, and the
catalog test asserts the new value), so only the docs needed work: docs/gaps.md
had no revision row for the §11 Japanese-catalog update. Add one dated
2026-10-20, and set the "Last updated" header of ROADMAP.md, docs/gaps.md and
docs/localization-parity.md to the same date as their new rows. Every existing
revision row keeps its original date.
si_body returned as soon as no valid run was written, so a shared string with a
<phoneticPr> and no <rPh> — a shape §18.4.8 allows — lost its properties on
save even though the reader keeps them. The runs and the properties are now
written independently: valid runs as before, then the valid <phoneticPr> when
the workbook has one; with neither, the <si> is exactly the plain t_el output.
property_only_shared_string_survives_open_and_save covers read, save and
reopen, and the si_body damaged-data test now expects the property-only output.
The write-time checks the reader also runs (sb/eb clamped into the UTF-16
length, empty readings skipped, type/alignment filtered through the shared
PHONETIC_KINDS/PHONETIC_ALIGNMENTS) were already in the head; the tests above
pin them, so no further change there.
Docs: the .xlsx row and the revision history now state that the shared string
table holds each text once, so duplicate <si> items with the same text but
different readings keep only the first reading (reported as a warning on open),
and that a <phoneticPr> without runs is kept. Both parity docs' "Last updated"
headers match their 2026-10-20 revision rows; the earlier rows keep their dates.
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
Parity doc: state the Dutch coverage precisely (the shared key set the
German, French and Czech catalogs cover, not the larger Portuguese one).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
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.
fr.rs and sr.rs translated three `context|label` keys with the context
kept ("Options|Général", "Opcije|Opšte", "Options de la feuille|Affichage"
...). `Language::tr` shows a found translation as it is, so File › Options
section headings and the Sheet Options "View" checkboxes showed the prefix.
The values now hold only the label.
Tests: `translations_never_show_the_context_prefix` (no catalog shows a `|`
for any key) and `translated_options_headings_show_no_context_prefix`
(File › Options in French and Serbian); both fail on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds Language::Vi (vi), following the per-language module pattern (crates/ui-egui/src/i18n/<lang>.rs). All 398 strings in the master table are covered, checked by the completeness test; vi-VN locale tags resolve to Vietnamese.
egui's bundled fonts have no Lao, and none of the script fallback faces
(CJK, Thai, Hebrew, symbols) carry it on macOS, so Lao text in a cell,
the formula bar or the interface drew as boxes unless the cell's own font
had Lao glyphs. Add a `sys-lao` fallback: Leelawadee UI / DokChampa on
Windows, Lao Sangam MN / Lao MN on macOS, Noto Sans Lao / Phetsarath OT
on Linux. `fallback_covers_its_script` now checks a Lao sample.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The argument hint, autocomplete, Insert Function and the Formulas-tab menus
read per-language tables of function descriptions and argument names
(i18n/<lang>_functions.rs), keyed by the catalog's English text and
binary-searched. The hint is assembled from the formula syntax's function
name and separator plus the language's argument names, all of them or none,
so it never mixes two languages. Insert Function searches the translated
descriptions and shows readable category names. SPANISH_HELP moves to
es_functions.rs with its 10 functions.
Adds Language::It ("it", native name "Italiano") with a new per-language
module crates/ui-egui/src/i18n/it.rs, following the existing pattern
(translate(text) -> Option<&'static str>). The catalog has 971 entries:
every key the most complete catalog (pt) translates, plus the PivotTable
Fields pane strings (from es) and Options|Editing. Labels follow Excel's
Italian terminology (Foglio, Cartella di lavoro, Blocca riquadri, Unisci e
allinea al centro, Somma automatica...). Function names are not touched.
Also: it / it-IT / it_CH tags resolve to Italian (system-language default),
Language::ALL and the coverage/tag/age tests include Italian, and
docs/localization-parity.md lists the new language.
Not compiled locally (no Rust toolchain on the author's machine); relying on CI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>