Mehmet Fatih İçin
EN İletişim

React Native Uygulamayı 14 Dile Yerelleştirmek: i18n, Çoğul Kuralları, RTL ve Mağaza Sayfaları

Mehmet Fatih İçin · · 10 dk okuma

Bir mobil uygulamayı tek dilde yayınlamak, potansiyel kullanıcıların büyük kısmını baştan dışarıda bırakmak demektir. Kendi uygulamalarımı 12 ile 14 dil arasında yayınlıyorum ve bu süreçte öğrendiğim en önemli şey şu oldu: yerelleştirme, metinleri çevirmekten ibaret değildir. Çoğul ekleri, tarih ve para biçimleri, sağdan sola yazılan diller, harf dönüşümleri, mağaza sayfaları ve ekran görüntüleri ayrı ayrı ele alınması gereken konulardır. Bu yazıda React Native ile çok dilli bir uygulamayı uçtan uca nasıl kurduğumu anlatıyorum.

1. Kütüphane seçimi: i18next ve expo-localization

React Native ekosisteminde en olgun seçenek i18next ile react-i18next ikilisidir. Geniş topluluk desteği, çoğul kuralları, iç içe anahtarlar, bağlam (context) ve tembel yükleme gibi ihtiyaç duyacağınız hemen her özelliği sunar. Cihazın dilini ve bölge ayarlarını okumak için Expo kullanıyorsanız expo-localization, saf React Native projelerinde react-native-localize iyi çalışır.

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { getLocales } from "expo-localization";
import tr from "./locales/tr.json";
import en from "./locales/en.json";
import ar from "./locales/ar.json";

const DESTEKLENEN = ["tr", "en", "de", "fr", "es", "ar", "ru"];
const cihazDili = getLocales()[0]?.languageCode ?? "en";

i18n.use(initReactI18next).init({
  resources: { tr: { translation: tr }, en: { translation: en }, ar: { translation: ar } },
  lng: DESTEKLENEN.includes(cihazDili) ? cihazDili : "en",
  fallbackLng: "en",
  interpolation: { escapeValue: false },
  returnNull: false
});

export default i18n;

Burada iki karar önemli. Birincisi, desteklenmeyen bir dilde cihaz açılırsa uygulamanın hangi dile düşeceği (fallbackLng). İkincisi, kullanıcının uygulama içinden dili değiştirebilmesi. Cihaz dilini varsayılan almak iyi bir başlangıçtır ama birçok kullanıcı telefonunu bir dilde, uygulamayı başka bir dilde kullanmak ister. Seçilen dili kalıcı depolamaya yazıp açılışta oradan okumak gerekir. iOS'ta ayrıca uygulamaya özel dil ayarı sistem ayarlarından da yapılabilir; bunun için desteklenen dillerin proje yapılandırmasında tanımlı olması gerekir.

2. Anahtar düzeni: metni değil anlamı adlandırın

Çeviri dosyalarında anahtar olarak İngilizce cümlenin kendisini kullanmak kısa vadede pratik görünür ama metin değiştiğinde tüm dillerdeki anahtar da değişir. Ekran ve anlama göre gruplanmış anahtarlar uzun vadede çok daha kolay yönetilir:

{
  "habits": {
    "empty": "Henüz alışkanlık eklemedin.",
    "add": "Alışkanlık ekle",
    "streak_one": "{{count}} günlük seri",
    "streak_other": "{{count}} günlük seri"
  },
  "settings": {
    "language": "Dil"
  }
}

Cümleleri parçalayıp kod içinde birleştirmekten kaçının. “Bugün” + sayı + “görev tamamladın” gibi birleştirmeler, kelime sırası farklı olan dillerde bozuk cümleler üretir. Değişkenleri cümlenin içine yer tutucu olarak koymak ve sırayı çevirmene bırakmak gerekir.

3. Çoğul kuralları: her dil “bir ve çok” değildir

Türkçede sayıdan sonra isim tekil kalır: “3 gün”. İngilizcede iki biçim vardır: “1 day”, “3 days”. Rusçada ve Lehçede sayının son basamağına göre üç biçim, Arapçada altı biçim bulunur. i18next, Unicode CLDR kurallarına dayanan Intl.PluralRules ile bu kategorileri otomatik seçer; çevirmenin yapması gereken, o dilin ihtiyaç duyduğu kategorileri doldurmaktır.

// ru.json
"streak_one":  "{{count}} день подряд",
"streak_few":  "{{count}} дня подряд",
"streak_many": "{{count}} дней подряд",
"streak_other":"{{count}} дня подряд"

// kullanım
t("habits.streak", { count: 21 })

Hermes motorunda Intl.PluralRules desteği güncel React Native sürümlerinde mevcuttur; eski bir sürüm kullanıyorsanız bir polyfill eklemeniz gerekebilir. Uygulamayı her dilde 0, 1, 2, 5, 11, 21 ve 101 gibi değerlerle test etmek, çoğul hatalarını yakalamanın en hızlı yoludur.

4. Tarih, saat, sayı ve para: Intl'e bırakın

Tarih biçimini elle yazmak (“GG.AA.YYYY”) yerelleştirmenin en yaygın hatalarındandır. Amerika'da ay önce gelir, birçok Asya ülkesinde yıl önce gelir, haftanın ilk günü ülkeden ülkeye değişir. JavaScript'in yerleşik Intl nesnesi bu kuralların tamamını bilir:

const dil = i18n.language;

new Intl.DateTimeFormat(dil, { dateStyle: "medium" }).format(tarih);
// tr: 4 Kas 2026   en: Nov 4, 2026   de: 04.11.2026

new Intl.NumberFormat(dil, { style: "currency", currency: "TRY" }).format(1250.5);
// tr: ₺1.250,50   en: TRY 1,250.50

new Intl.RelativeTimeFormat(dil, { numeric: "auto" }).format(-1, "day");
// tr: dün   en: yesterday

Para biriminde dikkat edilmesi gereken bir nokta var: dil ile para birimi aynı şey değildir. Almanya'da yaşayan bir Türk kullanıcı arayüzü Türkçe, para birimini Euro olarak görmek isteyebilir. Para birimini dilden değil, cihazın bölge ayarından ya da kullanıcının seçiminden almak daha doğrudur. Harcama takibi gibi uygulamalarda bunu ayrı bir ayar olarak sunmak gerekir.

5. Türkçe İ ve ı tuzağı

Türkçe, büyük-küçük harf dönüşümünde özel kuralları olan birkaç dilden biridir. "istanbul".toUpperCase() çoğu ortamda “ISTANBUL” döndürür; doğrusu “İSTANBUL”dur. Tersi de geçerlidir: “IŞIK” kelimesini standart küçük harfe çevirmek “işik” gibi yanlış sonuçlar verebilir. Bu, arama, sıralama ve karşılaştırma yaparken gerçek hatalara yol açar.

"istanbul".toLocaleUpperCase("tr-TR");   // "İSTANBUL"
"IŞIK".toLocaleLowerCase("tr-TR");       // "ışık"

// Dilden bağımsız arama için karşılaştırma
const ayni = (a, b) =>
  a.localeCompare(b, i18n.language, { sensitivity: "base" }) === 0;

// Sıralama
liste.sort(new Intl.Collator(i18n.language).compare);

Ters yönde de dikkatli olun: kod içinde anahtar, kimlik ya da teknik değerler üzerinde büyük-küçük harf dönüşümü yapıyorsanız yerel ayara bağlı fonksiyonları değil, dilden bağımsız olanları kullanın. Aksi halde Türkçe cihazda “id” kelimesi “İD”ye dönüşür ve karşılaştırmalar sessizce bozulur. Arayüzde de tamamen büyük harf kullanılan başlıklar, Türkçe ve Almanca gibi dillerde beklenmedik sonuçlar verebilir; mümkünse metni çeviri dosyasında doğru haliyle yazmak daha güvenlidir.

6. Sağdan sola diller: Arapça ve diğerleri

Arapça, Farsça ve İbranice gibi dillerde yalnızca metin değil, bütün arayüz yön değiştirir. React Native'de I18nManager bu yönü kontrol eder. Yön değişikliği uygulamanın yeniden başlatılmasını gerektirir:

import { I18nManager } from "react-native";
import * as Updates from "expo-updates";

const rtl = ["ar", "fa", "he"].includes(dil);
if (I18nManager.isRTL !== rtl) {
  I18nManager.allowRTL(rtl);
  I18nManager.forceRTL(rtl);
  await Updates.reloadAsync();
}

Stil tarafında marginLeft ve paddingRight yerine marginStart ve paddingEnd kullanmak, düzenin otomatik olarak aynalanmasını sağlar. Geri ok, ilerleme çubuğu ve kaydırma yönü gibi yön anlamı taşıyan simgelerin de aynalanması gerekir. Buna karşılık logolar, saat ve medya oynatma simgeleri genellikle aynalanmaz. RTL desteğini son ana bırakmak, çok sayıda ekranı yeniden düzenlemek anlamına gelir; ilk ekranı yazarken bile başlangıç ve bitiş değerlerini kullanmak iyi bir alışkanlıktır.

7. Uzayan metinlere yer açın

Almanca ve Fince gibi dillerde aynı ifade İngilizceden belirgin biçimde uzun olabilir. Sabit genişlikli düğmeler, tek satıra sığması beklenen başlıklar ve sekme çubukları ilk taşan yerlerdir. Esnek düzenler kurmak, numberOfLines ile kontrollü kısaltma yapmak ve en uzun dilde bütün ekranları gözden geçirmek bu sorunu büyük ölçüde çözer. Geliştirme sırasında metinleri yapay olarak uzatan bir “sahte dil” kullanmak, taşmaları çeviri gelmeden önce görmenizi sağlar.

8. Çeviri iş akışı ve kalite kontrol

Makine çevirisi ve yapay zekâ araçları bugün iyi bir ilk taslak üretir. Ancak bağlamı bilmeyen bir çeviri, “Save” kelimesini “kaydet” yerine “kurtar” olarak çevirebilir. Çevirmen ya da araç kim olursa olsun, şu adımları uyguluyorum:

  • Bağlam notu: Her anahtarın yanında kısa bir açıklama (bu bir düğme mi, başlık mı, ne kadar yer var?) tutmak çeviri kalitesini belirgin biçimde artırır.
  • Sözlük: Uygulamaya özgü terimlerin (seri, alışkanlık, premium) her dilde tek bir karşılığı olmalı.
  • Eksik anahtar kontrolü: Derleme sırasında tüm dil dosyalarını ana dille karşılaştırıp eksik ya da fazla anahtarları raporlayan küçük bir betik, yayına çıkmadan sorunları yakalar.
  • Yer tutucu kontrolü: Çeviride {{count}} gibi değişkenlerin silinmediğini ya da değiştirilmediğini otomatik olarak doğrulamak.
  • Ana dili konuşan biriyle kısa bir tur: Mümkün olan her dilde, uygulamayı birkaç dakika kullanan bir kişinin geri bildirimi çoğu zaman en değerli testtir.
// eksik-anahtar.js
const fs = require("fs");
const duz = (o, p = "") => Object.entries(o).flatMap(([k, v]) =>
  typeof v === "object" ? duz(v, p + k + ".") : [p + k]);
const ana = new Set(duz(require("./locales/en.json")));
for (const f of fs.readdirSync("./locales")) {
  const d = new Set(duz(require("./locales/" + f)));
  const eksik = [...ana].filter(k => !d.has(k) && !/_(zero|one|two|few|many|other)$/.test(k));
  if (eksik.length) console.log(f, "eksik:", eksik);
}

9. Mağaza sayfaları: yerelleştirmenin görünen yüzü

Uygulamanın içi ne kadar iyi yerelleştirilmiş olursa olsun, kullanıcı ilk olarak mağaza sayfasını görür. App Store ve Google Play her dil için ayrı başlık, alt başlık ya da kısa açıklama, uzun açıklama ve ekran görüntüsü kabul eder. Bu alanlar, o dildeki aramalarda görünürlüğü doğrudan etkiler.

Mağaza metnini çevirmek yeterli değildir; her dilde insanların gerçekten ne aradığını düşünmek gerekir. Türkçede “alışkanlık takibi” aranırken İngilizcede “habit tracker”, Almancada “Gewohnheiten” aranır. Anahtar kelimeleri her dilde ayrı araştırmak ve başlık ile alt başlıkta bu kelimelere yer vermek, mağaza optimizasyonunun (ASO) temelidir. Ekran görüntülerindeki metinler de o dilde olmalıdır; İngilizce ekran görüntüsüyle Japonca bir sayfa, kullanıcıya uygulamanın kendi dilinde olmadığı izlenimini verir. Ekran görüntülerini her dil için elle hazırlamak yerine, uygulamayı otomatik olarak her dilde açıp görüntü alan bir test betiği ya da şablon kullanmak büyük zaman kazandırır.

10. Yerelleştirme kontrol listesi

  1. Tüm metinler çeviri dosyasında, kodun içinde sabit metin yok.
  2. Cümleler parçalanmadan, yer tutucularla yazılmış.
  3. Çoğul biçimleri her dilin kategorilerine göre doldurulmuş.
  4. Tarih, saat, sayı ve para Intl ile biçimleniyor.
  5. Türkçe harf dönüşümleri yerel ayarla yapılıyor, teknik değerlerde yapılmıyor.
  6. RTL dillerde düzen başlangıç ve bitiş değerleriyle aynalanıyor.
  7. En uzun dilde tüm ekranlar taşma için kontrol edilmiş.
  8. Eksik anahtar ve yer tutucu kontrolü derlemeye bağlı.
  9. Mağaza başlığı, açıklaması ve ekran görüntüleri her dilde ayrı hazırlanmış.

Sonuç

Yerelleştirmeyi baştan doğru kurmak, sonradan eklemekten çok daha ucuzdur. İlk günden metinleri çeviri dosyasına koymak, Intl kullanmak ve düzeni yönden bağımsız yazmak, yeni bir dil eklemeyi birkaç saatlik bir işe dönüştürür. Bu yaklaşımla geliştirdiğim uygulamalardan biri olan StreakFlow'u ve diğer uygulamalarımı inceleyebilirsin.

Çeviri dosyalarıyla çalışırken JSON biçimlendirici, metin uzunluklarını karşılaştırmak için karakter sayacı ve Türkçe harf dönüşümünü denemek için büyük-küçük harf dönüştürücü işine yarayabilir. Uygulamanı çok dilli yayınlamak için destek arıyorsan teklif formundan yazabilirsin; bağımsız yayıncılığın diğer tarafını bağımsız geliştirici olarak uygulama yayınlamak yazısında anlattım.

Diğer yazılar