/ โ† Hreflang Fixes / Language Codes
In this guide
  1. The anatomy of a valid value
  2. Values that look right but are not
  3. Where wrong codes come from
  4. When to add a region, and when to leave it out
  5. Validating codes automatically
  6. The fix, step by step
  7. Keeping codes correct as you add markets
  8. Questions and answers

The anatomy of a valid value

A hreflang value is built from up to two parts joined by a hyphen:

  1. Language, required: a two-letter code from ISO 639-1, the international list of language codes. en is English, de is German, pl is Polish.
  2. Region, optional: a two-letter code from ISO 3166-1 alpha-2, the international list of country codes. GB is the United Kingdom, AT is Austria, MX is Mexico.

So de means "German, for anyone", and de-AT means "German, for people in Austria". The order never changes: language first, region second. A region can never appear on its own, and the special value x-default is the only thing allowed that does not follow the pattern.

Chinese is the common case for a third ingredient. Because the same language is written in two scripts, sites often need to separate Simplified from Traditional characters. Google recognises script subtags such as zh-Hans and zh-Hant, and region-based values such as zh-CN, zh-TW and zh-HK are also valid. Choose one approach for the whole cluster and keep to it.

Letter case is not significant to Google. en-gb, EN-GB and en-GB are read the same way. The convention of lower-case language and upper-case region is still worth following, because it makes mistakes easier to spot when you read the source.

๐Ÿ’ก The hyphen is the only separator. Values written with an underscore, such as en_GB, come from software locale settings and are not valid hreflang, even though they look official.

Values that look right but are not

Most invalid codes are not typing slips. They are reasonable guesses based on country names, domain endings or software settings. The table groups the mistakes by where they come from, so you can recognise the pattern on your own site.

Value foundWhy it failsUse instead
en-UKUK is a common abbreviation, but the ISO country code is GBen-GB
en_GB, fr_CAUnderscore format from WordPress, PHP or Java locale settingsen-GB, fr-CA
czTaken from the .cz domain; it is not a language code at allcs or cs-CZ
uaThe country code for Ukraine, not the languageuk or uk-UA
krReal code, wrong language: it means Kanuri, not Koreanko or ko-KR
ee for EstoniaReal code, wrong language: it means Eweet or et-EE
br for BrazilReal code, wrong language: it means Bretonpt-BR
iw, in, jiOld codes withdrawn in favour of new ones; some software still emits themhe, id, yi
es-419A numeric area code, outside the format Google documents for hreflangPlain es, or one value per country
en-INT, en-WWInvented regions meaning "international"Plain en, or x-default
fr-BE-nl, de-CH-FRTwo regions or a language tacked on the endOne language and at most one region
english, GermanLanguage names written outen, de

The "real code, wrong language" rows are the most dangerous. A validator that only checks whether a value exists will pass kr, ee and br, but you have told Google the page is in Kanuri, Ewe or Breton. The value is technically well formed and completely wrong for your audience, so it is worth checking the meaning of every code, not just its shape.

Where wrong codes come from

Fixing a code in the page source only lasts until the next deployment. Find the setting that produced it.

A related check: the lang attribute and og:locale

The same codes appear in two other places, and it helps to keep them consistent. The lang attribute on the html element uses the hyphen format, for example <html lang="en-GB">, and helps browsers and screen readers. The Open Graph og:locale tag is different: it uses the underscore format, for example en_GB. That difference is a common source of confusion, because code copied from og:locale into hreflang produces an invalid value.

When to add a region, and when to leave it out

A region narrows a page to one country. Add it only when the content actually changes by country: prices and currency, delivery terms, legal notices, spelling conventions or product ranges. If you have one German page that serves Germany, Austria and Switzerland equally, plain de describes it accurately, and inventing three region codes for the same page adds lines without adding meaning.

There is also a gap to avoid. If your cluster contains only en-GB and en-US, an English speaker in Canada or Australia matches neither. Adding a language-only value, such as en pointing at your most general English page, catches everyone else who reads that language. Google uses the language-only value as the fallback for users of that language whose region is not listed separately.

<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/">
<link rel="alternate" hreflang="en-US" href="https://example.com/us/">
<link rel="alternate" hreflang="en" href="https://example.com/uk/">
<link rel="alternate" hreflang="de" href="https://example.com/de/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">

Here the British page is listed twice: once for the UK, and once as the general English page. That is allowed. Every page in the cluster must still carry exactly the same five lines.

โš ๏ธ A region code does not target a country in the way a country-code domain does. It only tells Google which of your alternates suits searchers from that country. Content, currency and links still decide whether you rank there at all.

Validating codes automatically

Shape checks catch underscores, invented regions and extra parts. A regular expression covering language, optional script and optional region looks like this:

^(x-default|[a-z]{2}(-[A-Za-z]{4})?(-[A-Za-z]{2})?)$

Shape is only half the job. The value also has to exist, and the language has to be the one you meant. A short Python check that uses the langcodes package, or your own copies of the ISO lists, can test both:

import re

LANGS = {"en", "de", "fr", "cs", "uk", "ko", "et", "pt", "zh", "he"}   # load the full ISO 639-1 list
REGIONS = {"GB", "IE", "US", "DE", "AT", "CZ", "UA", "KR", "EE", "BR", "CN", "TW"}  # full ISO 3166-1 alpha-2 list
EXPECTED = {"/cz/": "cs", "/ua/": "uk", "/kr/": "ko"}  # folder to intended language

def check(value, url):
    if value == "x-default":
        return "ok"
    m = re.fullmatch(r"([a-z]{2})(?:-([A-Za-z]{4}))?(?:-([A-Za-z]{2}))?", value, re.I)
    if not m:
        return "bad format"
    lang, script, region = m.group(1).lower(), m.group(2), m.group(3)
    if lang not in LANGS:
        return "unknown language " + lang
    if region and region.upper() not in REGIONS:
        return "unknown region " + region
    for folder, want in EXPECTED.items():
        if folder in url and lang != want:
            return "language " + lang + " but folder expects " + want
    return "ok"

The expected-language map is what catches kr on a Korean section. Keep the ISO lists up to date from the official sources rather than typing them from memory.

The fix, step by step

Step 1

Collect every hreflang value in use

Run the Hreflang Checker on the main page of each locale and let it follow the clusters, then add any values from your XML sitemap and HTTP headers if you use them. You want a list of every distinct value the site emits, together with one example page for each. On most sites the list is short, often under twenty values, which makes the rest of the work quick once the list exists and is complete.

Step 2

Split each value into language and region

Break each value at the hyphen into its parts. Anything with an underscore, a space, more than one region, a written-out language name or an invented suffix fails at this stage and goes straight onto the fix list. Values with a four-letter middle part, such as zh-Hant, are script subtags and are allowed. Note which values have no region, because you will decide later whether that is intentional.

Step 3

Check the language part against ISO 639-1

Look up each language part in the ISO 639-1 list and confirm two things: that the code exists, and that it names the language the page is actually written in. This is where cz, ua and jp fail because they are not language codes, and where kr, ee and br fail because they belong to other languages. Replace withdrawn codes such as iw with current ones such as he.

Step 4

Check the region part against ISO 3166-1 alpha-2

For values with a region, confirm the two letters are an assigned country code. UK is the classic failure and must become GB. Continents, trading blocs and invented labels such as EU, LA, INT or WW are not usable regions, and numeric area codes like 419 fall outside the documented format. Replace them with plain language values, separate values per country, or the x-default fallback.

Step 5

Decide whether you need a region at all

For each language, ask whether the content genuinely differs by country. If one Spanish page serves every Spanish speaker, use plain es. If you have separate pages for Spain and Mexico, use es-ES and es-MX, and consider adding plain es pointing at the more general page so Spanish speakers in other countries still match. Write the final list of values down as your locale plan.

Step 6

Correct the codes where they are configured

Change the value at its source rather than in the output: the language settings of your translation plugin or platform, the locale-to-hreflang mapping in your templates, the sitemap generator or the market configuration. If the CMS stores locales with underscores, convert them with a single mapping function used everywhere. Leave URL folders alone unless you are already planning a migration, because folder names do not have to match codes.

Step 7

Validate the output and lock it in

Purge caches, rerun the Hreflang Checker and confirm every value now passes, then view the source of one page per locale to see the corrected tags as crawlers receive them. Add a format and meaning check, like the script above, to your build or release process so a new market cannot go live with an invalid code. Update the lang attribute at the same time if it had the same mistake.

Keeping codes correct as you add markets

For the wider picture of how codes fit into clusters, see the Hreflang Guide, and for choosing the fallback page for everyone outside your listed markets, see the x-default guide linked below.

Questions and answers

1. Why is en-UK wrong when everyone says UK?

Hreflang region codes come from ISO 3166-1 alpha-2, and the code assigned to the United Kingdom there is GB, not UK. So the correct value for British English is en-GB. Google does not recognise en-UK, so a page labelled that way is effectively missing from the cluster. It is an easy mistake because UK is used everywhere else, including in domain names, and it also appears in older templates and plugin settings, so check every place the value is generated.

2. Are hreflang codes case sensitive?

No. Google reads en-gb, EN-GB and en-GB as the same value. The usual convention is a lower-case language and an upper-case region, which is how the ISO lists present them and how most documentation writes them. Following the convention does not change how Google treats the page, but it makes the source easier to read and makes errors such as en-UK or cz stand out more clearly when you or a colleague review the tags.

3. Can I use en_GB with an underscore?

Not in hreflang. The underscore format comes from software locale settings in systems such as WordPress, PHP and Java, and it is also the correct format for the Open Graph og:locale tag. Hreflang, like the HTML lang attribute, uses a hyphen, so the value must be en-GB. If your CMS stores locales with underscores, convert them in the template or use the plugin's separate hreflang field, rather than editing individual pages by hand.

4. Do I need a region code for every language?

No. Use a region only when the page is written for one country, for example because prices, delivery terms or spelling differ. A single French page for all French speakers should use plain fr. If you do split a language by country, consider also adding a language-only value that points at your most general version, so speakers of that language in countries you have not listed are still matched to an appropriate page.

5. Is there a hreflang code for Latin America or the EU?

Not in the format Google documents. Hreflang regions are individual countries from ISO 3166-1 alpha-2, so values such as es-LA, en-EU or es-419 should not be relied on. If one Spanish page serves all of Latin America, label it plain es. If you have separate pages for individual countries, give each its own value, such as es-MX or es-AR. For audiences outside all of your markets, use the x-default fallback.

6. How do I target Traditional and Simplified Chinese?

You can separate them by script, using zh-Hant for Traditional characters and zh-Hans for Simplified, or by country, using values such as zh-TW, zh-HK and zh-CN. Both are accepted. Choose the approach that matches how your content really differs: if the difference is only the script, script subtags describe it best; if prices, products or legal details change by market, country values are clearer. Use one approach consistently within each cluster.

7. What happens to a page with an invalid hreflang code?

Google ignores the annotation with the invalid value, so that version drops out of the cluster. The page can still be indexed and can still rank, but Google no longer knows it is the alternate for that audience, so searchers may be shown a sibling version instead. If the invalid value is on every page in the locale, the whole locale is effectively unannotated. No warning appears in Search Console, so a crawl-based check is needed to find it.

Check every hreflang code on your site

The Hreflang Checker follows each cluster of locale pages and validates return tags, language codes, x-default, sitemap consistency, target reachability and canonical alignment.

Open Hreflang Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Hreflang Fixes  ·  Adding a Missing x-default  ·  Fixing Missing Return Tags  ·  Hreflang Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.