Skip to content

Translate the window into Omarchy's languages - #57

Merged
dhh merged 4 commits into
masterfrom
translations
Nov 12, 2026
Merged

dhh merged 4 commits into
masterfrom
translations

Conversation

@omarchybot

@omarchybot omarchybot commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Hype was the one Omarchy app whose window stayed in English whatever language was picked under Setup > Language: it had no Translator, no string was marked for translation, and there was no catalog for anything to fill. This gives it the same setup folio and signed have.

  • Translator. src/translator.{h,cpp} is the copy every Omarchy app carries, byte for byte, from the omarchy-app skill's templates. main.cpp installs it for the window, and for the --export-snapshot worker the window starts, so an export's progress and errors arrive in the window's language. The commands (hype check, render, export, new, ...) and command-line exports (hype file.md --pdf x.pdf) never install it: their output is read by scripts and agents, so it stays English, and hype_commands passes unchanged.
  • Strings. Every string the window shows goes through qsTr() or tr(): the toolbar, menus, dialogs, the shortcut overlay, the presenter view, and the status line's messages from Deck, recovery, the file chooser, export and the renderer's slide problems. Sentences that were glued together ("Slide " + n + " of " + count, "Cannot " + operation + ": " + ..., the export progress stages) became whole sentences with %1 placeholders, so a translation can reorder them; "N slides selected" is a counted string. The Place menu, which built its labels from position ids, names them from a table. The close dialog has its own Discard and Cancel buttons rather than Qt's standard ones, whose text Qt sets once from its own catalogs: about twenty of Omarchy's languages have none, and a live switch would leave the old words behind. Qt's right-click menu on the text fields asks for Cut, Copy, Paste and Select All through qsTr, so those are listed for the catalogs too, as folio does. A new presentation's starter headline is translated too, since it becomes the author's text. Key names, the app name, images/ and videos/ stay as they are. English reads as before, except that export progress now says "Converting the video on slide 2 of 5" where the old gluing produced "Converting video on slide 2 of 5".
  • Live switching. QML redoes its qsTr() bindings when the language changes, but text C++ makes and keeps would stay in the old language: parse errors are cached by slide source, the untitled name is read only when the deck changes, and thumbnails are cached by render id. The caches and render ids now carry a translation generation that a switch advances, and Deck::retranslate makes the deck's error, title and banners again, repeating whichever check found the error, since a slide edited within known boundaries may begin with --- that only those boundaries explain. A status message already on screen at the moment of a switch finishes its few seconds in the old language rather than being flashed again.
  • Catalogs. src/i18n/en.json lists the 250 strings, and the 49 language catalogs exist empty, all compiled in through resources.qrc. They exist up front because the sync may only modify catalogs that are already here. Until a catalog fills, its strings show in English.
  • Sync. scripts/i18n-sync.py and .github/workflows/i18n-sync.yml are signed's, from Translate the window into Omarchy's languages automatically signed#18, with master for the branch, Hype's brief for Muse, and Hype's Arch packages for the catalog tests, listed in the workflow rather than read from omarchy-pkgs. Like folio's and signed's, its land job merges its own "Update translations" pull request after checking that the patch only modifies existing catalogs, never en.json. omacom/hype already has the MUSE_API_KEY and I18N_SYNC_TOKEN organization secrets, so the first run after this merges fills the catalogs.
  • Tests. signed's catalog and translator tests come over: englishCatalogMatchesTheSources fails when a translated string is missing from en.json or a stale one is left in it, and the catalog tests check placeholders, plural forms, language codes and that every catalog is compiled in. The translator tests put back the default locale and layout direction they change, since Hype's tests share one process. windowFollowsTheLanguage loads the window in Danish, checks the close dialog's buttons through a live switch to Vietnamese and that Discard still closes the window, then switches back to English, and cachedTextFollowsTheLanguage switches a deck with a cached parse error and an untitled name to Danish. slidesRenderTheSameRightToLeft draws headlines, lists, a table, code, a quote and a placed image under both layout directions and requires identical pixels: the Translator turns the whole application right to left for Arabic, Hebrew, Persian and Urdu, and a slide is the author's, so it must not move. It does fail when the renderer's text follows the application's direction.
  • docs/i18n.md describes the setup, and the README mentions it.

Not in this pull request: for the right-to-left languages the window's layout isn't mirrored yet. Controls that follow the locale flip on their own while the layouts around them don't, as in signed today. Mirroring Hype's editor has to keep the slide stage and its placed media unmirrored, so it comes separately and is checked on a worker. The .desktop file's Name[…] and Comment[…] live in omarchy-pkgs and aren't generated from the catalogs.

Reviewed by Codex XHigh as a second opinion, once over the whole change and once over the caching it led to; its independence isn't guaranteed, since the reviewer can read this session's files. It contributed the missed glued messages, the stale caches and title, and the boundary case above, all fixed and covered by cachedTextFollowsTheLanguage, which fails if the generation is dropped from either cache or the render id, or if the deck always reparses the whole document. The fix for that boundary case, and the close dialog's buttons (which Greptile pointed out), were checked by the tests named here rather than by another codex review. It found nothing wrong with the command line, exports, locale handling, the workflow's jobs and trust boundaries, or test isolation. The workflow's validation step was run as written in a fresh Arch container. Nothing here was looked at on a desktop: English draws as before apart from the one progress message above, and no other language has text until the first sync.

🤖 Generated by Opus 5.5 in Claude Code. Reviewed by Codex XHigh.

omarchybot and others added 3 commits November 12, 2026 12:10
Hype takes the shared Translator every Omarchy app carries, marks every string the window shows for translation, lists them in src/i18n/en.json beside 49 empty catalogs, and gets folio's translation sync, by way of signed's, to fill them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex found three messages still glued in English, parse errors cached by source alone so they kept the old language, and an untitled title that QML's retranslation never re-reads. The parse caches and slide render ids now carry a translation generation, Deck::retranslate redoes the deck's error, title and banners on a switch, and the starter headline of a new presentation is translated too.

Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alone

A slide edited within known boundaries may begin with --- that only those boundaries explain, so reparsing the whole document on a language switch turned "Unclosed code fence" into "Front matter needs a closing ---". The deck now remembers which check produced its error and repeats that one. It no longer re-sets the status on a switch: the status line is a flash of a few seconds, and setting it again would show an error that had already gone. The test now reaches the per-slide cache through slide editing and undo, and the thumbnail's render id.

Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect Hype’s window and the workflow that updates its language catalogs.

The PR still needs the previously reported slide-direction fix before merging.

Findings

  1. P1 Slides follow the desktop direction ▶

Summary

Hype adds live window translations, compiled language catalogs, and an automated translation sync.

  • Hype’s window follows the selected language and refreshes translated messages live.
  • A sync workflow fills language catalogs and merges their updates.

Reviews (2) · Last reviewed commit: "Translate the close dialog's buttons and..." · Reviewed by Greptile

Comment thread src/translator.cpp
// too, and right-to-left languages mirror the window.
const QLocale locale = formatLocale(language);
QLocale::setDefault(locale);
QGuiApplication::setLayoutDirection(locale.textDirection());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Slides follow the desktop direction

Translator::reload() now changes the application's layout direction, but sizeSlideText() creates a QTextOption without setting its text direction. That option inherits the application's direction. On Arabic, Hebrew, Persian, or Urdu desktops, slide lists and tables can therefore draw differently from the same presentation on an English desktop.

Set an explicit direction for slide text that does not depend on the desktop. The new slidesRenderTheSameRightToLeft test covers this path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked, and this does not happen: in Qt 6 a QTextOption defaults to Qt::LayoutDirectionAuto, and a QPainter on a slide image starts at LayoutDirectionAuto too, whatever QGuiApplication::layoutDirection() is, so each paragraph takes its direction from its own text. slidesRenderTheSameRightToLeft renders a headline, a list, a table, code, a quote and a placed image with the application set left to right and then right to left and requires identical pixels; it passes, and it fails if sizeSlideText is changed to option.setTextDirection(QGuiApplication::layoutDirection()), which is the case described here. No change. Checked by Opus 5.5; not reviewed by a second model.

Comment thread src/Main.qml Resolved
The close dialog took Qt's standard Discard and Cancel, whose text Qt sets once from its own catalogs: about twenty of Omarchy's languages have none, and a live switch left the old words behind. The dialog now has its own buttons, translated like the rest of the window. Qt's right-click menu on the text fields asks for Cut, Copy, Paste and Select All through qsTr, so they are listed for the catalogs too, as folio does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dhh
dhh merged commit 527e362 into master Nov 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants