My portfolio is built with Astro and is fully static — no server-side rendering, no background API. The problem: when someone from Czechia opens the site, they get the English version. When someone from abroad opens it, they get Czech. Both are wrong.
The fix had to be simple — detect language automatically and redirect, but only once and without any visible flicker.
How it works
The entire detection runs as an inline <script> right in <head>, before the page renders:
-
Manual choice always wins. If the user clicked the language switcher, the selection is stored in
localStorage. This value always takes priority — we never override it. -
One-shot redirect.
sessionStoragetracks whether we already redirected in this session. If yes, we do nothing — no ping-pong loops. -
Detection via timezone.
Intl.DateTimeFormat().resolvedOptions().timeZonegives us a free geo hint — if the user is in Prague, they probably want Czech. No geolocation API, no third-party service, zero cost. -
Fallback: browser language. If not in Prague, we check
navigator.languages. If it contains “cs”, we treat the user as Czech. -
Redirect via
location.replace(). Notassign()—replace()doesn’t add a history entry, so the back button works correctly.
Why this approach
The alternative would be server-side detection (Cloudflare Workers, nginx rules, etc.), but that means extra config, another thing that can break, and potential SEO issues if the redirect isn’t done properly.
This solution:
- Costs $0 — no API, no infrastructure
- Runs before paint — no content flash in the wrong language
- SEO-safe — bots don’t execute JS, so they see the original page normally
- Respects the user — manual language choice is always sacred
The language switcher
When a user clicks the switcher, the choice is saved to localStorage under dragocz:lang alongside the standard redirect. On the next visit, auto-detect reads this value and keeps the user’s pick — even if they’re in a different country.
Three commits, clean result
Implementation ended up being three small commits: switcher saving the choice, inline detection script, and build verification. Under 50 lines of JavaScript total. No dependencies, no frameworks — just one script in the layout and one event listener on the switcher.
On a static site, this works surprisingly well. Clean, fast, zero maintenance.