Check if it fits Tell us what keeps taking extra work or getting delayed. We will check what, if anything, makes sense next, at no cost.
← All posts

Simple Internationalization: Language Management That Works

I simplified multilanguage for two realities: German readers in Germany and everyone else. The first version shipped but was overengineered. I cut it down to clear locale paths, a one-time detector that respects preference and browser settings, a switcher that keeps context, and a clean split between interface strings and long-form content. Predictable for people. Indexable for search.

By Mirko Imperio Trotta

Photorealistic 3D rendering of a bilingual website system with glowing browser windows labeled /en and /de, connected by a data stream

Summary

I set multilanguage in my website for two realities: German readers in Germany and everyone else. The first version shipped but was overengineered. I cut it down to clear locale paths, a one-time detector that respects preference and browser settings, a switcher that keeps context, and a clean split between interface strings and long-form content. Predictable for people. Indexable for search.

Context

Audience split: a growing base in Germany and a steady flow of English readers. The site had to feel local in Germany without trapping English speakers. Search engines needed clean alternates, not duplicates.

Problems

Problem A — Multilanguage and “place”

Support at least DE and EN. Make “place” practical: default to German in Germany, keep English close, and never surprise the user.

Problem B — Overengineered first build

It worked, but it was too clever: hidden fallbacks, too many flags, behavior I had to re-read. Complexity leaked into UX, SEO, and editorial flow.

Constraints and goals

  • Clear URLs for trust and bilingual SEO.
  • One polite decision on first visit, then remember it.
  • Switching language must keep the current page.
  • Editors must publish translations without a code deploy.
  • Architecture small enough to explain in one minute.

Approach

I paused and applied basics from Clean Code (Robert C. Martin):

  • “Functions should do one thing.”
  • “Meaningful names.”
  • “Leave the code better than you found it.”

No rewrite. Subtraction. I removed hidden rules, named decisions plainly, and wrote the behavior down.

Solution overview

  • Locale paths: every page lives at /de/... and /en/....
  • One-time detector: saved preference → browser Accept-Language → default = DE. Decide once, stop guessing.
  • Context-preserving switcher: change language and stay on the same route; preference is sticky.
  • Two translation layers: interface strings in small JSON files; articles and rich content in a CMS with EN/DE counterparts.
  • Search alignment: hreflang between versions and a sitemap that lists both locales.

How it works (user flow)

Arrive at / → if a language cookie exists, use it → else read Accept-Language and pick the first supported locale → else serve DE → user can switch any time → choice is saved for next visits.

Implementation notes (non-technical)

  • The URL tells the truth about language.
  • Detection is polite and one-time.
  • The switcher maps equivalent pages across languages.
  • Editors translate and publish in the CMS; interface text stays versioned in JSON.
  • Search engines see two clean versions with correct alternates.

Impact

  • Fewer surprises for users.
  • Faster editorial work; no deploy cycle for translations.
  • Cleaner indexing with obvious alternates.
  • A system simple enough to maintain and extend.

Challenges and fixes

  • Helpful fallbacks that weren’t. Removed silent rules; kept one detector and one recorded preference.
  • Context loss on switch. Ensured /de/x ↔ /en/x mapping.
  • SEO uncertainty. Locale in the path, hreflang links, bilingual sitemap.
  • Editorial mismatches. Paired posts in the CMS so mid-article switching lands on the correct counterpart.

Scalability plan (adding new languages)

When Spanish (es) and Italian (it) join:

  • Add locale codes and confirm the default.
  • Create UI files with existing keys; translate values.
  • Extend the switcher options; keep the same preference behavior.
  • Update CMS schema and link translated posts to avoid orphans.
  • Publish routes and sitemap for new locales; add hreflang.
  • Smoke-test first-visit choice, switching, and indexing.
  • Document supported locales.

Small, known steps. Repeatable.

What I’ll improve next

  • Preserve ?query and #anchor on language switch. Useful for analytics and deep links.
  • Explicit redirect types. Mark permanent routes as 301 instead of relying on defaults.

Lessons

Complexity arrives disguised as “helpful.” The fix is discipline: visible URLs, a one-time detector, a trustworthy switcher, and a clean split between interface strings and content. If you can explain the behavior in a minute, it’s ready.

References

Is recurring work behind the question?

Tell us what keeps happening in the free fit check. We will review whether Groundlens is the right next step.