Heceleme sözlüğü olmadan iki yana yaslı Türkçe metin
Chrome'da Türkçe heceleme sözlüğü yok, bu yüzden iki yana yaslı Türkçe sütunda boşluk nehirleri açılıyor. Çözüm: build sırasında yumuşak tire.
text-align: justify ile dizilmiş bir Türkçe sütun, nedenini söyleyemeden önce yanlış görünür. Kelime araları satırdan satıra farklıdır ve birkaç satırda bir bu boşluklar üst üste gelip paragrafın içinden aşağı inen soluk bir kanal oluşturur. CSS doğrudur, hyphens: auto da yazılmıştır. Chrome hiçbir Türkçe kelimeyi bölmez, çünkü elinde Türkçe heceleme sözlüğü yoktur ve Türkçe kelimeler uzundur.
Gerçekte ne oluyor
Yaslama, satırın sonunda artan boşluğu alıp o satırın kelime aralarına dağıtır. Satırda on kelime varsa artan boşluk dokuza bölünür ve kimse fark etmez. Uzun bir kelime alt satıra itilmişse ve geriye dört kelime kalmışsa aynı boşluk üçe bölünür, her ara üç katına çıkar.
Bunu normalde heceleme engeller. Tarayıcı bir kelimeyi satır sonunda bölebilir, ama yalnızca dilin izin verdiği yerden, ve bunu dil başına yüklenen desen dosyalarından öğrenir. Dili metindeki karakterlerden değil, elementteki lang özniteliğinden okur. Chrome bu desen dosyalarını sınırlı bir dil listesi için yüklüyor ve Türkçe bu listede hiç olmadı. Safari sistem sözlüklerine yaslandığı için Apple tarafında sonuç daha iyi. Tek bir motorda çalışan bir davranışın üzerine sütun tasarlayamazsınız.
Türkçede aralar İngilizcede olacağından daha kötü açılır, çünkü dil eklemeli. Ekler üst üste binince günlük bir kelime rahatlıkla on altı yirmi harf olur. Yanlış anda gelen böyle bir kelime satırı boşaltır.
Aynı özelliğin kimsenin kullanmadığı ikinci bir modu var. hyphens: manual zaten varsayılandır ve "kelimeyi yalnızca yumuşak tirenin, yani U+00AD'nin olduğu yerde böl" demektir. İşin içinde sözlük yoktur. Bütün motorlar bunu yıllardır destekler. Tek eksik, Türkçe hecelerin nerede bittiğini bilen bir şey.
Nasıl görülür
Dar bir kutuya hyphens: auto ile tek paragraf Türkçe koyun ve satır sayısını iki farklı lang değeriyle sayın. Motorda Türkçe desenler olsaydı sayı değişirdi:
const box = document.querySelector('#probe');
const lineHeight = parseFloat(getComputedStyle(box).lineHeight);
const lines = (lang) => {
box.lang = lang;
return Math.round(box.getBoundingClientRect().height / lineHeight);
};
console.log('tr', lines('tr'), 'en', lines('en'));
// Chrome: tr 14 en 13İngilizce desenler Türkçe için yanlıştır, yani lang="en" kötü bölme üretir, ama bölme üretir. Kod yolunun çalıştığını ve eksik olanın yalnızca Türkçe veri olduğunu gösteren kanıt budur. Bu etkiyi elde etmek için Türkçe metne lang="en" koymayın: ekran okuyucunun telaffuzunu ve dil algılamasını bozar, ki bu önce düzeltmeye değer erişilebilirlik hatalarından biridir.
İkinci deney, çözümün küçültülmüş hali. Tireleri elle koyun ve bölünmeyi izleyin:
<p style="width: 7ch; text-align: justify">de­ğer­len­dir­me</p>Türkçe hece bir kuraldır, sözlük değil
Türkçe hecede tam olarak bir ünlü bulunur ve iki hece arasındaki sınırı yalnızca iki ünlü arasında kaç ünsüz olduğu belirler:
- art arda iki ünlü aralarından bölünür: V-V
- iki ünlü arasındaki tek ünsüz sonraki heceye gider: V-CV
- iki ünsüz ortadan ayrılır: VC-CV
- üç ünsüzde ilk ikisi kalır, sonuncusu öne geçer: VCC-CV
Dördü de aynı cümleye iner: kümenin son ünsüzü sonraki heceyi başlatır. Bu bir döngüdür, sözlük değil.
const VOWELS = new Set([...'aeıioöuüâîûAEIİOÖUÜÂÎÛ']);
const SHY = '';
function syllabify(word) {
const chars = [...word];
const isVowel = chars.map((c) => VOWELS.has(c));
const breaks = [];
let prev = -1;
for (let i = 0; i < chars.length; i++) {
if (!isVowel[i]) continue;
if (prev >= 0) {
const cluster = i - prev - 1; // önceki ünlüden beri gelen ünsüz sayısı
breaks.push(cluster === 0 ? i : i - 1); // ünlüden önce ya da son ünsüzden önce
}
prev = i;
}
// kelimenin iki ucunda da bir iki harfi yalnız bırakma
const ok = new Set(breaks.filter((b) => b >= 2 && chars.length - b >= 2));
return chars.map((c, i) => (ok.has(i) ? SHY + c : c)).join('');
}Çözüm
Heceleyiciyi kelime kelime dolaşan bir geçişe sarın, kısa kelimeleri atlayın ve bir istisna listesi tutun:
const LETTER = /[A-Za-zÇçĞğİıÖöŞşÜüÂâÎîÛû]/;
// ödünç ünsüz kümesi taşıyan alıntılar ve çevrilmeden kullanılan İngilizce terimler
const SKIP = new Set(['elektrik', 'kontrol', 'santral', 'backend', 'request', 'blockchain']);
export function hyphenate(text) {
let out = '';
let word = '';
const flush = () => {
const w = word.length >= 7 && !SKIP.has(word.toLocaleLowerCase('tr')) ? syllabify(word) : word;
word = '';
return w;
};
for (const ch of text) {
if (LETTER.test(ch)) word += ch;
else out += flush() + ch;
}
return out + flush();
}toLocaleLowerCase içindeki locale argümanı süs değil. Onu yazmazsanız sonuç build'in koştuğu makineye bağlı olur, yani büyük harf i tuzağı arka kapıdan içeri girer.
Bu geçişi build sırasında, içerik alanları şablona uğramadan önce çalıştırın. CSS de sıkıcı kalır:
.prose {
text-align: justify;
hyphens: manual; /* varsayılan: yalnızca yumuşak tirede böl */
overflow-wrap: break-word;
}Maliyeti küçük ama söylemeye değer. Bir sayfada bu geçiş 295 yumuşak tire ekledi. Ham haliyle 590 bayt, gzip sonrası 256, brotli sonrası 212 bayt. Türkçe karakterler başka yerlerde, özellikle de bir SMS'in kodlanmasında pahalıdır, ama HTML yanıtında sıkıştırıcının içinde kaybolurlar.
Çalıştığını nasıl doğrularsınız
Çıktıya ne düştüğünü sayın ve hiçbirinin yapısal bir yere düşmediğini doğrulayın:
node -e 'const s = require("node:fs").readFileSync(process.argv[1], "utf8");
console.log("yumusak tire:", [...s].filter((c) => c === "").length);
console.log("tag icinde :", (s.match(/<[^>]*[^>]*>/g) || []).length);' dist/tr/index.html
# yumusak tire: 295
# tag icinde : 0Asıl önemli olan ikinci sayı. Sıfırdan farklıysa geçiş metnin değil HTML'in üzerinden koşmuş demektir ve artık bir öznitelik değerinin ya da bir tag adının içinde yumuşak tire vardır.
Sonra sayfayı yenileyin ve bir paragrafın sağ kenarına bakın. Ardışık satırlardaki kelime araları aynı genişlikte görünüyorsa ve dört satırda bir tire çıkıyorsa iş tamamdır.
Sağı ne zaman serbest bırakmalı
Yaslama bedava değil ve dürüst cevabın "vazgeç" olduğu durumlar var:
- Sütun altmış karakterden dar. Artan boşluğun saklanacak yeri yoktur, tek başına heceleme de kurtarmaz.
- Metni kullanıcı giriyor. Metnin Türkçe olduğunu garanti edemezsiniz; Türkçe hece kuralını bir Almanca ya da Arapça isim üzerinde çalıştırmak, markup'a kalıcı olarak yanlış yerden bölünmüş bir kelime yazar.
- İçerikte bölünemeyen uzun parçalar var: URL, identifier, dosya yolu. Bunlar ne yaparsanız yapın yaslamayı bozar.
- Build adımı çalıştıramıyorsunuz. Tireleri tarayıcıda, boyamadan sonra eklemek bütün sütunu yeniden akıtır ve düzensiz bir kenardan çok daha göze batar.
Nelere dikkat etmeli
- Bazı tarayıcılarda yumuşak tire kopyalanan metinle birlikte gider. Kod örneklerinin, komutların, e-posta adreslerinin ve sayıların içine girmesin.
- Site içi aramanız varsa indekslemeden ve eşleştirmeden önce U+00AD'yi temizleyin, yoksa bir kelimeyi arayan kullanıcı aynı kelimenin sayfadaki tireli halini bulamaz.
- Kural ödünç kümelerde yanılır. Kötü bir bölme gördüğünüzde kurala fonotaktik öğretmeye çalışmak yerine kelimeyi istisna listesine ekleyin. Liste birkaç düzine satırda kalır ve okunur; zekice versiyonu ikisi de olmaz.
- Geçişi iki kez uygulamayın. Zaten tirelenmiş metnin üzerinden geçmenin zararsız olmasının tek sebebi yumuşak tirenin harf sayılmaması, bu da güvenilecek bir kaza değil.
Otomatik heceleme bir tarayıcı özelliği gibi görünür, aslında bir veri meselesidir ve küçük diller için o veri geç gelir ya da hiç gelmez. Platformun dilinizle ilgili cevabı yoksa, dilin kendisinde kodlanacak kadar basit bir kural olup olmadığına bakmaya değer; yirminci yüzyılda düzenlenmiş yazım sistemlerinde genellikle vardır. Yirmi satırlık bir build adımı, hiçbir CSS'in üretemeyeceği bir dizgi özelliğini satın aldı. Aynı soruyu her "desteklenmiyor" cevabından önce sormakta fayda var: kim tarafından desteklenmiyor ve altındaki kural gerçekten o kadar zor mu.
Sorular ve cevaplar
- hyphens: auto neden Türkçe metinde hiçbir şey yapmıyor?
- Otomatik heceleme dile özel desen dosyalarına ihtiyaç duyar ve tarayıcı bu dosyayı elementin lang özniteliğine bakarak seçer. Chrome bu desenleri sınırlı bir dil listesi için yüklüyor ve Türkçe o listede değil, yani özellik kabul ediliyor ama elinde çalışacak veri olmuyor. Aynı paragrafı lang="en" yapıp satır sayısının değiştiğini görerek kanıtlayabilirsiniz.
- Yumuşak tireyi HTML'e koymak güvenli mi?
- Evet. U+00AD satır orada bölünene kadar görünmez ve UTF-8'de iki bayt tutar, sıkıştırmadan sonra neredeyse hiç yer kaplamaz. Risk render tarafında değil, sonrasında: arama indeksi, kopyalanan metin ve string karşılaştırmaları okuyucunun hiç görmediği bir karakteri görür. İndekslemeden önce temizleyin; kod, URL ve kopyalanacak hiçbir şeyin içine koymayın.
- Kural tabanlı heceleyici hangi kelimelerde yanılır?
- Türkçenin normalde hece başında kabul etmediği ünsüz kümelerini taşıyan alıntı kelimelerde. Kural elektrik'i elekt-rik, kontrol'ü kont-rol diye böler, oysa okuyucu e-lek-trik ve kon-trol bekler. Yirmi otuz kelimelik bir liste, bir de çevirmeden kullandığınız İngilizce terimler, pratikte karşılaşacağınız her durumu kapatır.
- Telefonda metni iki yana yaslamalı mıyım?
- Genelde hayır. Yaslama ancak satır artan boşluğu emecek kadar uzunsa iyi görünür, bu da kabaca altmış karakter ve üstü demek. Dar sütunda sağı serbest bırakın: satır içindeki düzensiz kelime aralığı, düzensiz bir sağ kenardan çok daha rahatsız edicidir.