Internationalization
Not a UI-only rule — it covers the capture extension and server-side notification text too.
ui/ and extension/ are each fully internationalized with Paraglide JS (EN + DE), with separate catalogs, separate compiled output, and separate gates. Never hardcode user-facing text. Every display string — labels, buttons, placeholders, title/aria-label, empty/error/loading states, toast text, and server-side notification payloads — must route through a message.
Write to the catalog of the package you are editing — they are not shared, and a key added to the wrong one simply never resolves:
| Editing | Catalogs (add the key to both locales) | Import | Gate |
|---|---|---|---|
ui/src/** |
ui/messages/en.json + de.json |
import { m } from "$lib/paraglide/messages" |
cd ui && bun run check:i18n |
extension/src/** |
extension/messages/en.json + de.json |
relative, e.g. import { m } from "../lib/paraglide/messages" |
cd extension && bun run check:i18n |
Then: m.my_key() / m.my_key({ count }). Keys are snake_case and component-prefixed (viewport_diff_tab, broadcast_failed, prbadge_open); use {param} for interpolation. Reuse an existing key where one fits (e.g. common_close, common_loading).
Data passed through verbatim (tool-use summaries, PR titles, designations like TASK-07) is not translated — only chrome the app itself authors.
Server-side notification text (no gate covers this)
Section titled “Server-side notification text (no gate covers this)”Push notification strings live in the locale-keyed NOTIFY_TEXT table (en + de) in src/push.ts, read by blockSummary() and buildPayload(); src/ready-notify.ts composes the NotifyInput that reaches them. A new notification kind or reason adds its strings to NOTIFY_TEXT, under every locale — never as a literal at the call site.
This table is deliberately separate from the Paraglide catalog: the server package can’t reach ui/’s compiled messages. Don’t try to import them here — keep EN+DE in sync by hand.
Each check:i18n resolves its message directory to its own package’s messages/, so neither gate can see this table. Nothing will fail if you hardcode here; this rule and review are the only enforcement.
Run the one for the package you touched (both, if the change spans them) — see the table above. Each asserts that its package’s locale catalogs share an identical, non-empty key set. Paraglide silently falls back to EN for a missing key, so an incomplete de.json would otherwise ship looking fine.
Neither gate detects hardcoded strings that skip the catalog entirely, and neither reads the other package’s catalog; that’s on you and review.