The Multilingual SEO Problem

When you publish the same content in two languages on the same domain, search engines face a dilemma: which version should rank for which audience? Without explicit signals, Google might show your Turkish page to English speakers, or worse, treat both versions as duplicate content and penalize your rankings for both.

We hit this problem directly with the JekCMS marketing site (jekcms.alfadizayn.com), which serves every page in both Turkish and English. The blog posts, documentation, feature pages — everything exists in two languages on the same domain, differentiated by URL structure. After six months of testing different approaches across our network of 12 bilingual sites, we settled on a system that consistently delivers correct language targeting in Google Search results.

This guide documents every technical decision, with real Search Console data showing what works and what does not.

URL Structure: Subdirectory vs. Subdomain vs. Parameter

There are three standard approaches for multilingual URLs:

  • example.com/en/about — Subdirectory (our choice)
  • en.example.com/about — Subdomain
  • example.com/about?lang=en — Parameter (avoid this)

We use subdirectories because they consolidate domain authority. Every backlink to any language version strengthens the entire domain. Subdomains split authority — Google sometimes treats them as separate sites. Parameters are the worst option because Google often ignores URL parameters for indexing purposes.

JekCMS's routing handles this through the routes.php configuration:

// routes.php
$routes = [
    // English routes
    '/en/blog/{slug}' => ['controller' => 'BlogController', 'action' => 'show', 'lang' => 'en'],
    '/en/blog' => ['controller' => 'BlogController', 'action' => 'index', 'lang' => 'en'],

    // Turkish routes (default language, no prefix needed)
    '/blog/{slug}' => ['controller' => 'BlogController', 'action' => 'show', 'lang' => 'tr'],
    '/blog' => ['controller' => 'BlogController', 'action' => 'index', 'lang' => 'tr'],
];

Turkish is the default language and uses unprefixed URLs. English uses the /en/ prefix. This keeps Turkish URLs short (important for a primarily Turkish audience) while making the language distinction clear for search engines.

hreflang Implementation

The hreflang attribute tells search engines which language and region a page targets. Every page that exists in multiple languages must include hreflang tags pointing to all available versions, including itself.

Here is the implementation from JekCMS's header.php:

<?php
function output_hreflang_tags(string $currentLang, string $slugEn, string $slugTr): void {
    $baseUrl = rtrim(SITE_URL, '/');

    // Turkish version (default language, no prefix)
    $trUrl = $baseUrl . '/blog/' . $slugTr;
    // English version
    $enUrl = $baseUrl . '/en/blog/' . $slugEn;

    echo '<link rel="alternate" hreflang="tr" href="' . $trUrl . '" />' . "
";
    echo '<link rel="alternate" hreflang="en" href="' . $enUrl . '" />' . "
";
    echo '<link rel="alternate" hreflang="x-default" href="' . $trUrl . '" />' . "
";
}
?>

<!-- In the <head> section -->
<?php output_hreflang_tags($currentLang, $post['slug_en'], $post['slug_tr']); ?>

Three critical details in this implementation:

  1. Self-referencing: The Turkish page includes a hreflang pointing to itself (hreflang="tr"). Google requires this — every page must reference all language versions, including its own.
  2. Bidirectional: Both the Turkish and English pages must include the exact same set of hreflang tags. If the Turkish page points to the English version but the English page does not point back, Google ignores the signal.
  3. x-default: This tells search engines which version to show when no other hreflang matches the user's language. We set it to Turkish since that is our primary audience.

Canonical URLs: One Per Language

A common mistake is using a single canonical URL for both language versions. This tells Google that one version is the "original" and the other is a copy — exactly the opposite of what you want.

Each language version must have its own canonical URL pointing to itself:

<!-- On the Turkish page: /blog/jekcms-rehberi -->
<link rel="canonical" href="https://jekcms.alfadizayn.com/blog/jekcms-rehberi" />

<!-- On the English page: /en/blog/jekcms-guide -->
<link rel="canonical" href="https://jekcms.alfadizayn.com/en/blog/jekcms-guide" />

In JekCMS, the canonical tag is generated dynamically based on the current language:

function output_canonical(string $lang, string $slug): void {
    $baseUrl = rtrim(SITE_URL, '/');

    if ($lang === 'en') {
        $url = $baseUrl . '/en/blog/' . $slug;
    } else {
        $url = $baseUrl . '/blog/' . $slug;
    }

    // Strip query parameters - canonical should be clean
    $url = strtok($url, '?');

    echo '<link rel="canonical" href="' . htmlspecialchars($url) . '" />' . "
";
}

The strtok($url, '?') call on line 10 strips any query parameters. This prevents pagination parameters, UTM tracking codes, or sorting parameters from creating duplicate canonical URLs.

Per-Language Sitemaps

Google recommends including hreflang annotations in your sitemap as well as in HTML. This provides a second signal and helps Google discover language variants for pages it finds through the sitemap before it crawls the HTML.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <url>
        <loc>https://jekcms.alfadizayn.com/blog/jekcms-rehberi</loc>
        <xhtml:link rel="alternate" hreflang="tr"
            href="https://jekcms.alfadizayn.com/blog/jekcms-rehberi" />
        <xhtml:link rel="alternate" hreflang="en"
            href="https://jekcms.alfadizayn.com/en/blog/jekcms-guide" />
        <xhtml:link rel="alternate" hreflang="x-default"
            href="https://jekcms.alfadizayn.com/blog/jekcms-rehberi" />
        <lastmod>2026-03-20</lastmod>
    </url>
    <url>
        <loc>https://jekcms.alfadizayn.com/en/blog/jekcms-guide</loc>
        <xhtml:link rel="alternate" hreflang="tr"
            href="https://jekcms.alfadizayn.com/blog/jekcms-rehberi" />
        <xhtml:link rel="alternate" hreflang="en"
            href="https://jekcms.alfadizayn.com/en/blog/jekcms-guide" />
        <xhtml:link rel="alternate" hreflang="x-default"
            href="https://jekcms.alfadizayn.com/blog/jekcms-rehberi" />
        <lastmod>2026-03-20</lastmod>
    </url>
</urlset>

Notice both the Turkish and English URL entries include the full set of hreflang annotations. Each URL entry must reference all language variants, just like in the HTML implementation.

JekCMS generates this sitemap dynamically:

function generate_bilingual_sitemap(): string {
    global $db;

    $posts = $db->fetchAll(
        "SELECT slug_en, slug_tr, updated_at FROM marketing_blog_posts
         WHERE status = 'published' ORDER BY published_at DESC"
    );

    $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "
";
    $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
              xmlns:xhtml="http://www.w3.org/1999/xhtml">' . "
";

    $baseUrl = rtrim(SITE_URL, '/');

    foreach ($posts as $post) {
        $trUrl = $baseUrl . '/blog/' . $post['slug_tr'];
        $enUrl = $baseUrl . '/en/blog/' . $post['slug_en'];
        $lastmod = date('Y-m-d', strtotime($post['updated_at']));

        // Turkish entry
        $xml .= generate_url_entry($trUrl, $trUrl, $enUrl, $lastmod);
        // English entry
        $xml .= generate_url_entry($enUrl, $trUrl, $enUrl, $lastmod);
    }

    $xml .= '</urlset>';
    return $xml;
}

Open Graph and Social Sharing

Social media platforms use Open Graph tags to generate link previews. For bilingual content, you need language-specific OG tags:

<!-- Turkish page -->
<meta property="og:locale" content="tr_TR" />
<meta property="og:locale:alternate" content="en_US" />
<meta property="og:title" content="<?= htmlspecialchars($post['title_tr']) ?>" />
<meta property="og:description" content="<?= htmlspecialchars($post['excerpt_tr']) ?>" />
<meta property="og:url" content="<?= $trUrl ?>" />

<!-- English page -->
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="tr_TR" />
<meta property="og:title" content="<?= htmlspecialchars($post['title_en']) ?>" />
<meta property="og:description" content="<?= htmlspecialchars($post['excerpt_en']) ?>" />
<meta property="og:url" content="<?= $enUrl ?>" />

The og:locale tag uses the full locale code (e.g., tr_TR) rather than just the language code. The og:locale:alternate tag tells platforms that another language version exists. When someone shares the English URL on Facebook in Turkey, Facebook may choose to display the Turkish preview instead — though in practice, this behavior is inconsistent across platforms.

Common Mistakes and How to Avoid Them

After implementing hreflang on 12 bilingual JekCMS sites and monitoring Search Console for six months, here are the most common mistakes we encountered:

Mistake 1: Missing Return Links

The Turkish page has hreflang="en" pointing to the English version, but the English page does not have hreflang="tr" pointing back. Google ignores unconfirmed hreflang signals. Both pages must reference each other.

Fix: Use a shared function that generates the complete set of hreflang tags. Both templates call the same function, guaranteeing consistency.

Mistake 2: Wrong Language Codes

Using hreflang="tr-TR" instead of hreflang="tr". The region subtag (-TR) is only needed when you have multiple regional variants of the same language (e.g., pt-BR vs pt-PT for Brazilian vs European Portuguese). For Turkish and English without regional targeting, just use tr and en.

Mistake 3: hreflang on Non-Canonical URLs

If a page redirects (301) to another URL, the hreflang tags should be on the final destination URL, not the redirecting URL. Google will ignore hreflang on pages that redirect.

Mistake 4: Different Content Between Languages

Google expects hreflang-linked pages to be translations of each other, not completely different content. If your English "About" page has different information than your Turkish one, Google may ignore the hreflang connection. The content should be equivalent in meaning, even if not a word-for-word translation.

Mistake 5: Mixing Canonical and hreflang Signals

Setting the canonical of the English page to point to the Turkish page while also having hreflang tags creates a conflict. The canonical says "the Turkish page is the original," while hreflang says "these are equal alternatives." Google gets confused. Keep canonical self-referencing for each language version.

Google Search Console Validation

After deploying hreflang tags, validate them through Google Search Console:

  1. Go to Search Console > International Targeting
  2. Check the Language tab for hreflang errors
  3. Common errors: "No return tag," "Unknown language code," "Tag on non-canonical page"
  4. Use URL Inspection to verify specific pages show the correct hreflang annotations

It takes Google 2-4 weeks to fully process hreflang changes. During this period, you might see temporary ranking fluctuations as Google re-evaluates which version to show for which queries. Do not panic and change things during this adjustment period.

After implementing this complete hreflang setup on our 12 sites, we saw a 23% average increase in organic traffic from English-speaking countries within 6 weeks, simply because Google started showing the English version to English searchers instead of the Turkish one.

Testing Your Implementation

Before relying on Search Console (which has a delay), test your implementation immediately with these methods:

  • View page source: Check that both hreflang tags and canonical tags are present in the <head> section
  • Google's Rich Results Test: Enter your URL and inspect the rendered HTML for hreflang tags
  • Screaming Frog: Crawl your site and check the hreflang report for inconsistencies
  • Manual cross-check: Visit the Turkish page, note the hreflang URLs, then visit each URL and verify it has return hreflang tags

Multilingual SEO requires precision in implementation but the payoff is significant. Each language version can rank independently for queries in its target language, effectively doubling your organic search visibility without creating any duplicate content issues.