Çok dilli (TR-EN) web siteleri için teknik SEO
TR-EN sitelerde teknik SEO: alt dizin mi alt alan mı, hreflang kurgusu, canonical çatışması, çeviri kalitesi ve sitemap'te dil işaretleme; Python örnekleriyle.
Türkçe ve İngilizce yayın yapan bir site kurduğunuzda işin görünen kısmı çeviridir; asıl zaman alan kısımsa çok dilli web siteleri için teknik SEO tarafı oluyor. Aynı içeriğin iki dilde yaşaması, arama motoruna "bu iki sayfa birbirinin çevirisidir, kopyası değildir" demeyi gerektiriyor. Bu mesajı yanlış verdiğinizde iki sonuçtan biri çıkıyor: ya İngilizce sayfalar hiç indekslenmiyor ya da Türkiye'den arayan kullanıcıya İngilizce sayfa gösteriliyor.
Bu yazıda TR-EN sitelerde tekrar tekrar karşılaşılan beş konuyu topladım: URL yapısı seçimi, hreflang kurgusu, canonical ile hreflang'in birbirini iptal ettiği durumlar, çeviri kalitesinin indekslemeye etkisi ve sitemap'te dil işaretleme. Her başlıkta "ne yapılmalı" kadar "neyi yapmamalı" kısmına da yer verdim; çünkü gördüğüm kadarıyla bu alandaki sorunların çoğu eksik yapılandırmadan değil, birbirini çelen sinyallerden çıkıyor.
Örnekler Python ağırlıklı ve ikisi de olduğu gibi çalıştırılabilir. Amaç, bir SEO aracına bağımlı kalmadan kendi altyapınızda tekrar edilebilir bir üretim ve denetim düzeni kurmak.
URL yapısı: alt dizin mi, alt alan adı mı?
İlk karar, İngilizce içeriğin nerede yaşayacağı. Üç seçenek var: alt dizin (ornek.com/en/), alt alan adı (en.ornek.com) ve ayrı ülke uzantısı (ornek.co.uk). Küçük ve orta ölçekli bir TR-EN site için benim tercihim neredeyse her zaman alt dizin oluyor. Gerekçelerim:
- Tek mülk, tek sertifika, tek dağıtım: Alt dizin yapısında Search Console'da tek mülkle çalışırsınız; TLS sertifikası ve CDN kuralları tek yerde durur; statik site üreteçleri bu yapıyı doğal olarak destekler.
- Otorite bölünmez: Alt alan adının aynı site sayılıp sayılmayacağı yıllardır tartışılır. Kesin bir kural ilan etmek istemiyorum, ama uygulamada alt alan adını ayrı bir site gibi yönetmek zorunda kalıyorsunuz. Alt dizinde bu belirsizlik yok.
- Ülke uzantısı yalnızca hedef gerçekten ülkeyse mantıklı: İngilizce içeriğiniz belirli bir ülkedeki müşterilere değil de dünya geneline hitap ediyorsa, ayrı bir ccTLD sizi coğrafi olarak daraltır.
Yapıdan bağımsız iki kural daha var. Birincisi, dil bilgisi URL'de olmalı; çerezle ya da ?lang=en gibi sorgu parametresiyle dil değiştiren siteler arama motoruna her dil için ayrı bir adres sunamaz. İkincisi, kullanıcıyı IP adresine veya tarayıcı diline bakarak zorla yönlendirmeyin. Googlebot büyük ölçüde ABD'den geldiği için otomatik yönlendirme Türkçe sayfaların hiç taranmamasına yol açabilir. Bunun yerine "Bu sayfanın İngilizcesi mevcut" diyen küçük bir uyarı şeridi kullanıyorum; kullanıcı seçimini kendisi yapıyor, tarayıcı bot ise istediği sayfaya ulaşıyor.
hreflang kurgusu: karşılıklılık ve kendine referans
hreflang, arama motoruna "bu sayfanın Türkçesi şurada, İngilizcesi burada" diyen sinyal. Üç yerden verilebilir: sayfanın head bölümündeki link etiketleriyle, HTTP Link başlığıyla (PDF gibi HTML olmayan dosyalar için) ya da XML sitemap içinde. Üçünü birden kullanmak hata değil, ama bakımı zorlaştırıyor; ben tek kaynak seçip onu doğru tutmayı tercih ediyorum. Kaynak ne olursa olsun kurallar aynı:
- Kendine referans şart: Türkçe sayfa, alternatifler listesinde kendisini de
hreflang="tr"olarak göstermeli. Yalnızca "diğer" dili listeleyen sayfalar en sık gördüğüm hata. - Karşılıklılık şart: TR sayfa EN sayfayı gösteriyorsa EN sayfa da TR sayfayı göstermeli. Tek yönlü bağlantı yok sayılır.
- Mutlak URL:
/en/aboutdeğil,https://www.ornek.com/en/about. Protokol vewwwtercihi canonical ile birebir aynı olmalı. - Doğru dil kodu: Dil için ISO 639-1 (
tr,en), bölge için ISO 3166-1 alpha-2 (en-GB,en-US).en-UKgeçersizdir;tr-TRiletraynı sitede karışık kullanılmamalı. - x-default: Hiçbir dil eşleşmediğinde gösterilecek sayfa. Dil seçim sayfanız varsa o, yoksa ana dildeki sürüm. TR-EN sitede genellikle Türkçe sayfayı x-default yapıyorum, çünkü sitenin birincil dili o.
- Hedef doğrudan 200 dönmeli: Yönlendirme, 404 ya da
noindexiçeren bir adresi hreflang'de göstermek o bağlantının yok sayılmasına yol açar; karşılıklılık da onunla birlikte bozulur.
Bu kurallar tek tek basit; zor olan yüzlerce sayfada hepsinin aynı anda doğru kalması. Bir şablon değişikliği ya da yeniden yönlendirme, dün doğru olan çiftleri sessizce bozabiliyor. O yüzden bir sonraki bölümdeki denetim betiğini her yayından sonra çalıştırıyorum.
Canonical ile hreflang çatışması
Çok dilli sitelerde en sinsi sorun bu. Canonical etiketi "bu sayfanın asıl sürümü şudur" der; hreflang ise "bu sayfa şu sayfanın eşdeğer çevirisidir" der. Türkçe sayfanın canonical'ı İngilizce sayfayı gösteriyorsa iki sinyal çelişir: canonical, Türkçe sayfanın kopya olduğunu ve indekslenmemesi gerektiğini söylerken hreflang onun bağımsız bir alternatif olduğunu söyler. Bu durumda arama motoru genellikle canonical'ı esas alır, Türkçe sayfa indeksten düşer ve hreflang işlevsiz kalır.
Kural net: her dil sürümünün canonical'ı kendisini göstermeli. Tek istisna, gerçekten aynı içeriğe sahip bölgesel varyantlardır; sözgelimi en-US ve en-GB sayfalarınız birebir aynıysa birini canonical yapıp hreflang'de canonical olan adresi gösterirsiniz. TR-EN sitede bu istisna neredeyse hiç geçerli olmaz. Denetlerken baktığım maddeler:
- Canonical ve hreflang URL'leri sondaki eğik çizgi, büyük-küçük harf ve sorgu parametresi dahil birebir aynı mı?
- Sayfa şablonu canonical'ı sabit bir dilden mi üretiyor? Bazı CMS temaları canonical'ı "varsayılan dil" ayarından türetip her çeviride aynı adresi basıyor.
- Sayfalandırma ya da filtre parametreli sayfalarda canonical ana sayfaya dönerken hreflang parametreli adresi mi gösteriyor?
noindexverilmiş bir sayfa hâlâ hreflang listesinde mi?
Bunları elle kontrol etmek sürdürülebilir değil. Aşağıdaki betik, verilen her URL için canonical'ın kendine dönüp dönmediğini, kendine referans veren hreflang olup olmadığını, karşı sayfaların geri referans verip vermediğini ve html lang özniteliğinin hreflang koduyla uyuşup uyuşmadığını raporluyor. Yönlendirmeleri bilerek takip etmiyor; hedefin doğrudan 200 dönmesi gerekiyor. Gereksinimleri requests ve beautifulsoup4; adresleri komut satırından alıyor.
import sys
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
def sayfa_bilgisi(url):
r = requests.get(url, timeout=15, allow_redirects=False)
bilgi = {"durum": r.status_code, "hreflang": {}, "canonical": None, "lang": None}
if r.status_code != 200:
return bilgi
soup = BeautifulSoup(r.text, "html.parser")
for link in soup.find_all("link", rel="alternate"):
if link.get("hreflang") and link.get("href"):
bilgi["hreflang"][link["hreflang"]] = urljoin(url, link["href"])
canonical = soup.find("link", rel="canonical")
if canonical and canonical.get("href"):
bilgi["canonical"] = urljoin(url, canonical["href"])
html = soup.find("html")
if html:
bilgi["lang"] = html.get("lang")
return bilgi
def denetle(url):
kok = sayfa_bilgisi(url)
if kok["durum"] != 200:
return [f"{kok['durum']} dondu, 200 bekleniyordu"]
sorunlar = []
if kok["canonical"] != url:
sorunlar.append(f"canonical kendine donmuyor: {kok['canonical']}")
if url not in kok["hreflang"].values():
sorunlar.append("kendine referans veren hreflang yok")
for dil, hedef in kok["hreflang"].items():
karsi = sayfa_bilgisi(hedef)
if karsi["durum"] != 200:
sorunlar.append(f"{dil}: {hedef} {karsi['durum']} dondu")
continue
if url not in karsi["hreflang"].values():
sorunlar.append(f"{dil}: {hedef} geri referans vermiyor")
if karsi["canonical"] != hedef:
sorunlar.append(f"{dil}: {hedef} canonical'i baska yere isaret ediyor")
if dil != "x-default" and karsi["lang"]:
if karsi["lang"].split("-")[0].lower() != dil.split("-")[0].lower():
sorunlar.append(f"{dil}: {hedef} html lang={karsi['lang']} ile uyusmuyor")
return sorunlar
if __name__ == "__main__":
for u in sys.argv[1:]:
hatalar = denetle(u)
print(u, "TEMIZ" if not hatalar else f"{len(hatalar)} sorun")
for h in hatalar:
print(" -", h)
Ben bunu yerel bir test sunucusunda kasıtlı olarak bozuk bir Almanca sayfayla denedim: betik geri referans eksikliğini, başka yere işaret eden canonical'ı, uyuşmayan lang özniteliğini ve 404 dönen adresi ayrı ayrı yakaladı. Çıktı satır satır sorun listesi olduğu için doğrudan tabloya oturuyor; düzenli takip için Excel ile kalite kontrol otomasyonu yazısındaki yaklaşımı olduğu gibi uygulayabilirsiniz.
Çeviri kalitesinin indekslemeye etkisi
Teknik kurgu kusursuz olsa da içerik tarafı zayıfsa İngilizce sayfalar indekse girmiyor ya da girip kısa sürede düşüyor. Burada "kalite" derken edebi çeviri kastetmiyorum; arama motorunun sayfayı bağımsız ve değerli bir belge olarak görmesini kastediyorum. Deneyimimde en çok zarar veren üç durum:
- Gözden geçirilmemiş makine çevirisi: Google'ın spam politikaları, yayın öncesi insan denetiminden geçmemiş otomatik çevrilmiş metni ölçekli içerik istismarı örnekleri arasında sayıyor. Makine çevirisi başlangıç noktası olabilir; yayın öncesi bir gözden geçirme adımı olmadan çıkmamalı.
- Karışık dilli sayfalar: Gövde İngilizce, menü ve altbilgi Türkçe kalmış sayfalar. Şablon metinleri (buton, form etiketi, çerez uyarısı) çevrilmemişse sayfanın dili belirsizleşir.
html langözniteliği de içeriğin gerçek diliyle uyuşmalı. - Boş kabuklar: "Bu sayfanın İngilizcesi üzerinde çalışıyoruz" diyen sayfalar. Çevrilmemiş sayfa için EN adres açmayın; açtıysanız hreflang listesinden çıkarıp
noindexverin. Yarım çeviriyi indekse sokmak, hiç sokmamaktan daha çok zarar veriyor.
Kısmi çeviri normaldir; bir sitenin tamamının iki dilde olması gerekmez. Önemli olan, yalnızca gerçekten çevrilmiş sayfaların hreflang çiftine sahip olması. Tek dilli kalan sayfalar sitemap'e girer ama alternatif listelemez. Bir sonraki bölümdeki üreteç bu ayrımı veri yapısından kendiliğinden çıkarıyor.
Slug çevirisi konusunda katı bir kural yok. /en/hakkimizda çalışır, ama /en/about kullanıcı için daha anlaşılır. Karar verdikten sonra tutarlı kalın; yarı Türkçe yarı İngilizce slug karışımı hem kullanıcıya hem de bakım yapan kişiye zorluk çıkarıyor.
Sitemap'te dil işaretleme
Sitemap üzerinden hreflang vermek, özellikle head bölümüne müdahale edemediğiniz ya da her sayfaya onlarca link etiketi eklemek istemediğiniz durumlarda temiz bir çözüm. Kurgu şöyle: her url girdisi, xhtml ad alanındaki link elemanlarıyla kendisi dahil tüm dil sürümlerini listeler. Tek sitemap'te iki dili birlikte tutmak da dil başına ayrı sitemap kullanmak da geçerli; ikisinde de her girdi tam alternatif kümesini taşımalı. Aşağıdaki betik sayfa listesini tek bir veri yapısından okuyup sitemap'i üretiyor. XML'i metin olarak birleştirmek yerine ElementTree kullandım; ad alanı ve karakter kaçışlarını kütüphane hallediyor, elle yazılmış sitemap'lerde sık rastlanan bozuk XML sorunu böylece ortadan kalkıyor. ET.indent için Python 3.9 ve üzeri gerekiyor.
import xml.etree.ElementTree as ET
SITE = "https://www.ornek.com"
XHTML = "http://www.w3.org/1999/xhtml"
SAYFALAR = [
{"tr": "/tr/hakkimizda", "en": "/en/about"},
{"tr": "/tr/hizmetler/veri-otomasyonu", "en": "/en/services/data-automation"},
{"tr": "/tr/iletisim"}, # henuz cevrilmedi: alternatifsiz girer
]
ET.register_namespace("xhtml", XHTML)
urlset = ET.Element("urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
for sayfa in SAYFALAR:
for dil, yol in sayfa.items():
url = ET.SubElement(urlset, "url")
ET.SubElement(url, "loc").text = SITE + yol
if len(sayfa) == 1:
continue # tek dilli sayfa: hreflang yazilmaz
for alt_dil, alt_yol in sayfa.items():
ET.SubElement(url, f"{{{XHTML}}}link",
rel="alternate", hreflang=alt_dil, href=SITE + alt_yol)
ET.SubElement(url, f"{{{XHTML}}}link",
rel="alternate", hreflang="x-default", href=SITE + sayfa["tr"])
ET.indent(urlset)
ET.ElementTree(urlset).write("sitemap.xml", encoding="utf-8", xml_declaration=True)
print("yazildi:", len(urlset), "url girdisi")
Çıktıda /tr/iletisim tek başına, alternatifsiz yer alır; diğer iki sayfa TR, EN ve x-default olmak üzere üçer bağlantı taşır. Betiği içerik deposundan beslerseniz sitemap her yayında yeniden üretilir ve hreflang'in tek kaynağı bu dosya olur. Search Console'daki eski uluslararası hedefleme raporu kaldırıldığından beri hreflang hatalarını size toplu hâlde bildiren bir yer yok; bu yüzden hem üretim hem de denetim tarafını kendi elinizde tutmanız gerekiyor. İki betiği sistem izleme betiği yazısındaki gibi zamanlanmış bir göreve bağlayıp yayın sonrası otomatik koşturmak, bu işin en ucuz sigortası.
Özetle: dili URL'ye koyun, alt dizinle başlayın, her sayfada kendine dönen canonical ve karşılıklı hreflang kurun, çevrilmemiş sayfayı indekse sokmayın ve sitemap'i elle değil veriden üretin. Bu beş maddeyi oturttuktan sonra çok dilli sitenin teknik SEO tarafı büyük ölçüde bakım gerektirmeyen, denetlenebilir bir yapıya dönüşüyor. Kendi sitenizde bu düzeni kurmak ya da mevcut kurguyu denetletmek isterseniz ana sayfa üzerindeki iletişim bölümünden (/#contact) bana yazabilirsiniz.