Wire locale catalogs, repair mint terminology, and confirm login with a toast.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
michilis
2026-08-24 22:35:43 +02:00
co-authored by Cursor
parent b95aab2bcd
commit 301679d340
33 changed files with 1570 additions and 963 deletions
+43 -9
View File
@@ -375,8 +375,31 @@ Both gate a deploy. See [Languages](#languages) for what they look for.
## Languages
English, Spanish and Dutch. English is the site as it was; the other two are the same
site, prerendered again.
Twenty-four, listed in `web/src/i18n/config.ts`. English is the site as it was; the other
twenty-three are the same site, prerendered again, 84 pages each.
English, Spanish and Dutch are hand-written. The other twenty-one began as bulk machine
translation, and they are in two states.
**German, Danish, Swedish, Indonesian, Vietnamese and Turkish** have had a full
terminology pass: every string that names a mint, and every label, title and meta
description on the home page, the mint list and a mint page. The machine had translated
the site's central noun into the local word for a coin factory (`Münzprägeanstalt`,
`mincovna`, `darphane`) or, in the Germanic languages, into the *sweet*: Danish and
Swedish shipped `Cashu-pastiller`, "Cashu lozenges", and Swedish offered to sort your
`minttabletter`. Indonesian had `permen`, Vietnamese `cây mint`, the mint plant. Those
six now say `mint`, and `sats`, `ecash` and `Lightning` survive untranslated in them as
the glossary requires.
**The other fifteen** still carry that damage in their body copy, in the same shapes:
Czech `mincovny`, Polish `mennice`, Romanian `monetării`, Greek `νομισματοκοπείο`. Their
chrome, titles and meta descriptions are repaired, so what a reader meets first is right,
but the prose underneath is not. Roughly 400 strings, concentrated in the four inflected
languages where a word-level fix needs case-correct edits inside running sentences, which
is a native speaker's job rather than a careful search and replace.
`web/src/i18n/GLOSSARY.md` is where a pass starts, and the house rules at the top of it
hold in every language, particularly the first row of the table.
### Routing
@@ -404,9 +427,9 @@ opened the English page is not overruled by a browser setting they configured ye
The language control in the header is how you change language, and it lands on the page
you were already reading.
One file per route emits all three: pages live under `web/src/pages/[...locale]/`, and
One file per route emits every locale: pages live under `web/src/pages/[...locale]/`, and
`localePaths()` in `web/src/i18n/paths.ts` is their `getStaticPaths`. There is one copy
of each page's markup, translated by `t()`, rather than three copies to keep in step.
of each page's markup, translated by `t()`, rather than twenty-four to keep in step.
### What is and is not translated
@@ -425,9 +448,10 @@ Never translated, because it is data rather than copy:
A mint's own `description` is the first sentence of its page's meta description, verbatim.
Only when a mint has published none does the site write that sentence itself.
### Adding a fourth language
### Adding a language
Four steps, and the build tells you if you miss one.
Five steps. Four of them the build tells you about; the fifth it cannot, which is why it
has a test of its own.
1. **`web/src/i18n/GLOSSARY.md`** — add the column and decide the terms first. This is
the step people skip, and it is the one that costs later: a reader who meets two words
@@ -453,6 +477,15 @@ Four steps, and the build tells you if you miss one.
TypeScript exists. `check-i18n` compares the two lists and fails if they disagree, so
forgetting this is a build error rather than a mystery.
5. **`web/src/i18n/index.ts`** — `import pt from './pt.json'` and add `pt: pt as Catalog`
to `CATALOGS`. This is the step to get right, because it is the only one nothing
downstream complains about: `catalogFor()` falls back to English by design, so a
locale listed in `LOCALES` with no catalog behind it does not fail the build. It
prerenders the whole English site under the new `lang`, the new URL prefix and a full
reciprocal hreflang set, and tells every crawler those pages are a different language.
Twenty-one locales shipped in exactly that state once. `pnpm test` now fails on it:
see `web/test/i18n-wiring.test.mjs`.
That is the whole change. The Astro i18n config, the routes, the switcher, the hreflang
sets, the `og:locale:alternate` list and the sitemap all read from `LOCALES` and pick the
new language up on the next build.
@@ -505,7 +538,7 @@ English. Islands read it through `useI18n()` in `web/src/i18n/client.ts`.
The alternative, importing `es.json` from an island, does not work here: island chunks
are shared across every locale, so anything imported into one is downloaded by all of
them. A dynamic `import()` keyed on locale would avoid that but costs a round trip on the
critical path of every island and leaves all three catalogs sitting in `dist/_astro/`.
critical path of every island and leaves every catalog sitting in `dist/_astro/`.
Inlining travels in HTML the page was sending anyway.
It is in the body rather than the head because the view transition router replaces the
@@ -658,10 +691,11 @@ below rather than by a test for Cashu.
reviews panel work unchanged), a route in `targetPath` (`web/src/lib/feed-resolve.ts`),
nav entries in `Topbar.astro` and `Footer.astro`, and sitemap entries in
`src/pages/sitemap.xml.ts`.
5. **Copy.** A namespace in `en.json`, `es.json` and `nl.json`, the namespace added to
5. **Copy.** A namespace in `en.json` and in every other catalog, the namespace added to
`CLIENT_NAMESPACES` if an island renders any of it, and a row in
`web/src/i18n/GLOSSARY.md` for every term the ecosystem introduces. `pnpm check:i18n`
fails the build until all three catalogs have the keys.
warns per locale until every catalog has the keys; until then they fall back to
English.
Not in scope, deliberately: **LNURL**. Nothing in the routes, event kinds, schema values
or copy refers to it, and it is planned as a later stage rather than half-built now.