The problem is bigger than a language button
A TR/EN switch makes an interface readable in two languages. It does not, by itself, decide which language a shared link opens, what the initial HTML contains, or where the contact link on an English project page should lead. Those decisions form the address model of a portfolio. They deserve to be explicit even when the site has only a few page types.
The kaahn.com build defines a path for each page and language. Its Turkish profile lives at /hakkimda/ and its English counterpart at /en/about/. The generator reads those pairs from a central mapping. A language switch can therefore preserve the current subject instead of sending every visitor back to the other language’s home page.
Prepare the content when publishing
The source design for this site also supports a single-page preview. The production build extracts the shared header and footer, selects a page body, applies its translations and writes an HTML file for each address. Titles and descriptions come from the corresponding language record. Browser code can add interaction afterwards; the main text does not need a language-switching script to become readable.
Google describes crawling, rendering and indexing as separate stages for JavaScript pages. The engineering choice here is to prepare relatively stable content in advance, making the published file directly inspectable. This is a description of the implementation, not a measured ranking improvement or a general claim that client-rendered applications are always slower.
Make one route map serve several outputs
This simplified example assigns two paths to one content identity. Navigation, language switches and the sitemap can consume that same record. Independently typing each path into several templates creates more places for an old URL to survive. A shared mapping reduces that risk, while a missing translation should still cause a visible publishing error rather than silently substituting another language.
pages = {
"about": {"tr": "/hakkimda/", "en": "/en/about/"},
"projects": {"tr": "/projeler/", "en": "/en/projects/"},
}
for page, languages in pages.items():
assert set(languages) == {"tr", "en"}
for language, path in languages.items():
assert path.startswith("/") and path.endswith("/")Separate the canonical address from language alternatives
A translated page identifying its preferred address and that page identifying its translation are different relationships. Here, the page record produces both a self-referencing canonical URL and Turkish, English and default-language alternatives. Pointing every English canonical at a Turkish page would conflict with the decision to maintain a distinct English version.
Google’s localized-versions guidance requires reciprocal language relationships. Checking only the Turkish file is therefore insufficient: open both generated files and compare their alternate targets. Also inspect the visible prose. Correct language metadata cannot compensate for a page whose body is accidentally left in the other language.
A release acceptance table
These checks are repeatable publishing criteria, not a search-position forecast. A successful build and Google choosing a page for a query are different events. The production server must also serve the expected file at its public address with an appropriate HTTP status.
| Check | Expected result | Failure caught |
|---|---|---|
| Page with JavaScript disabled | Heading, body and links remain readable | Content exists only in browser state |
| Language pair | Both pages reference each other | Missing or mismatched alternative |
| Internal links | Final address in the intended language | Lost language context or needless redirects |
| Sitemap | Contains published page addresses | Draft or obsolete URLs exposed |
Know when the design stops fitting
Concurrent editors, per-user content and updates that must appear within seconds introduce different requirements. They may justify a content system or runtime rendering. For a small portfolio, a content record, two languages and inspectable static output provide a legible starting point. The first design question is where a page’s identity and language live; choosing another framework comes after that question.