Skip to content

feat(i18n): carry the download page's language into the app - #124

Merged
meocong merged 1 commit into
masterfrom
feat/language-handoff
Aug 21, 2026
Merged

feat(i18n): carry the download page's language into the app#124
meocong merged 1 commit into
masterfrom
feat/language-handoff

Conversation

@meocong

@meocong meocong commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Asked for: download from the Vietnamese page → the app opens in Vietnamese, and the same for every other language.

Why it needs a link rather than something automatic

The app already picks its language from the OS, and that's the better signal almost always — somebody on a Vietnamese Mac reading the English page still wants a Vietnamese app. It's wrong for exactly one person: whoever reads this page's language on a machine set to a different one.

There is no automatic channel for that. I checked:

  • macOS records the origin URL (kMDItemWhereFroms) on the .dmg; dragging Summo.app out doesn't carry it.
  • Windows keeps Zone.Identifier on the .msi, not on what it installs.
  • Both language pages hand out the same binary from the same GitHub release.
  • The interface locale lives in the webview's localStorage, not ~/.summo — deliberately, per i18n/context.tsx: it's per-machine and must be readable before the daemon handshake. So there's no settings file an installer could seed either.

Hence an explicit handoff: the download page offers summo://lang/vi (or /en) below the button, where it's meaningful because something has just been installed.

Safety

language_from treats the URL as what it is — an input surface the OS hands this process from anywhere, including a page nobody here wrote. It answers lang/<code> and nothing else, and unit tests cover traversals, script, whitespace, summo://open/~/.summo/vault, and a 64-character code.

single-instance ships with it because the two are one feature: without it, clicking the link while Summo runs starts a second shell — second window, two processes racing for the same vault. Both arrival states are handled: on_open_url for a running app, get_current for the cold start, which otherwise works only on the second click.

Three rules, and the last two matter most

Case Result
First run, English machine, link from the VI page Vietnamese
User already chose Japanese stays Japanese — a web page doesn't undo a real preference
Link asks for ko, which we don't ship ignored, not English — the OS locale had already chosen better

The gap this exposed

Both halves were untested. languages.mjs wrote summo.locale before every load, so it proved each language renders and never touched the state every install passes through exactly once: a first run with nothing saved. Nobody developing this ever sees it again after their first launch.

It now covers five system locales — including ko-KR, which has no catalogue and must land on English — and asserts detection does not write a choice. If it did, the handoff and any later change of system language would both be outranked by a preference nobody set.

first run vi-VN → vi      handoff en-GB saved=null + lang/vi → vi
first run ja-JP → ja      handoff en-GB saved=ja   + lang/vi → ja
first run zh-CN → zh      handoff vi-VN saved=null + lang/ko → vi
first run en-GB → en
first run ko-KR → en

Not changed

Setup step 1 already asks for the interface language, first, pre-filled from the OS. It didn't need work, so I left it alone.

Verification

cargo fmt, clippy -D warnings, cargo test (5 pass) in apps/desktop/src-tauri — note that crate is a separate workspace, so the root cargo fmt --all does not cover it and reported clean while it was not. Plus tsc, lint, prettier --check, 441 unit tests, and the full languages.mjs suite against a rebuilt binary.

🤖 Generated with Claude Code

Summo picks its interface language from the operating system, and that is
the right signal almost always — somebody on a Vietnamese Mac reading the
English page still wants a Vietnamese app. It is wrong for exactly one
person: whoever reads this page's language on a machine set to another
one. The only evidence of them is the page they were standing on.

Nothing about a downloaded file survives installation in a form the app
can read. macOS records the origin URL on the .dmg and drops it when the
app is dragged out; Windows keeps it on the .msi rather than on what it
installs; and both language pages hand out the same binary from the same
GitHub release. So the handoff is an explicit link instead: the download
page offers `summo://lang/vi` (or `/en`) below the button, and the shell
turns it into the language the interface opens in.

`language_from` treats the URL as what it is — an input surface the OS
hands this process from anywhere, including a page nobody here wrote. It
answers `lang/<code>` and nothing else, checks the code is a short
language tag, and is unit-tested against traversals, script, whitespace,
invented verbs and a 64-character code.

single-instance ships with it because the two are one feature: without it,
clicking the link while Summo is running starts a second shell, with a
second window and two processes racing for the same vault. Both arrival
states are handled — `on_open_url` for a running app, `get_current` for
the cold start where the link is what launched it, which is the case that
would otherwise work only on the second click.

The offer never overrides a choice. A saved locale means the user picked
one, in settings or in setup, and a web page does not get to undo that —
otherwise revisiting the download page silently re-languages an app
somebody has used for a month. It applies only when nothing is saved, and
an unshipped code is ignored rather than fallen back on, since falling
back would put English on a first run whose OS locale had chosen better.

Both halves were untested. `languages.mjs` set `summo.locale` before every
load, so it proved each language renders and never touched the state every
install passes through exactly once: a first run with nothing saved. It now
covers five system locales including one Summo ships no catalogue for, and
asserts detection does not *write* a choice — if it did, the handoff and a
later change of system language would both be outranked by a preference
nobody set.
@meocong
meocong merged commit e315393 into master Aug 21, 2026
12 checks passed
@meocong
meocong deleted the feat/language-handoff branch August 21, 2026 15:24
@meocong meocong mentioned this pull request Aug 21, 2026
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.

1 participant