Tüm yazılar Mobil Geliştirme

Türkçe "i" Harfi Yüzünden Kaybettiğim İki Gün

Uygulama benim telefonumda çalışıyor, test cihazında çalışıyor, ama bazı kullanıcılarda hiç açılmıyordu. Sebebi bulmam iki gün sürdü ve tek bir harften ibaretti.

DT
Demir Taşdemir Mobil Uygulama & Web Geliştirici
— dk okuma

Android tarafında yazdığım bir uygulamada tuhaf bir hata raporu almaya başladım. Uygulama bazı kullanıcılarda açılır açılmaz çöküyordu. Benim telefonumda sorun yoktu. Emülatörde sorun yoktu. Test ettiğim iki cihazda da sorun yoktu.

Çökme raporlarına baktığımda ortak bir nokta göremedim: farklı cihazlar, farklı Android sürümleri, farklı ekran boyutları. İki gün sonra fark ettim ki ortak nokta cihazda değildi — cihazın dil ayarındaydı.

Türkçenin dört "i" harfi

Çoğu dilde i harfinin büyüğü I'dır. Türkçede öyle değil. Türkçede noktalı ve noktasız olmak üzere iki ayrı harf var ve her birinin kendi büyük hâli var:

KüçükBüyükNot
iİNoktalı i — büyüğünde de nokta var
ıINoktasız ı — büyüğünde nokta yok

Bu, dil bilgisi açısından doğru. Sorun şu ki işletim sistemi bu kuralı cihazın dili Türkçe olduğunda her metne uyguluyor — kullanıcıya gösterilen metne de, kodun içindeki teknik dizgelere de.

Hatanın kendisi

Kodda şuna benzer bir karşılaştırma vardı:

String tip = sunucudanGelenDeger;   // örneğin "IMAGE"

if (tip.toLowerCase().equals("image")) {
    gorseliGoster();
}

Cihaz dili İngilizce olduğunda "IMAGE".toLowerCase() sonucu "image" oluyor ve koşul sağlanıyor. Cihaz dili Türkçe olduğunda ise sonuç "ımage"noktasız ı ile. Koşul sağlanmıyor, akış beklenmedik bir dala giriyor ve uygulama çöküyor.

Bu hatanın en sinsi tarafı

Geliştirici genelde cihazını İngilizce kullanır. Yani hata sizin makinenizde hiçbir zaman çıkmaz. Sadece kullanıcılarda çıkar ve raporlarda tutarsız görünür.

Nerelerde patlıyor?

Bir kez farkına vardıktan sonra kodun her yerinde görmeye başladım. Risk taşıyan yerler:

  • Dosya uzantısı kontrolü: "FOTO.PNG".toLowerCase()".pnğ" değil ama "IMG" içeren her kontrol bozulur.
  • Sunucudan gelen tip/durum alanları: "ACTIVE", "PENDING", "INFO" gibi değerlerin karşılaştırılması.
  • HTTP başlıkları: "Content-Type" gibi başlık adlarını küçültüp karşılaştırmak.
  • Arama ve filtreleme: kullanıcı "istanbul" yazınca "İstanbul" bulunmuyorsa sebebi budur.
  • E-posta karşılaştırması: büyük harfli e-posta adreslerinin küçültülerek eşleştirilmesi.
  • Renk ve stil adları: "LIGHT", "DARK" gibi değerlerin normalleştirilmesi.

Doğru çözüm: makine dizgesi mi, insan metni mi?

Çözüm "Türkçeyi devre dışı bırakmak" değil. Doğru soru şu: bu metin bir insana mı gösterilecek, yoksa makinenin karşılaştırması için mi kullanılacak?

  • İnsana gösterilecekse — başlık, isim, şehir adı — cihazın dilini kullanın. Kullanıcı "İSTANBUL" görmeli, "ISTANBUL" değil.
  • Makine karşılaştıracaksa — tip alanı, anahtar, uzantı, başlık adı — dilden bağımsız (invariant) dönüşüm kullanın.

Java ve Kotlin tarafında düzeltilmiş hâli:

// YANLIŞ — cihazın diline göre davranır
tip.toLowerCase()

// DOĞRU — dilden bağımsız, her cihazda aynı sonuç
tip.toLowerCase(Locale.ROOT)

// Kullanıcıya gösterilecek metinde ise cihazın dili doğru tercih
baslik.uppercase(Locale.getDefault())

Swift ve JavaScript tarafında durum biraz farklı ama tuzak aynı:

// Swift — lowercased() zaten dilden bağımsızdır, güvenli
tip.lowercased()

// Ama bu locale'e duyarlıdır, makine karşılaştırmasında kullanmayın
tip.lowercased(with: Locale.current)


// JavaScript — toLowerCase() dilden bağımsızdır, güvenli
tip.toLowerCase()

// Bu ise cihazın diline göre davranır
tip.toLocaleLowerCase()
Kalıcı çözüm

Makine karşılaştırmalarını metin üzerinden yapmayı bırakıp enum kullanmak bu sınıf hatayı tamamen ortadan kaldırıyor. Dizge karşılaştırması kaçınılmazsa bile, dönüşümü tek bir yardımcı fonksiyonda toplayıp her yerde onu çağırmak hem güvenli hem de denetlenebilir oluyor.

Nasıl test edilir?

Bu hatayı yakalamanın en hızlı yolu, test cihazının dilini Türkçe yapıp uygulamayı baştan sona bir kez gezmek. Otomatik testlerde ise dili zorlayarak çalıştırmak mümkün:

// Testte varsayılan dili geçici olarak Türkçe yap
Locale.setDefault(new Locale("tr", "TR"));

Çok dilli bir uygulama geliştiriyorsanız bu testi sürüm öncesi kontrol listesine eklemek, iki günlük bir hata avını on dakikalık bir kontrole indiriyor.

Özet

Bu hata bana iki şey öğretti. Birincisi teknik: büyük/küçük harf dönüşümü masum bir işlem değil, kullanıcının diline bağlı bir davranış. İkincisi daha genel: "benim makinemde çalışıyor" cümlesi bir teşhis değil, sadece bir gözlem.

Yalnızca belirli kullanıcılarda çıkan hataların ortak noktası genelde cihazda değil, o kullanıcıların ayarlarında oluyor. Dil, saat dilimi ve bölge — hata ararken ilk bakılacak üç yer.

  • Locale
  • Türkçe i
  • Java
  • Kotlin
  • Hata ayıklama
  • i18n
Paylaş: LinkedIn X WhatsApp
DT

Demir Taşdemir

Mobil Uygulama & Web Geliştirici

2018'den beri yazılım geliştiriyorum. App Store ve Google Play'de 11 uygulama yayınladım; şu an 6 mobil uygulama, 1 e-ticaret platformu ve 1 masaüstü oyun üzerinde çalışıyorum.

Böyle hataları avlamayı seven biri

Yalnızca belirli kullanıcılarda çıkan hatalar en öğretici olanlar. Bu tarz problemleri kovalamayı seviyorum.