Ders 20 / 21
Yerelleştirme
Dil dizileri, yer tutucu, çoğul biçim ve yedek dil; istenen dilde anahtar yokken o dilin metninin bağlanmaması ve yedek dilin hatasız devreye girmesi, anahtar varken istenen dilin koşması ve yedek dilin eksik çeviriyi gizlemesi.
Yayın Güncelleme
Bu derste
- Dil dizilerinden yer tutucu ve çoğul biçimle metin kurar
- İstenen dilde anahtar yokken o dilin metninin bağlanmadığını ve yedek dilin hatasız devreye girdiğini ölçer
- Yedek dilin eksik çeviriyi gizlediğini ve hiçbir dilde olmayan anahtarın adıyla döndüğünü gösterir
İçindekiler
Zamanlanmış Görevler dersinde bir görevin ne zaman çalışacağını sorduk; çalışan görev bir kullanıcıya bildirim gönderiyorsa, bildirimin hangi dilde yazılacağı da bir seçimdir. Yanıtın metni koda gömülü olursa her dil için ayrı bir kod yolu gerekir. Metni kodun dışına, dile göre ayrılmış bir tabloya almak işi tersine çevirir: kod aynı kalır, değişen tablodur. Bu ders şunu sorar: istenen dilde bir metin yoksa yanıt ne olur?
Yerelleştirme (localization) bir uygulamanın metinlerini kullanıcının diline göre seçmektir. Metinler bir dil dosyasında durur: bu derste dil dosyası, anahtarları metne eşleyen bir PHP dizisidir. Metnin içindeki değişken kısımlar yer tutucu (placeholder) ile işaretlenir: :ad gibi bir parça, çağrı sırasında gelen değerle değiştirilir. Bir metnin sayıya göre değişen biçimlerine çoğul biçim (plural form) denir: “1 item” ve “3 items” aynı anahtarın iki biçimidir. İstenen dilde bir anahtar yoksa ikinci olarak bakılan dile yedek dil (fallback language) denir.
US10. Dil dosyası bir PHP dizisidir ve iki dil tanımlıdır: tr ve en. Yedek dil tr’dir ve tek düzeydir: istenen dilde anahtar yoksa yalnız yedek dile bakılır, yedeğin de yedeği yoktur. Anahtarlar düz adlardır; genel.bilinmeyen gibi bir ad nokta içerse de iç içe bir yapıyı ifade etmez. Dil kodu bölge parçası taşımaz, tarih, sayı ve para birimi biçimlendirmesi modellenmez, en dosyasında indirim anahtarı bilerek eksik bırakılmıştır.
US11. Çoğul biçim yalnızca iki durumu ayırır: sayı tam olarak 1 ise bir, değilse cok. Bu, İngilizce için doğrudur; çoğul kurallarının dilden dile çok daha karmaşık olduğu (sıfırın, ikinin ya da belli sayı sonlarının ayrı biçim alabildiği diller vardır) dersin modelinde yoktur. Türkçe sayıdan sonra ismi çoğullamaz, bu yüzden tr dosyasındaki iki biçim aynıdır. Yer tutucu :ad biçimindedir ve metindeki ilgili parça düz bir dize değişimiyle doldurulur; değer biçimlendirmesi ve kaçış modellenmez.
Ölçüm Çekirdeği
Kurs boyunca her derste aynı ölçüm kullanılır. kostu.php beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. Gunluk üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. sinifla() bunlara sırayla bakar. Karar sırası sinifla()’nın kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır. Bu derste bir parça bir metindir ve adı dil/anahtar biçimindedir: en/indirim, “İngilizce dosyadaki indirim metni bu istekte yanıta girdi mi” sorusunu ölçer. Metin kullanıldığında hem yola() hem kostu() çağrılır; kullanılmadığında hiçbiri çağrılmaz.
<?php // kostu.php — beklenen bir parça bu istekte koştu mu? declare(strict_types=1); final class Gunluk { public static array $yol = []; // isteğin yoluna konan parçalar public static array $kostu = []; // istek içinde çağrılanlar public static array $ertelenen = []; // isteğin dışına bırakılanlar public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; } public static function yola(string $parca): void { self::$yol[] = $parca; } public static function kostu(string $parca): void { self::$kostu[] = $parca; } public static function ertele(string $parca): void { self::$ertelenen[] = $parca; } } function sinifla(string $parca): string { return match (true) { in_array($parca, Gunluk::$kostu, true) => 'koştu', in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi', in_array($parca, Gunluk::$yol, true) => 'atlandı', default => 'bağlanmadı', }; }
Dil Dizileri, Yer Tutucu ve Yedek Dil
yerellestirme.php üç parça tanımlar. Ceviri::al() istenen dili ve yedek dili sırayla dener: ilk bulduğunda parçayı yola koyar, koştu olarak işaretler ve metni doldurur; ikisinde de bulamazsa anahtarın kendisini döndürür. doldur() metin bir diziyse sayıya göre bir ya da cok biçimini seçer, sonra yer tutucuları strtr ile değiştirir. dilDizileri() iki dil dosyasını verir. SepetDenetleyicisi ise bir dil, bir ad, bir sayı ve bir anahtar listesi alır, her anahtarı çevirir ve parçaları tek bir yanıta birleştirir.
Bu yapıda denetleyici hiçbir dilin metnini bilmez. Dil seçimi denetleyiciye bir dize olarak gelir ve çözümleme tamamen Ceviri’dedir. Yedek dil Ceviri’ye kurulurken verilir; yani yedeği değiştirmek denetleyiciyi değiştirmez.
<?php // yerellestirme.php — dil dizileri, yer tutucu, çoğul biçim ve yedek dil declare(strict_types=1); require_once 'kostu.php'; final class Ceviri { public function __construct(private array $diller, private string $yedek) {} public function al(string $dil, string $anahtar, array $degerler = []): string { foreach ([$dil, $this->yedek] as $aday) { if (!isset($this->diller[$aday][$anahtar])) { continue; } Gunluk::yola("$aday/$anahtar"); Gunluk::kostu("$aday/$anahtar"); return $this->doldur($this->diller[$aday][$anahtar], $degerler); } return $anahtar; } private function doldur(string|array $metin, array $degerler): string { if (is_array($metin)) { $metin = ($degerler['sayi'] ?? 0) === 1 ? $metin['bir'] : $metin['cok']; } $yerTutucular = []; foreach ($degerler as $ad => $deger) { $yerTutucular[':' . $ad] = (string) $deger; } return strtr($metin, $yerTutucular); } public function anahtarlar(string $dil): array { return array_keys($this->diller[$dil]); } } function dilDizileri(): array { return [ 'tr' => [ 'karsilama' => 'Merhaba, :ad', 'sepet' => ['bir' => ':sayi ürün', 'cok' => ':sayi ürün'], 'indirim' => 'Sepette indirim var', ], 'en' => [ 'karsilama' => 'Hello, :ad', 'sepet' => ['bir' => ':sayi item', 'cok' => ':sayi items'], ], ]; } final class SepetDenetleyicisi { public function __construct(private Ceviri $ceviri) {} public function goster(string $dil, string $ad, int $sayi, array $anahtarlar): array { Gunluk::yeniIstek(); $satirlar = []; foreach ($anahtarlar as $anahtar) { $satirlar[] = $this->ceviri->al($dil, $anahtar, ['ad' => $ad, 'sayi' => $sayi]); } return [200, implode(' | ', $satirlar)]; } }
İstenen Dilin Metni Bağlanmadı
Sürücü en dilinde yalnız indirim anahtarını ister. en dosyasında bu anahtar bilerek yoktur. Yanıttan sonra iki parçanın sınıfı yazılır: İngilizce dosyadaki indirim metni ve Türkçe dosyadaki aynı metin.
<?php // yedekdil.php — istenen dilde anahtar yok: yedek dil devreye girer require_once 'yerellestirme.php'; $denetleyici = new SepetDenetleyicisi(new Ceviri(dilDizileri(), 'tr')); $yanit = $denetleyici->goster('en', 'Ada', 3, ['indirim']); printf("goster(dil: en, indirim) -> %d %s\n", $yanit[0], $yanit[1]); foreach (['en/indirim', 'tr/indirim'] as $parca) { printf(" %-11s %s\n", $parca, sinifla($parca)); }
goster(dil: en, indirim) -> 200 Sepette indirim var en/indirim bağlanmadı tr/indirim koştu
İstek hata vermedi; 200 döndü ve yanıt Türkçe metni taşıdı. en/indirim bağlanmadı: İngilizce dosyada böyle bir metin yoktur, bu yüzden yola hiç girmedi. tr/indirim ise koştu: yedek dil devreye girdi ve metni yanıta soktu. Dersin ölçü sınıfı bu yüzden bağlanmadı olarak okunur, ama bu bir hata değil, bir yoldur: kullanıcı, istediği dilde olmayan bir metin yerine başka bir dilde bir metin görür ve uygulama çalışmaya devam eder.
Burada “atlandı” demek yanlış olurdu. Atlanan bir parça yola konmuş ama çağrılmamış bir parçadır; en/indirim ise yola hiç konmadı, çünkü çözümleme dosyada bulamadığı bir metni yola koymaz. Metin bağlanamadı, koşulun sonucuyla geçilmedi.
Karşıt: Anahtar Varken İstenen Dil Koştu
Aynı denetleyiciye bu kez iki dilde de bulunan karsilama ve sepet anahtarları verilir. Sürücü üç istek yapar: en dilinde bir ürünle, en dilinde üç ürünle ve tr dilinde üç ürünle. Her istekten sonra iki dildeki karsilama metninin sınıfı yazılır.
<?php // anahtarvar.php — anahtar istenen dilde varken o dilin metni koşar require_once 'yerellestirme.php'; $denetleyici = new SepetDenetleyicisi(new Ceviri(dilDizileri(), 'tr')); foreach ([['en', 1], ['en', 3], ['tr', 3]] as [$dil, $sayi]) { $yanit = $denetleyici->goster($dil, 'Ada', $sayi, ['karsilama', 'sepet']); printf("goster(dil: %s, sayi: %d) -> %d %s\n", $dil, $sayi, $yanit[0], $yanit[1]); foreach (['en/karsilama', 'tr/karsilama'] as $parca) { printf(" %-13s %s\n", $parca, sinifla($parca)); } }
goster(dil: en, sayi: 1) -> 200 Hello, Ada | 1 item en/karsilama koştu tr/karsilama bağlanmadı goster(dil: en, sayi: 3) -> 200 Hello, Ada | 3 items en/karsilama koştu tr/karsilama bağlanmadı goster(dil: tr, sayi: 3) -> 200 Merhaba, Ada | 3 ürün en/karsilama bağlanmadı tr/karsilama koştu
Anahtar istenen dilde olduğunda istenen dilin metni koştu, yedek dilinki ise bağlanmadı. Yani indirim örneğinin tersi: yedek dil çözümlemeye hiç girmedi, çünkü ilk denenen dil yeterliydi. Yanıtlar iki şeyi birlikte gösterir. Çoğul biçim sayıya göre seçilir: en dilinde 1 ürün 1 item, 3 ürün 3 items oldu; tr dilinde ise 3 ürün, çünkü Türkçe dosyada iki biçim aynıdır. Yer tutucu da her istekte doldu: :ad her dilde Ada oldu, :sayi ise çağrıdan gelen sayıyı aldı.
İki dilin sınıfları birbirinin aynasıdır: bir dil koşunca öbürü bağlanmaz. Bu, yedek dilin yalnızca istenen dil başarısız olduğunda devreye girdiği anlamına gelir.
Sınırlayıcı Ölçüm
Yedek dil kullanıcıyı hatadan korur, ama aynı zamanda eksik çeviriyi gizler. eksikdil.php önce Türkçe dosyadaki bütün anahtarları en diliyle ister ve hangilerinin İngilizce dosyada bağlanmadığını, yani yedekten geldiğini listeler. Sonra hiçbir dilde bulunmayan bir anahtar ister.
<?php // eksikdil.php — yedek dil eksik çeviriyi gizler; hiçbir dilde olmayan anahtar adıyla döner require_once 'yerellestirme.php'; $ceviri = new Ceviri(dilDizileri(), 'tr'); $denetleyici = new SepetDenetleyicisi($ceviri); $anahtarlar = $ceviri->anahtarlar('tr'); $denetleyici->goster('en', 'Ada', 2, $anahtarlar); $eksik = array_filter($anahtarlar, fn(string $k) => sinifla("en/$k") === 'bağlanmadı'); printf("en isteği: %d anahtar, yedekten gelen: %s\n", count($anahtarlar), implode(', ', $eksik)); $yanit = $denetleyici->goster('en', 'Ada', 2, ['genel.bilinmeyen']); printf("hiçbir dilde yok -> %d %s\n", $yanit[0], $yanit[1]); foreach (['en/genel.bilinmeyen', 'tr/genel.bilinmeyen'] as $parca) { printf(" %-19s %s\n", $parca, sinifla($parca)); }
en isteği: 3 anahtar, yedekten gelen: indirim hiçbir dilde yok -> 200 genel.bilinmeyen en/genel.bilinmeyen bağlanmadı tr/genel.bilinmeyen bağlanmadı
İlk satır, 200 yanıtının arkasında bir çeviri borcu bulunduğunu gösterir: üç anahtarın biri yedekten geldi. Yanıt dışarıdan tam görünür; eksik ancak ölçümle, yani hangi parçanın bağlanmadığına bakarak fark edilir. Bu bir test ya da denetim kurallarının konusudur: yedek dil kullanıcı deneyimini korur, ama bir çevirinin eksik olduğunu söylemez.
İkinci kısım yedeğin de yetmediği durumu gösterir. Anahtar iki dilde de olmadığı için Ceviri anahtarın adını döndürdü; yanıt yine 200’dür ve iki parça da bağlanmadı. Kullanıcı yanıtta genel.bilinmeyen yazısını görür. Bu modelde hata atılmaz; başka bir uygulama hata atmayı ya da boş metin döndürmeyi seçebilir. Ölçüm bu seçimlerin hangisinin doğru olduğunu söylemez, yalnız bu modelde ne olduğunu gösterir.
Özet
- Bu dersin ölçü sınıfı bağlanmadı:
endilindeindirimanahtarı yoktu,en/indirimyola girmedi, yedek diltr/indirimmetnini hatasız yanıta soktu. - Dil dosyası bir PHP dizisidir; metin yer tutucuyla (
:ad) ve sayıya görebirya dacokbiçimiyle kurulur. - Karşıt: anahtar istenen dilde varken o dilin metni koştu ve yedek dilinki bağlanmadı; çoğul biçim
1 itemve3 itemsolarak ayrıldı. - Sınırlayıcı ölçümde yedek dil eksik çeviriyi gizledi: üç anahtarın biri yedekten geldi, yanıt yine 200 kaldı.
- Hiçbir dilde olmayan anahtar adıyla döndü ve iki dilin metni de bağlanmadı.
Sonraki Adım
Bu kursta her derste bir parçanın koşup koşmadığını ölçtük: kimlik doğrulama, kuyruk, olay, bildirim, önbellek, dosya, zamanlayıcı ve dil. Her biri farklı bir yoldan geldi ve her birinin kendine ait bir karşıtı vardı. Peki bu ölçümleri bir sürücü ve elle okunan bir çıktı yerine, her değişiklikten sonra kendiliğinden doğrulayan bir şeye çevirebilir miyiz? Kursun son dersi, bunların hepsinin bir istekten yanıta ve tek tek birimler düzeyinde nasıl doğrulanacağını sorar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.