Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
033248d
i18n #30: add Ukrainian interface and language preference
dmatviichuk Oct 9, 2026
6ee9c6a
i18n: add Polish (pl) interface
brzuchal Oct 12, 2026
4571ded
Merge remote-tracking branch 'origin/main' into i18n-pl
brzuchal Oct 12, 2026
13fa90a
Merge remote-tracking branch 'origin/main' into i18n-pl
brzuchal Oct 12, 2026
281d318
Merge remote-tracking branch 'origin/main' into i18n-pl
brzuchal Oct 13, 2026
531c4e6
Merge origin/main into i18n-pl
brzuchal Oct 13, 2026
9a3ca36
Merge origin/main into i18n-pl
brzuchal Oct 13, 2026
6bc4bf6
Merge origin/main into i18n-pl
brzuchal Oct 13, 2026
19481bd
Merge origin/main into i18n-pl
brzuchal Oct 13, 2026
6409f0f
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
92f6d20
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
103a1dc
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
77f7b78
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
bc86423
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
4b2be6c
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
0b6fb86
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
1ee4f04
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
7b1c10b
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
5420781
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
15a8c29
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
452dcd5
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
33d9926
Merge origin/main into i18n-pl
brzuchal Oct 14, 2026
829ca35
Merge origin/main into i18n-pl
brzuchal Oct 15, 2026
4cbbe97
Merge origin/main into i18n-pl
brzuchal Oct 19, 2026
fd601c3
i18n: Cancel restores the interface language; app.language lists pl; …
brzuchal Oct 19, 2026
862e91e
Docs: name Polish next to Ukrainian in the localization summaries
brzuchal Oct 19, 2026
6963b4d
Merge origin/main into i18n-pl
brzuchal Oct 25, 2026
a0703ac
Merge origin/main into i18n-pl
brzuchal Oct 25, 2026
f4a77cd
i18n: tabs, colour controls and checker findings read in the interfac…
brzuchal Oct 25, 2026
3444f33
Merge origin/main into i18n-pl
brzuchal Oct 25, 2026
77dbc49
Merge origin/main into i18n-pl
brzuchal Oct 25, 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
2 changes: 2 additions & 0 deletions ATTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ artwork. The ribbon and menu icons stay original drawings.
| `crates/media/tests/fixtures/damaged/*` (`ebml-header.mkv`, `ebml-position.webm`, `demux-loop.mp4`, `demux-loop.m4a`) | DeckCraft contributors | the `clip-*` and `tone*` fixtures above with bytes changed by a mutation fuzzer | CC0-1.0 | Damaged media must not panic or hang |
| `crates/media/tests/fixtures/damaged/*` (`stsc-count.m4a`, `stsz-size.m4a`) | DeckCraft contributors | `tone.m4a` above with bytes changed by a mutation fuzzer | CC0-1.0 | Damaged MP4 sample tables must not abort |
| `crates/media/tests/fixtures/make.sh` | DeckCraft contributors | this repository | MIT OR Apache-2.0 | Regenerates the media fixtures |
| `crates/ui-egui/src/i18n/uk.tsv` | @dmatviichuk, assisted by Codex | Contributor-original Ukrainian translation in this repository; terminology checked against VESUM 6.8.6 and ULIF (validation tools/data are not bundled) | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | Ukrainian interface text |
| `crates/ui-egui/src/i18n/pl.tsv` | @brzuchal, assisted by Claude | Contributor-original Polish translation written from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | Polish interface text |
| `crates/render/tests/fixtures/colr-squares.ttf` | DeckCraft contributors | two squares as a COLRv0/CPAL test font, built by `make_colr_font.py` (fontTools as the builder only) | CC0-1.0 | Colour glyph rendering tests |
| `crates/fonts/tests/fixtures/legacy-semibold.ttf` | DeckCraft contributors | a one-glyph Semibold face named like Segoe UI Semibold (its Windows family differs from the typographic one), built by `make_legacy_font.py` (fontTools as the builder only) | CC0-1.0 | Font lookup by Windows family name tests |
| `crates/fonts/tests/fixtures/legacy-regular.ttf` | DeckCraft contributors | the same family's one-glyph Regular face, built by `make_legacy_font.py` (fontTools as the builder only) | CC0-1.0 | Font lookup by Windows family name tests |
Expand Down
26 changes: 18 additions & 8 deletions Cargo.lock

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

14 changes: 10 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Stage: alpha** · next: beta, ~20 points and ~120–210 h away

> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-25 · **Change:** minor (Ukrainian and Polish UI catalogs and language selection; previous: trivial, progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Ctrl+wheel zoom during the slide show, #1286; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: picture and embedded object fills; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (fill and outline edits reach group members, #1079; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
Expand Down Expand Up @@ -90,7 +90,11 @@ EMF/SVG pictures, the data-loss and startup-crash bugs (#60, #40, #59) and the b

## Languages

English only today; no string catalog yet (Ukrainian is in review as PR #34). Detail:
English, Ukrainian and Polish: Preferences offers System language, English, Українська and Polski.
Ukrainian and Polish OS/browser locales select those languages automatically; explicit desktop
choices persist in `ui.json`.
Menus, ribbon, command palette, panels, dialogs and presenter controls use the interface catalog.
Presentation content and command IDs retain their original values. Detail:
[docs/localization-parity.md](docs/localization-parity.md).

| Language | Code | UI strings | Status | Hours to full |
Expand All @@ -108,8 +112,8 @@ English only today; no string catalog yet (Ukrainian is in review as PR #34). De
| Korean | ko | 0% | none | 4–7 |
| Vietnamese | vi | 0% | none | 3–5 |

Plus 8–14 h once for the catalog infrastructure. Other languages shipped: none. PowerPoint for Mac
ships 26.
Other languages shipped: **Ukrainian (uk)**, with English fallback and bundled Cyrillic glyph
coverage, and **Polish (pl)**. PowerPoint for Mac ships 26 languages; additional catalogs and native review remain.

## Upcoming

Expand Down Expand Up @@ -282,6 +286,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-12 | Render: line widths, dashes and arrowheads inside a group no longer scale with the group's child-coordinate mapping, so a group in a small legacy child space (scale about 1600) draws thin lines instead of covering the slide (#233) |
| 2026-10-12 | `deckcraft-cli help` and each subcommand's `--help` explain the CLI to an agent; `describe` spells out a command's parameters and what it acts on; `commands` lists one command per line |
| 2026-10-11 | Animated GIF export of the deck: the Export… dialog (Animated GIF), `file.export` format `gif`, CLI and MCP (#171) |
| 2026-10-10 | Ukrainian UI catalog, system-language detection and persisted language choice (#15, #30); translated placeholder prompts preserve presentation content |
| 2026-10-10 | Progress docs restructured to the craftrules standard; full re-measure (breadth 79%, ready ~55%, mainstream ~53%, essentials ~62%, alpha). Landed: Animation Painter (#54), Morph geometry outlines (#27), animated GIF playback (#39), hyperlink colour and following links in the show (#37, #45), date fields in their own format (#36), flipped shapes keep readable text (#35), show transition fix (#46), start screen from the Home button (#49), MCP export lists PDF (#65), OmaStore manifest (#57) |
| 2026-10-09 | v0.4.0 released. UAX #9 bidi and Arabic shaping (#4), Morph with Words and Characters (#5), tables grow to fit and PDF cell text (#14), rotating log file (#7), DX12 default on Windows (#16), H.264/HEVC overflow hardening (#2), PPTX sniffing anywhere in the zip (#19) |
| 2026-10-08 | v0.2.0 and v0.3.0 released: Windows arm64, Flatpak aarch64, AppImage zsync updates, branded DMG |
Expand Down Expand Up @@ -379,6 +384,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-12 | trivial | Progress log: File tab (Backstage, #76) |
| 2026-10-12 | trivial | Progress log: lines in scaled groups (#233) |
| 2026-10-11 | minor | Progress log: animated GIF export (#171); P2 breadth 36% → 37%, overall breadth stays 79% |
| 2026-10-10 | minor | Added Ukrainian UI and language selection to the reorganized language summary |
| 2026-10-10 | minor | Readiness-by-audience table with hours to ~95% for full, mainstream and essentials |
| 2026-10-10 | minor | Added mainstream practitioner (~53%) and essentials user (~62%); features recomputed from written area weights, ready ~56% → ~55% |
| 2026-10-10 | minor | Stage checked against the new core-workflow gate: passes, stays alpha |
Expand Down
2 changes: 1 addition & 1 deletion apps/deckcraft-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ rfd = { workspace = true }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] }
web-sys = { version = "0.3", features = ["Window", "Navigator", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] }
9 changes: 5 additions & 4 deletions apps/deckcraft-web/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub fn start() {
deckcraft_ui_egui::install_ime_pass_hooks(&cc.egui_ctx);
let inbox: Inbox = Inbox::default();
let mut app = SlideApp::new(Session::new(), services(inbox.clone(), cc.egui_ctx.clone()));
app.services.system_locale = web_sys::window().and_then(|w| w.navigator().language());
let q = query();
if !q.contains("blank") {
if let Err(e) = deckcraft_engine::sample::open_sample(&mut app.session) {
Expand Down Expand Up @@ -123,10 +124,10 @@ fn services(inbox: Inbox, ctx: egui::Context) -> Services {
let inbox = open_inbox.clone();
let ctx = ctx.clone();
let dialog = match purpose {
"picture" => rfd::AsyncFileDialog::new().add_filter("Pictures", PICTURE_EXTS),
"audio" => rfd::AsyncFileDialog::new().add_filter("Audio", AUDIO_EXTS),
"video" => rfd::AsyncFileDialog::new().add_filter("Video", VIDEO_EXTS),
_ => rfd::AsyncFileDialog::new().add_filter("Presentations", PRESENTATION_EXTS),
"picture" => rfd::AsyncFileDialog::new().add_filter(deckcraft_ui_egui::i18n::tr("Pictures"), PICTURE_EXTS),
"audio" => rfd::AsyncFileDialog::new().add_filter(deckcraft_ui_egui::i18n::tr("Audio"), AUDIO_EXTS),
"video" => rfd::AsyncFileDialog::new().add_filter(deckcraft_ui_egui::i18n::tr("Video"), VIDEO_EXTS),
_ => rfd::AsyncFileDialog::new().add_filter(deckcraft_ui_egui::i18n::tr("Presentations"), PRESENTATION_EXTS),
};
wasm_bindgen_futures::spawn_local(async move {
let Some(file) = dialog.pick_file().await else {
Expand Down
1 change: 1 addition & 0 deletions apps/deckcraft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ default-run = "deckcraft"
workspace = true

[dependencies]
sys-locale = "0.3.2"
deckcraft-engine = { workspace = true }
deckcraft-ui-egui = { workspace = true }
deckcraft-media = { workspace = true }
Expand Down
18 changes: 11 additions & 7 deletions apps/deckcraft/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,24 @@ fn save_prefs(app: &SlideApp) {

fn services() -> Services {
Services {
system_locale: sys_locale::get_locale(),
pick_open: Some(Box::new(|purpose: &str| {
let d = rfd::FileDialog::new();
let d = match purpose {
"picture" => d.add_filter("Pictures", &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff"]),
"picture" => d.add_filter(deckcraft_ui_egui::i18n::tr("Pictures"), &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff"]),
"audio" => d.add_filter(
"Audio",
deckcraft_ui_egui::i18n::tr("Audio"),
&["wav", "mp3", "m4a", "m4b", "aac", "flac", "ogg", "oga", "opus", "aif", "aiff", "aifc", "caf", "wma", "weba", "mka"],
),
"video" => d.add_filter("Video", &["mp4", "m4v", "mov", "webm", "mkv", "wmv"]),
"theme" => d.add_filter("Themes and presentations", &["thmx", "pptx", "potx", "ppsx"]).add_filter("Office Theme", &["thmx"]),
"video" => d.add_filter(deckcraft_ui_egui::i18n::tr("Video"), &["mp4", "m4v", "mov", "webm", "mkv", "wmv"]),
"theme" => d
.add_filter(deckcraft_ui_egui::i18n::tr("Themes and presentations"), &["thmx", "pptx", "potx", "ppsx"])
.add_filter(deckcraft_ui_egui::i18n::tr("Office Theme"), &["thmx"]),
_ => d
.add_filter("Presentations", &["deckcraft", "pptx", "potx", "ppsx"])
.add_filter(deckcraft_ui_egui::i18n::tr("Presentations"), &["deckcraft", "pptx", "potx", "ppsx"])
.add_filter("DeckCraft", &["deckcraft"])
.add_filter("PowerPoint", &["pptx", "potx", "ppsx"])
.add_filter("Outline", &["txt", "md"]),
.add_filter(deckcraft_ui_egui::i18n::tr("Outline"), &["txt", "md"]),
};
d.pick_file().map(|p| p.to_string_lossy().to_string())
})),
Expand Down Expand Up @@ -275,7 +278,8 @@ fn main() -> eframe::Result {
if let Ok(v) = app.session.execute("file.recovery.open", &serde_json::json!({}))
&& let Some(n) = v.get("opened").and_then(|o| o.as_array()).map(Vec::len).filter(|n| *n > 0)
{
app.set_status(format!("Recovered {n} unsaved presentation{} from the last session", if n == 1 { "" } else { "s" }));
deckcraft_ui_egui::i18n::set_current(app.ui.interface_language.resolve(app.services.system_locale.as_deref()));
app.set_status(deckcraft_ui_egui::i18n::recovered(n));
}
}
app.integrated_titlebar = cfg!(target_os = "macos");
Expand Down
9 changes: 6 additions & 3 deletions crates/render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ pub struct RenderOpts<'a> {
pub scale: f64,
/// Editor view: placeholder prompts and dashed placeholder outlines.
pub edit: bool,
/// Optional interface prompt provider; custom layout prompts and document text stay intact.
pub placeholder_prompt: Option<fn(PhType) -> &'static str>,
/// Draw shapes marked hidden (Selection pane) — the editor hides them like the show does.
pub show_hidden: bool,
/// Per-shape animation state.
Expand Down Expand Up @@ -250,6 +252,7 @@ impl Default for RenderOpts<'_> {
RenderOpts {
scale: 1.0,
edit: false,
placeholder_prompt: None,
show_hidden: false,
state: None,
skip: &[],
Expand Down Expand Up @@ -1421,7 +1424,7 @@ impl Renderer {
}
} else if f.opts.edit && s.ph.is_some() {
let kind = s.ph_type().unwrap_or(PhType::Body);
let prompt_body = prompt_body(rctx, s, kind);
let prompt_body = prompt_body(rctx, s, kind, f.opts.placeholder_prompt.map(|prompt| prompt(kind)));
if !prompt_body.is_empty() {
let grey = Rgba::rgb(0x59, 0x59, 0x59);
draw_text(ctx, rctx, s, &prompt_body, tr, m, &f.fields, Some(grey));
Expand Down Expand Up @@ -2088,7 +2091,7 @@ fn arrowheads(ctx: &mut RenderContext, rctx: &Ctx, line: &Line, ph: Option<Rgba>
}

/// The prompt shown in an empty placeholder, formatted like its content would be.
fn prompt_body(rctx: &Ctx, s: &Shape, kind: PhType) -> TextBody {
fn prompt_body(rctx: &Ctx, s: &Shape, kind: PhType, interface_prompt: Option<&str>) -> TextBody {
// Layout/master prompt text when the layout supplies a custom one.
let (lp, _) = resolve::parents(rctx, s);
let custom = lp.and_then(|l| l.ph.as_ref().filter(|p| p.has_custom_prompt).and(l.text.as_ref())).filter(|t| !t.is_empty());
Expand All @@ -2098,7 +2101,7 @@ fn prompt_body(rctx: &Ctx, s: &Shape, kind: PhType) -> TextBody {
if matches!(rctx.owner, resolve::Owner::Layout | resolve::Owner::Master) {
return TextBody::default();
}
let txt = kind.prompt();
let txt = interface_prompt.unwrap_or_else(|| kind.prompt());
if txt.is_empty() || kind.is_footer_kind() {
return TextBody::default();
}
Expand Down
Loading