İçeriğe geç
wedevit

August 25, 2026 · 9 min read · software

İlhan Buğra Aslan

How to build a multilingual site: URL structure, hreflang and the work that starts after translation


A multilingual site is three separate jobs, and budgets usually cover only the first. Job one is addressing: every language lives at its own permanent URL and does not change based on a cookie or a browser setting. Job two is telling search engines which page corresponds to which, which means reciprocal hreflang annotations and an x-default. Job three is everything that remains after the translator is done: plural rules, date and currency formatting, sorting, case conversion, text that grows and breaks the layout. Skip jobs two and three and you get a site that looks translated and behaves wrong.

Every language needs its own URL

Sites that swap language at the same address fail for one structural reason: there are no multiple versions at that address, there is one version that changes shape per visitor. Google's documentation is explicit about how it detects language, and neither hreflang nor the HTML lang attribute is part of it. Language is inferred algorithmically from the visible content of the page. So if your page decides what to render from a cookie or an Accept-Language header, Googlebot sees exactly one variant and the others effectively do not exist.

The second problem is human, not technical. A reader on your Turkish page sends the link to a colleague in Germany, and that colleague does not land in their own language, they land in whatever your session was serving. Automatic redirection based on IP or browser locale is not the fix either. Google warns against it directly, because those redirects stop users and crawlers alike from reaching all versions of a site. The pattern that works: a visible language switcher on every page, a dismissible banner on the first visit offering the other language, a remembered preference, and an address that never changes unless the visitor clicks something.

Subdirectory, subdomain, or country domain

Google supports all three and compares them in its own guidance. A country-code domain (example.de) sends the clearest geographic signal and is independent of where your server sits, but each domain is a separate entity: separate registration, separate certificates, separate link profile, separate maintenance. A subdomain (de.example.com) is easy to set up but reads as a weak signal to users. A subdirectory (example.com/de/) is the cheapest to maintain and shares the authority built on one domain, at the cost of the vaguest geotargeting signal. URL parameters (example.com?lang=de) are the one approach Google recommends against.

In practice, most companies running a single marketing site should use subdirectories. A country domain earns its keep when you have a legal entity in that market, a separate price list, and a content plan that will actually feed the domain. One detail is no longer negotiable: Search Console's International Targeting report, which included the site-wide country targeting setting, was announced as deprecated in August 2022 and removed from the interface that September. There is no dashboard switch to compensate for structure any more. What your URLs and your content say is what counts.

Slugs are content too

Giving a Turkish page an English URL is a common and pointless loss. The reader cannot parse the address, the URL fragment shown in search results falls out of language, and the shared link looks foreign. The distinction worth drawing: the file name or translation key is a technical identifier, the URL is something a human reads. The same article can live at /en/blog/multilingual-site-architecture and /tr/blog/cok-dilli-site-url-yapisi-hreflang, with hreflang tying the two together.

If your target language has characters outside ASCII, transliterate them in the slug. Non-ASCII URLs work in the address bar, but they turn into percent-encoded noise the moment someone copies and pastes them into an email or a chat window.

hreflang: few rules, no tolerance

The annotation can live in three places: a link element in the HTML <head>, an HTTP Link header for non-HTML files, or an xhtml:link child in your XML sitemap. All three are equivalent. On sites with hundreds of pages the sitemap usually ages better, because the entire mapping sits in one file instead of being scattered across templates.

The rules are short and unforgiving:

  • Every page lists all variants, including itself.
  • The links must be reciprocal. Google's wording leaves no room: if two pages don't both point to each other, the tags will be ignored. A one-way annotation does nothing at all.
  • The language code is ISO 639-1, optionally followed by an ISO 3166-1 Alpha 2 region code: tr, en, en-GB, de-CH. You cannot specify a country code on its own; the first part has to be a language.
  • x-default points at the page to use when no language matches. It fits a language selector page or your primary version.
  • A broken annotation is not penalized, it is simply ignored. That sounds reassuring and isn't, because it means the failure is silent.

Who audits it is also a question that changed in 2022. With the International Targeting report gone, hreflang errors no longer surface in Search Console, so the job belongs to a crawler or to a check you write into your own build. A test that verifies the reverse link still exists whenever a page is renamed or retired covers most of the risk on its own.

Language versions versus market versions

Splitting en-GB from en-US doubles the work for most companies and returns very little. A regional variant makes sense when price, currency, shipping terms, availability, or legal text genuinely differ. When they don't, you end up with two nearly identical pages competing for the same query, and you are bidding against yourself. Keep a single en version and split later, when a real market difference appears.

The same restraint applies to how many languages you launch with. Three languages is not three times the content, it is three times the content operation: every new page, every price change, every campaign line now lives in three places. Carrying one language well beats carrying three halfway, on every metric worth watching.

Where machine translation stops being fine

Google's spam policies added a "scaled content abuse" section in March 2024, and it addresses this directly. One of the listed examples is scraping feeds, search results, or other content to generate many pages, including through automated transformations like synonymizing, translating, or other obfuscation techniques, where little value is provided to users. The policy is deliberately about outcome rather than method. Whether a human or a model produced the text is not the test; whether the page is worth landing on is.

So machine translation is not the problem, unreviewed bulk translation is. What works is machine translation with human post-editing, applied by priority rather than uniformly: pages that sell, pricing and contract text, and the parts of your documentation people actually read come first. Keep a glossary as well. If product names, module names, and industry terms don't resolve to the same word every time, the same feature ends up with three names across three pages and none of them accumulate any search signal. How you show up in AI-assisted search is a related but separate problem, covered in AI search visibility.

Translation is done, localization is just starting

If strings are still embedded in code, everything downstream gets expensive. The baseline is a key for every user-facing string, and never building a sentence from fragments. Something like "Found " + n + " records" is unrepairable in a language with a different word order. The standard answer is ICU MessageFormat, where variables, plural branches, and gender branches all live inside one template.

Plurals are messier than most teams expect. Unicode CLDR defines six plural categories: zero, one, two, few, many, and other. Every language uses a subset. Arabic uses all six; English and Turkish use two. Turkish adds a rule English speakers rarely anticipate, in that a noun following a number takes no plural suffix, so "3 kitap" is correct and "3 kitaplar" is not. A plural template designed around English grammar will get that wrong. For dates, times, and currency, don't hand-roll formatting either; the Intl API from ECMA-402 does it with CLDR data. Storing timestamps in UTC and localizing only at render time belongs to the same family of rules.

The casing trap, using Turkish as the example

In Turkish the lowercase form of I is ı, and the uppercase form of i is İ. This produces one of the best documented localization bugs in the industry. Java's own documentation spells it out: in a Turkish locale, "TITLE".toLowerCase() returns "tıtle", and code that needs locale-independent results should call toLowerCase(Locale.ROOT). In .NET, ToLower() is culture-sensitive by default. JavaScript inverts the defaults: toLowerCase() is locale-independent, and toLocaleLowerCase() is the one that honors a locale.

The rule that keeps you out of trouble: use locale-aware conversion for text you show a person, never for text you compare. Usernames, email addresses, file extensions, HTTP headers, cache keys, and permission checks all need locale-independent handling. Otherwise your application behaves differently on a server configured with a Turkish locale than on an English one, and that class of bug almost never shows up in tests.

Sorting and search are the other half. Alphabetical order is language-specific, and letters like ç and ş don't sit in a fixed position across languages. MySQL 8 ships language-specific collations such as utf8mb4_tr_0900_ai_ci, and PostgreSQL has supported ICU collations since version 10. Search has an obvious expectation attached: someone typing "Istanbul" should find "İstanbul". Just check that whatever normalization you add to make that work hasn't made your indexes unusable, or multilingual search quietly turns into a slow query problem.

Text grows, and sometimes it changes direction

Translation makes text longer. The IBM expansion figures cited by the W3C put short English strings of up to 10 characters at 200 to 300 percent expansion, dropping to around 130 percent for strings over 70 characters. Short text expands most, and short text is exactly what sits in the tightest places: buttons, tabs, form labels, captions next to icons. A fixed-width button that looks fine in English overflows in German.

The cheap way to test this is pseudolocalization. You generate a locale that pads every string and marks it with accented characters, open the interface with it, and you see both the boxes that overflow and the hardcoded strings nobody externalized, all in one pass. If Arabic or Hebrew is on the roadmap, add another layer: dir="rtl", logical CSS properties like margin-inline-start instead of margin-left, and mirrored directional icons.

Half-translated is the expensive state

Most multilingual sites don't break on launch day, they break in month six. A page gets added on one side, the other language is deferred, that page joins no hreflang set, and it drifts. Give it long enough and the gap between languages grows to the point where nobody can say which version is current.

One sentence prevents it: a page with incomplete translation does not ship, and if it must ship it ships with noindex and stays out of the language mapping. Then name an owner. Who maintains the translation memory and the glossary, who opens the translation request when a page is created, and what check tells you the translation went stale after the source changed? Accessibility belongs in the same pass: the lang attribute on the html element is a Level A success criterion in WCAG 2.2, and while Google doesn't use it for language detection, screen readers use it to pick pronunciation, with in-page language changes needing their own markup (WCAG 2.2 compliance).

A first step that fits in this week

Run five checks. One: does every language version have its own permanent URL, or is the language tied to a cookie? Two: do both pages carry hreflang annotations pointing at each other, and is x-default defined? Three: is there automatic redirection by location, and can you replace it with a dismissible suggestion banner? Four: are dates, currency, and plurals correct in the target language, and does anything overflow in the interface? Five: how many published pages are still partly translated, and are they kept out of the index?

Four of those are a day of work, the fifth is a permanent process. That order matters: fix addressing and mapping first, then assign an owner to the translation operation. Measure page speed for the new language separately as well, since a site serving a distant market won't produce the numbers you see at home (Core Web Vitals and field data).


Need help with this topic?

get in touchall posts