Türkçe klavyede yan yana duran iki tuş, bilgisayar için dört ayrı karakter üretir: büyük I (U+0049), küçük i (U+0069), noktalı büyük İ (U+0130) ve noktasız küçük ı (U+0131). İlk ikisi İngilizcedeki harflerle aynı karakterdir. Son ikisi Türkçe ve Azerbaycan Türkçesi için ayrıca kodlanmıştır. Bir yazılım bu dördünü yalnızca İngilizce kurallarla eşleştirirse “İstanbul” araması “istanbul” sözcüğünü bulamaz, “ISPARTA” küçülünce “isparta” olur.
Bu yazı, font seçiminden bağımsız olarak metnin kendisinde çıkan i sorunlarını anlatıyor: büyük-küçük harf dönüşümü, görünmeyen birleşik nokta, karşılaştırma ve alfabetik sıralama. Fontun bu harfleri çizip çizmediği ayrı bir konu; onu font listemizde her aile için ölçüyoruz.
Dört karakter, iki çift
| Karakter | Kod noktası | Türkçedeki eşi |
|---|---|---|
| I | U+0049 | ı (U+0131) |
| i | U+0069 | İ (U+0130) |
| İ | U+0130 | i (U+0069) |
| ı | U+0131 | I (U+0049) |
İngilizcede I ile i bir çifttir. Türkçede ise I’nın küçüğü ı, i’nin büyüğü İ’dir. Unicode bu farkı karakterlerin kendisine değil, dile bağlı bir kural dosyasına yazmıştır. SpecialCasing.txt adlı dosyanın “Turkish and Azeri” bölümü, tr ve az dil kodları için şu dönüşümleri tanımlar: büyük harfe çevirirken i → İ, küçük harfe çevirirken I → ı ve İ → i.
CLDR’nin Türkçe için tanımladığı temel harf listesi de bunu yansıtıyor: listede “i” ve “İ” bir arada, “ı” ise ayrı bir harf olarak yazılı.
Dil verilmezse ne olur?
Dil kodu verilmeden yapılan dönüşüm varsayılan Unicode kurallarını kullanır. Bunlardan biri Türkçe yazılımlarda sürpriz yaratır. SpecialCasing.txt’nin dilden bağımsız bölümündeki satır şöyle:
0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
Bu satıra göre noktalı büyük İ, dil belirtilmeden küçük harfe çevrilirse tek bir karaktere değil, iki karaktere dönüşür: küçük i (U+0069) ve üstüne eklenen birleşik nokta (U+0307). Dosyadaki açıklama nedenini “noktalı I’nın kanonik eşdeğerliğini korumak” olarak veriyor; Türkçe kuralı ise aşağıda ayrıca tanımlanmış.
Bunun pratik sonucu şu:
"İstanbul".length; // 8
"İstanbul".toLowerCase().length; // 9
"İstanbul".toLocaleLowerCase("tr").length; // 8
Dil verilmeden küçültülen “İstanbul”, ekranda “istanbul” gibi görünse de aslında dokuz karakterdir ve ikinci karakter görünmez bir noktadır. Font bu birleşik noktayı iyi konumlandırmazsa i’nin üstünde iki nokta ya da kaymış bir nokta görürsünüz. Bu metni bir veritabanında “istanbul” ile karşılaştırırsanız eşleşmez; adres satırı (URL) üretirken kullanırsanız adreste fazladan bir kodlanmış karakter belirir.
JavaScript’te doğru yöntem
MDN’in toLocaleUpperCase() sayfası, Türkçe gibi büyük-küçük harf eşlemesi Unicode’un varsayılanından farklı olan dillerde bu yöntemin toUpperCase()tan farklı sonuç verebileceğini yazıyor ve doğrudan bu örneği gösteriyor:
const city = "istanbul";
city.toLocaleUpperCase("en-US"); // "ISTANBUL"
city.toLocaleUpperCase("TR"); // "İSTANBUL"
Aynı sayfa bir uyarı daha yapıyor: dönüşüm her zaman bire bir değildir, sonuç metnin uzunluğu girdiden farklı olabilir. Türkçe metinle çalışırken şu alışkanlıklar işinizi kolaylaştırır:
- Kullanıcıya görünen metni büyütüp küçültürken her zaman
toLocaleUpperCase("tr")vetoLocaleLowerCase("tr")kullanın. Dil kodunu parametre olarak verin; tarayıcının diline güvenmeyin, çünkü Türkçe sayfanızı İngilizce ayarlı bir tarayıcıyla açan ziyaretçide sonuç değişir. - Arama ve karşılaştırma için metni önce aynı dil koduyla küçültün, sonra karşılaştırın.
- Uzunluk sınırı olan alanlarda (ör. 20 karakterlik bir başlık) uzunluğu dönüşümden sonra ölçün.
- Adres satırı (slug) üretirken Türkçe harfleri bilinçli olarak eşleyin: ı → i, İ → i, ğ → g, ş → s gibi. Önce dil kodsuz küçültüp sonra aksanları silmeye çalışırsanız birleşik nokta (U+0307) arada kalabilir.
Arada kalmış noktayı yakalamak
Daha önce dil kodsuz küçültülmüş metinler bir veritabanına girmişse onları ayıklamanız gerekebilir. Birleşik nokta görünmediği için gözle bulunmaz, ama tek satırlık bir düzenli ifadeyle yakalanır:
const supheli = (s) => /i̇/.test(s);
supheli("İstanbul".toLowerCase()); // true
supheli("istanbul"); // false
Unicode normalleştirmesi bu sorunu çözmez. normalize("NFC") birleşik işaretleri mümkün olduğunda tek karakterde birleştirir, ama Unicode’da “noktalı küçük i” diye ayrı bir karakter olmadığı (küçük i zaten noktalıdır) için i ile U+0307 yan yana kalır; metnin uzunluğu yine 2’dir. Temizlik için U+0307’yi i’den sonra geldiği yerlerde silmek gerekir: s.replace(/i̇/g, "i"). Bu işlemi yalnız Türkçe ve Azerbaycan Türkçesi metinlerde yapın; Litvanca gibi bazı dillerde bu işaret anlamlıdır ve MDN’in örneklerinde de Litvanca için ayrı bir dönüşüm gösteriliyor.
CSS tarafında aynı sorunun karşılığı text-transform ve lang özniteliğidir; bunu CSS büyük harf yazımızda anlattık.
Türkçe sıralama
Alfabetik sıralama da dile bağlıdır. Türk alfabesinde c’den sonra ç, g’den sonra ğ, h’den sonra ı, ı’dan sonra i gelir. Varsayılan karakter kodu sıralaması bunu bilmez: kod noktası sırasında ç (U+00E7) z’den (U+007A) sonra, ı (U+0131) ise hepsinden sonra gelir. “Çanakkale” ve “İzmir” bu yüzden basit bir sort() ile Zonguldak’tan sonraya, listenin sonuna düşer.
MDN’in localeCompare() sayfası, bu yöntemin locales parametresinin Intl.Collator ile aynı işi yaptığını ve çok sayıda metni sıralarken bir Intl.Collator nesnesi oluşturup onun compare işlevini kullanmanın daha verimli olduğunu belirtiyor:
const tr = new Intl.Collator("tr");
["Zonguldak", "Çanakkale", "Isparta", "İzmir", "Cide"].sort(tr.compare);
Türkçe karşılaştırıcıyla sonuç “Cide, Çanakkale, Isparta, İzmir, Zonguldak” sırasındadır: ç, c’nin hemen ardından, I ile başlayan sözcük İ ile başlayandan önce gelir. Bizim font listemiz de aynı yöntemle çalışır; arama kutusuna “ı” ya da “İ” yazdığınızda eşleme Türkçe kurallara göre yapılır.
Fontla ilgisi ne?
Metin doğru dönüştürülse bile fontun İ ve ı harflerini çizmesi gerekir. 20 Ekim ölçümünde İ, Türkçesi eksik ailelerde en sık bulunmayan harf (553 aile); noktasız ı ise 148 ailede yok. Atkinson Hyperlegible ve Radley gibi bazı aileler Türkçenin bütün harflerini çiziyor ama yalnız İ’yi atlıyor; Rubik Mono One ve La Belle Aurore gibi bazıları ise yalnız ı’yı. Bu fontlarla doğru dönüştürülmüş metin bile yedek fonttan gelen tek bir harfle bozuk görünür. Hangi popüler fontlarda bu durumun yaşandığını Türkçesi eksik fontlar sayfasında listeledik.
Birleşik nokta (U+0307) ise ayrı bir sınavdır. Font bu işaretin konumunu i’nin üstüne göre ayarlamıyorsa, dil kodsuz küçültülmüş metinde nokta i’nin üstünde değil yanında durabilir. Sorunun kökü yazılımda olduğu için bunu fontu değiştirerek değil, dönüşümü doğru yaparak çözmek gerekir.