Running a bilingual site through the address bar
There's an easy but wrong way to build a multilingual site: keep the language in browser storage and swap the text when a button is pressed. It looks like it works, and then these problems show up:
- The link you send a friend opens in their language, not the one you were reading.
- Google can't see two languages as two pages; it sees one address with one piece of content.
- On first load nobody knows which language wins, so you get a flicker.
The right way: make language part of the URL
/tr/blog/hello
/en/blog/hello
These are now two separate pages. Shareable, bookmarkable, indexable. In Next.js we set this up by turning a folder name into a variable: src/app/[locale]/.... Whatever comes first in the address arrives as the locale parameter.
Dictionaries
We don't write copy inside components; we collect it in two JSON files:
src/i18n/dictionaries/tr.json
src/i18n/dictionaries/en.json
Only the relevant dictionary gets loaded:
const dictionaries = {
tr: () => import("./dictionaries/tr.json").then((m) => m.default),
en: () => import("./dictionaries/en.json").then((m) => m.default),
};
That import() call is dynamic. When a visitor opens /en, tr.json is never loaded. The more languages you add, the more this matters.
But what if someone just types sitename.com?
That address carries no language. This is where middleware steps in — a layer that runs before the request ever reaches a page:
- Does the path already start with
/tror/en? Then leave it alone. - If not, is there a previously chosen language in a cookie? Send them there.
- If not, read the browser's
Accept-Languageheader and pick the best match. - Redirect to the right address.
The result: a visitor never sits on a language-less URL, and once they choose, the choice is remembered.
Telling machines which language this is
Two small but important details:
<html lang="en">— so screen readers pronounce the text correctly.alternates.languages— telling Google "the Turkish version of this page lives here". The two pages are then treated as translations of each other, not duplicates.