Rehberler//15 dk okuma

Expo ile React Native Lokalizasyonu — Uygulamanızı 50'den Fazla Dilde Yayınlayın

Eray Gündoğmuş
Paylaş

Mobil uygulamalar, kontrol ettiğiniz bir sunucuda değil, kullanıcının cihazında çalışır. Bu tek fark, React Native'de lokalizasyonu web lokalizasyonundan temelden farklı kılar. Çevrimdışı erişimi, cihaz yerel ayarı değişikliklerini, birden fazla dilde uygulama mağazası meta verilerini ve OTA çeviri güncellemelerini yönetmeniz gerekir — tüm bunları makul bir paket boyutunu koruyarak yapmanız gerekmektedir.

Bu rehber, cihaz yerel ayarı tespitinden üretim ortamına hazır OTA çeviri teslimatına kadar Expo ile bir React Native uygulamasını yerelleştirme sürecini adım adım anlatır. Cihaz ayarları için expo-localization, çeviri çalışma zamanı için i18next ve çeviri teslimatı için Better i18n CDN kullanacağız. Aynı zamanda bir web ön yüzü geliştiren ekipler için 2026 için kapsamlı Next.js i18n rehberimiz web tarafındaki eşdeğer stack'i ele almaktadır.

Mobil i18n Neden Farklıdır?

Koda dalmadan önce, mobil lokalizasyonun neden kendine özgü zorlukları olduğunu anlayalım:

1. Önce Çevrimdışı

Web uygulamaları her istekte çeviri alabilir. Mobil uygulamalar ağ erişilebilirliğini varsayamaz. Çevirilerinizin yedek olarak uygulamayla birlikte paketlenmesi, bağlantı mevcut olduğunda ise OTA güncellemelerinin taze içerik getirmesi gerekir.

2. Cihaz Yerel Ayarı

Web'de yerel ayarı URL'lerden, çerezlerden veya Accept-Language başlıklarından tespit edersiniz. Mobil cihazlarda ise kullanıcının telefon ayarlarında belirlediği bir sistem yerel ayarı vardır. Uygulamanız varsayılan olarak bu tercihe uymalı, isteğe bağlı bir uygulama içi dil seçici de sunabilir.

3. Paket Boyutu

Web uygulamaları çevirileri yerel ayara göre kod bölümleyebilir ve isteğe bağlı yükleyebilir. Mobil uygulamalar tek bir binary olarak gönderilir. Paketlediğiniz her dil, uygulama boyutunuzu artırır. 50'den fazla dil için bu, indirme boyutunuza megabaytlar ekleyebilir. Bu, çerçeveler genelinde yaklaşımları karşılaştıran mobil uygulama lokalizasyonu rehberimizde ele alınan temel zorluklardan biridir.

4. Uygulama Mağazası Meta Verileri

Google Play ve App Store, desteklenen her dil için yerelleştirilmiş meta veri (başlık, açıklama, ekran görüntüleri) gerektirir. Bu, uygulama içi çevirilerden ayrı bir iş akışıdır. Android'de Android lokalizasyon rehberimiz Play Store meta veri iş akışını ayrıntılı olarak ele almaktadır.

5. RTL Desteği

Arapça, İbranice, Farsça ve Urduca sağdan sola (RTL) düzen gerektirir. React Native, I18nManager aracılığıyla yerleşik RTL desteğine sahiptir, ancak bunu açıkça ele almanız gerekir — düzen çevirme, metin hizalama ve yönlü ikonlar.

Proje Kurulumu

Yeni bir Expo projesiyle başlayın veya mevcut birine ekleyin:

npx create-expo-app my-localized-app
cd my-localized-app

Bağımlılıkları yükleyin:

npx expo install expo-localization
npm install i18next react-i18next @better-i18n/react-native

Her paketin işlevi:

PaketAmaç
expo-localizationCihaz yerel ayarı tespiti ve takvim/sayı biçimlendirme tercihleri
i18nextÇeviri çalışma zamanı (anahtar çözümleme, interpolasyon, çoğullama)
react-i18nexti18next için React bağlamaları (hook'lar, bileşenler, context)
@better-i18n/react-nativeBetter i18n için CDN teslimatı, çevrimdışı önbellekleme ve OTA güncellemeleri

1. Cihaz Yerel Ayarı Tespiti

İlk adım, kullanıcının tercih ettiği dili tespit etmektir. Expo bunun için expo-localization sağlar:

// lib/i18n/locale.ts
import { getLocales, getCalendars } from "expo-localization";

export function getDeviceLocale(): string {
  const locales = getLocales();

  if (locales.length === 0) {
    return "en";
  }

  // İlk yerel ayar kullanıcının tercih ettiği dildir
  const preferred = locales[0];

  // Dil kodunu döndür (örn. "en", "fr", "tr")
  // Tam yerel ayar için languageTag kullanın (örn. "en-US", "fr-FR")
  return preferred.languageCode ?? "en";
}

export function getDeviceRegion(): string | null {
  const locales = getLocales();
  return locales[0]?.regionCode ?? null;
}

export function isRTL(): boolean {
  const locales = getLocales();
  return locales[0]?.textDirection === "rtl";
}

Önemli: getLocales(), kullanıcının tercih ettiği dillerin sıralı bir dizisini döndürür. iOS'ta bu, Ayarlar → Genel → Dil ve Bölge'den gelir. Android'de ise Ayarlar → Sistem → Dil'den gelir. Birincil tercih olarak her zaman ilk öğeyi kullanın.

2. i18next Yapılandırması

i18next'i cihaz yerel ayarı tespiti ve Better i18n CDN teslimatıyla yapılandırın:

// lib/i18n/config.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { createBetterI18nBackend } from "@better-i18n/react-native";
import { getDeviceLocale } from "./locale";

// Paketlenmiş yedek çeviriler (uygulamayla birlikte gönderilir)
import en from "../../locales/en.json";
import es from "../../locales/es.json";

const fallbackResources = {
  en: { translation: en },
  es: { translation: es },
};

const betterI18nBackend = createBetterI18nBackend({
  project: "acme/mobile-app",
  cacheTTL: 60 * 60 * 1000, // Çevirileri 1 saat önbellekle
  offlineStorage: true,      // Çevrimdışı kullanım için AsyncStorage'a kaydet
});

i18n
  .use(betterI18nBackend)
  .use(initReactI18next)
  .init({
    lng: getDeviceLocale(),
    fallbackLng: "en",
    resources: fallbackResources, // CDN erişilemez olduğunda kullanılır
    interpolation: {
      escapeValue: false, // React Native escape işlemini kendisi yapar
    },
    react: {
      useSuspense: true,
    },
  });

export default i18n;

Yedek zinciri nasıl çalışır:

  1. CDN getirme — Better i18n CDN'den çevirileri yüklemeye çalış
  2. Çevrimdışı önbellek — CDN erişilemez ise AsyncStorage'daki önbellenmiş çevirileri kullan
  3. Paketlenmiş kaynaklar — Önbellek yoksa uygulamayla birlikte gönderilen çevirileri kullan

Bu üç katmanlı strateji, internetsiz ilk açılışta bile uygulamanızın her zaman çevirilere sahip olmasını sağlar.

3. Translation Hook Kullanımı

Bileşenlerinizde useTranslation hook'unu kullanın:

// screens/HomeScreen.tsx
import { View, Text, StyleSheet } from "react-native";
import { useTranslation } from "react-i18next";

export function HomeScreen() {
  const { t } = useTranslation();

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{t("home.title")}</Text>
      <Text style={styles.subtitle}>
        {t("home.welcome", { name: "Developer" })}
      </Text>
      <Text style={styles.count}>
        {t("home.itemCount", { count: 5 })}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  title: { fontSize: 24, fontWeight: "bold" },
  subtitle: { fontSize: 16, marginTop: 8 },
  count: { fontSize: 14, marginTop: 4, color: "#666" },
});

Çeviri dosyası yapısı:

{
  "home": {
    "title": "Welcome",
    "welcome": "Hello, {{name}}!",
    "itemCount_one": "{{count}} item",
    "itemCount_other": "{{count}} items"
  }
}

Çoğullama: i18next çoğullamayı otomatik olarak ele alır. Farklı çoğul biçimler için _one, _other, _few, _many soneklerini kullanın. Bu, Arapça (6 çoğul biçim), Lehçe (3 biçim) ve Rusça (3 biçim) gibi diller için kritik öneme sahiptir. Diller arası çoğullama kurallarının tam bir incelemesi için özel çoğullama rehberimize bakın.

4. Uygulama İçi Dil Seçici

Uygulama varsayılan olarak cihaz yerel ayarını kullanmalı olsa da, kullanıcılar genellikle bunu değiştirmek ister. İşte bir dil seçici bileşeni:

// components/LanguagePicker.tsx
import { View, Text, TouchableOpacity, FlatList, StyleSheet } from "react-native";
import { useTranslation } from "react-i18next";
import AsyncStorage from "@react-native-async-storage/async-storage";

const LANGUAGES = [
  { code: "en", name: "English", nativeName: "English" },
  { code: "es", name: "Spanish", nativeName: "Español" },
  { code: "fr", name: "French", nativeName: "Français" },
  { code: "de", name: "German", nativeName: "Deutsch" },
  { code: "tr", name: "Turkish", nativeName: "Türkçe" },
  { code: "ja", name: "Japanese", nativeName: "日本語" },
  { code: "ko", name: "Korean", nativeName: "한국어" },
  { code: "ar", name: "Arabic", nativeName: "العربية" },
];

export function LanguagePicker() {
  const { i18n } = useTranslation();

  const handleLanguageChange = async (code: string) => {
    await i18n.changeLanguage(code);
    await AsyncStorage.setItem("userLanguage", code);
  };

  return (
    <FlatList
      data={LANGUAGES}
      keyExtractor={(item) => item.code}
      renderItem={({ item }) => (
        <TouchableOpacity
          style={[
            styles.item,
            item.code === i18n.language && styles.selected,
          ]}
          onPress={() => handleLanguageChange(item.code)}
        >
          <Text style={styles.nativeName}>{item.nativeName}</Text>
          <Text style={styles.name}>{item.name}</Text>
        </TouchableOpacity>
      )}
    />
  );
}

const styles = StyleSheet.create({
  item: {
    flexDirection: "row",
    justifyContent: "space-between",
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: "#eee",
  },
  selected: { backgroundColor: "#f0f7ff" },
  nativeName: { fontSize: 16, fontWeight: "600" },
  name: { fontSize: 14, color: "#666" },
});

Kalıcılık: Kullanıcı manuel olarak bir dil seçtiğinde, bunu AsyncStorage'a kaydedin. Uygulama açılışında, cihaz yerel ayarına düşmeden önce kayıtlı bir tercihi kontrol edin:

// i18n config başlatma kodunuzda
const savedLanguage = await AsyncStorage.getItem("userLanguage");
const initialLanguage = savedLanguage || getDeviceLocale();

5. RTL Düzen Desteği

Arapça, İbranice ve diğer RTL diller için React Native'in açık yapılandırmaya ihtiyacı vardır:

// lib/i18n/rtl.ts
import { I18nManager } from "react-native";
import * as Updates from "expo-updates";

const RTL_LANGUAGES = ["ar", "he", "fa", "ur"];

export async function handleRTL(languageCode: string) {
  const shouldBeRTL = RTL_LANGUAGES.includes(languageCode);
  const isCurrentlyRTL = I18nManager.isRTL;

  if (shouldBeRTL !== isCurrentlyRTL) {
    I18nManager.forceRTL(shouldBeRTL);
    I18nManager.allowRTL(shouldBeRTL);

    // RTL değişiklikleri etkili olması için uygulamanın yeniden başlatılmasını gerektirir
    if (!__DEV__) {
      await Updates.reloadAsync();
    }
  }
}

Kritik not: I18nManager.forceRTL(), uygulama yeniden başlatılana kadar etkili olmaz. Geliştirme ortamında hot reload kullanabilirsiniz, ancak üretimde düzen yönü değişikliğini uygulamak için Updates.reloadAsync() çağırmanız gerekir.

RTL-Aware Stiller

Stilleri koşullu olarak çevirmek için I18nManager.isRTL kullanın:

import { I18nManager, StyleSheet } from "react-native";

const styles = StyleSheet.create({
  row: {
    flexDirection: I18nManager.isRTL ? "row-reverse" : "row",
  },
  icon: {
    marginLeft: I18nManager.isRTL ? 0 : 8,
    marginRight: I18nManager.isRTL ? 8 : 0,
  },
});

Expo SDK 50+ için left ve right yerine start ve end özelliklerini kullanabilirsiniz:

const styles = StyleSheet.create({
  container: {
    paddingStart: 16, // LTR'de sol, RTL'de sağ
    paddingEnd: 8,    // LTR'de sağ, RTL'de sol
  },
});

6. OTA Çeviri Güncellemeleri

Katil özellik: Mağazaya yeni uygulama sürümü göndermeden çevirileri güncelleyin.

// lib/i18n/ota.ts
import { AppState } from "react-native";
import i18n from "./config";

export function setupOTAUpdates() {
  // Uygulama ön plana geldiğinde yeni çevirileri kontrol et
  const subscription = AppState.addEventListener("change", (state) => {
    if (state === "active") {
      refreshTranslations();
    }
  });

  return () => subscription.remove();
}

async function refreshTranslations() {
  try {
    // Better i18n backend önbellek geçersiz kılmayı ele alır
    // Önbellek TTL'si dolmuşsa bu, taze bir getirmeyi tetikler
    await i18n.reloadResources(i18n.language);
  } catch (error) {
    // Sessiz hata — uygulama önbellenmiş çevirilerle çalışmaya devam eder
    console.warn("Translation refresh failed:", error);
  }
}

Better i18n ile OTA nasıl çalışır:

  1. Better i18n platformunda çevirileri güncellersiniz (AI Drawer, editör veya MCP aracılığıyla)
  2. Değişiklikleri CDN'e yayınlarsınız
  3. Uygulamanın önbellek TTL'si bir sonraki dolduğunda ve kullanıcı uygulamayı açtığında taze çeviriler getirilir
  4. Arayüz, React'in yeniden render döngüsü aracılığıyla otomatik olarak güncellenir

Uygulama mağazasına gönderme yok. Binary güncelleme yok. Çeviriler dakikalar içinde yayınlanır. OTA çeviri teslimatının çapraz platform lokalizasyon stratejisine nasıl uyduğuna dair daha geniş bir bakış için mobil ve web için OTA çeviriler rehberimiz mimari kararları derinlemesine ele almaktadır.

7. Tarih, Sayı ve Para Birimi Biçimlendirme

Yerel ayara duyarlı biçimlendirme için expo-localization kullanın:

// lib/i18n/formatting.ts
import { getLocales } from "expo-localization";

export function formatNumber(value: number): string {
  const locale = getLocales()[0]?.languageTag ?? "en-US";
  return new Intl.NumberFormat(locale).format(value);
}

export function formatCurrency(value: number, currency: string = "USD"): string {
  const locale = getLocales()[0]?.languageTag ?? "en-US";
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  }).format(value);
}

export function formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string {
  const locale = getLocales()[0]?.languageTag ?? "en-US";
  return new Intl.DateTimeFormat(locale, options).format(date);
}

export function formatRelativeTime(date: Date): string {
  const locale = getLocales()[0]?.languageTag ?? "en-US";
  const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });

  const diffMs = date.getTime() - Date.now();
  const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));

  if (Math.abs(diffDays) < 1) {
    const diffHours = Math.round(diffMs / (1000 * 60 * 60));
    return rtf.format(diffHours, "hour");
  }

  return rtf.format(diffDays, "day");
}

Not: React Native'in Hermes motoru, Hermes 0.12'den beri Intl API'lerini destekler. Daha eski bir sürümdeyseniz intl-pluralrules ve @formatjs/intl-numberformat polyfill'lerini ekleyin.

8. Yerelleştirilmiş Bileşenleri Test Etme

Bileşenlerinizi farklı yerel ayarlarla test edin:

// __tests__/HomeScreen.test.tsx
import { render, screen } from "@testing-library/react-native";
import { I18nextProvider } from "react-i18next";
import i18n from "../lib/i18n/config";
import { HomeScreen } from "../screens/HomeScreen";

function renderWithI18n(component: React.ReactElement, locale: string = "en") {
  i18n.changeLanguage(locale);
  return render(
    <I18nextProvider i18n={i18n}>{component}</I18nextProvider>
  );
}

describe("HomeScreen", () => {
  it("renders in English", () => {
    renderWithI18n(<HomeScreen />, "en");
    expect(screen.getByText("Welcome")).toBeTruthy();
  });

  it("renders in Spanish", () => {
    renderWithI18n(<HomeScreen />, "es");
    expect(screen.getByText("Bienvenido")).toBeTruthy();
  });

  it("handles pluralization", () => {
    renderWithI18n(<HomeScreen />, "en");
    expect(screen.getByText("5 items")).toBeTruthy();
  });
});

9. Uygulama Mağazası Lokalizasyonu

Uygulama mağazası listenizin ayrı lokalizasyona ihtiyacı vardır. Expo bunu app.json aracılığıyla destekler:

{
  "expo": {
    "name": "My App",
    "locales": {
      "es": "./locales/store/es.json",
      "fr": "./locales/store/fr.json",
      "de": "./locales/store/de.json",
      "tr": "./locales/store/tr.json",
      "ja": "./locales/store/ja.json"
    }
  }
}

Her yerel ayar dosyası uygulama mağazası meta verilerini içerir:

{
  "CFBundleDisplayName": "Mi Aplicación",
  "NSLocationWhenInUseUsageDescription": "Necesitamos tu ubicación para mostrarte tiendas cercanas."
}

iOS için bu, ana ekrandaki uygulama adını ve izin diyaloglarını yerelleştirir. Android için Play Store meta verilerini Google Play Console veya Fastlane aracılığıyla ele almanız gerekir.

Üretim Kontrol Listesi

Yerelleştirilmiş React Native uygulamanızı göndermeden önce:

  • Varsayılan yerel ayar tespiti cihaz ayarlarından çalışıyor
  • Yedek çeviriler uygulama binary'siyle birlikte paketlenmiş
  • Çevrimdışı mod önbellenmiş çevirilerle çalışıyor
  • OTA güncellemeleri ön plana gelişte taze çeviriler getiriyor
  • RTL düzen Arapça veya İbranice ile test edildi
  • Çoğullama tüm hedef diller için çalışıyor
  • Tarih/sayı biçimlendirme cihaz yerel ayarına uyuyor
  • Dil seçici kullanıcı tercihini kaydediyor
  • Uygulama mağazası meta verileri tüm hedef pazarlar için çevrildi
  • Paket boyutu makul düzeyde (npx expo export ile kontrol edin)
  • Performans — çeviriler arayüzü engellemeden yükleniyor

Sonuç

Expo ile bir React Native uygulamasını yerelleştirmek, web lokalizasyonundan daha kapsamlıdır; ancak araçlar önemli ölçüde olgunlaşmıştır. Cihaz tercihleri için expo-localization, çeviri çalışma zamanı için i18next ve CDN teslimatı ile OTA güncellemeleri için Better i18n kullanarak, çeviri dosyalarını elle yönetmenin karmaşıklığı olmadan üretim ortamına hazır çok dilli bir uygulama gönderebilirsiniz.

Temel içgörü: minimal çevirileri paketleyin, gerisini CDN'den teslim edin. Bu, uygulama boyutunuzu küçük tutarken OTA güncellemeleri aracılığıyla 50'den fazla dili desteklemenizi sağlar. Kullanıcılarınız kendi dillerinde çeviriler alır ve bir çeviri değiştiğinde her seferinde yeni bir uygulama sürümü göndermenize gerek kalmaz.

Aynı ilkeleri çapraz platform Flutter uygulamalarına genişletmek isteyen ekipler için Flutter intl lokalizasyon rehberimiz Dart ekosistemi için eşdeğer kurulumu adım adım açıklamaktadır.


İlgili Kaynaklar

Comments

Loading comments...