Skip to content

The public page in four languages, at four addresses - #127

Merged
suskozaver merged 1 commit into
ArunNGun:betafrom
suskozaver:feat/the-public-page-speaks-four-languages
Sep 16, 2026
Merged

suskozaver merged 1 commit into
ArunNGun:betafrom
suskozaver:feat/the-public-page-speaks-four-languages

Conversation

@suskozaver

Copy link
Copy Markdown
Collaborator

Every screen inside the app has been translated for a while. The one page a stranger actually arrives at was still English, and the reason recorded in 05-decisions.md was sound: /landing renders on the server, before any browser has said which language it wants, so useLang has nothing to read. Making it a client component would have translated it and cost the thing it is for, which is arriving instantly on a phone that has never opened the app.

So the language comes from the URL instead.

The addresses

English keeps /landing. The rest live at /landing/de, /landing/sl and /landing/pl.

English does not move to /landing/en: every link to this page that exists in the world points at /landing, and a second URL holding the same page is a duplicate a search engine has to be told to ignore.

All four are prerendered, carry a canonical pointing at themselves, and name each other in hreflang, with x-default on English. The paths are relative, because this app is self-hosted by whoever wants it and there is no one absolute origin to write down. A wrong one would be worse than none.

dynamicParams = false, so /landing/fr is a 404 rather than an attempt to render a language the dictionary does not carry. That is also what the static export needs: a folder of files has no server to render an unknown locale on demand.

Checked in the output, not in the config

Both build modes were built and the emitted HTML read:

  • server build: /landing, /landing/de, /landing/sl, /landing/pl prerendered
  • BUILD_TARGET=static: out/landing/sl/index.html exists, says <link rel="canonical" href="/landing/sl">, carries all five alternate links, and opens with <main lang="sl">

Where lang sits

On <main>, not on <html>. The root layout owns <html> and is shared by the whole app, so varying it would mean two root layouts and a route group around every other page. A screen reader switches voice at the element carrying lang, which is what the attribute is for.

Shape

The page body moved into Landing.tsx, which takes the language as a prop. The two route files are what is left: eight lines and a metadata call each.

Still English on purpose

The app's own metadata in layout.tsx. It titles /, /plan, /stock and the rest, which are client screens whose language lives in a store in the browser. Routing those would mean a locale segment through the entire app, for a title nobody links to. Written up in 05-decisions.md along with what the old entry said and why the wait was right.

Sixty-eight keys in four languages.

Checks

npx tsc --noEmit, npx next lint, npm test and TZ=America/New_York npm test: 63 files, 1643 tests, no warnings. Plus both production builds.

Every screen inside the app has been translated for a while. The one
page a stranger actually arrives at was still English, and the reason
recorded in 05-decisions.md was sound: /landing renders on the server,
before any browser has said which language it wants, so useLang has
nothing to read. Making it a client component would have translated it
and cost the thing it is for, which is arriving instantly on a phone
that has never opened the app.

So the language comes from the URL instead. English keeps /landing and
the rest live at /landing/de, /landing/sl and /landing/pl. English does
not move to /landing/en: every link to this page that exists points at
/landing, and a second URL holding the same page is a duplicate a
search engine has to be told to ignore.

All four are prerendered, carry a canonical pointing at themselves, and
name each other in hreflang, with x-default on English. The paths are
relative because this app is self-hosted by whoever wants it and there
is no one absolute origin to write down; a wrong one would be worse
than none. Checked by reading the emitted HTML in both build modes, the
server build and BUILD_TARGET=static, rather than by trusting the
config: /landing/sl/index.html exists, says canonical /landing/sl, and
carries all five alternate links.

dynamicParams is false, so /landing/fr is a 404 rather than an attempt
to render a language the dictionary does not carry. That is also what
the static export needs, since a folder of files has no server to
render an unknown locale on demand.

lang sits on <main> rather than on <html>. The root layout owns <html>
and is shared by the whole app, so varying it would mean two root
layouts and a route group around every other page. A screen reader
switches voice at the element carrying lang, which is what the
attribute is for.

The page body moved into Landing.tsx, which takes the language as a
prop. The two route files are what is left: eight lines and a metadata
call each.

The app's own metadata in layout.tsx is still English, and that is not
an oversight. It titles /, /plan, /stock and the rest, which are client
screens whose language lives in a store in the browser. Routing those
would mean a locale segment through the whole app for a title nobody
links to. Written into 05-decisions.md, along with what the old entry
said and why the wait was right.

Sixty-eight keys in four languages.
@vercel

vercel Bot commented Sep 16, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Arun's projects Team on Vercel.

A member of the Team first needs to authorize it.

@suskozaver
suskozaver merged commit bf8b03f into ArunNGun:beta Sep 16, 2026
1 of 2 checks passed
@suskozaver
suskozaver deleted the feat/the-public-page-speaks-four-languages branch September 16, 2026 17:03
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