İçindekiler
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:
| Paket | Amaç |
|---|---|
expo-localization | Cihaz yerel ayarı tespiti ve takvim/sayı biçimlendirme tercihleri |
i18next | Çeviri çalışma zamanı (anahtar çözümleme, interpolasyon, çoğullama) |
react-i18next | i18next için React bağlamaları (hook'lar, bileşenler, context) |
@better-i18n/react-native | Better 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:
- CDN getirme — Better i18n CDN'den çevirileri yüklemeye çalış
- Çevrimdışı önbellek — CDN erişilemez ise AsyncStorage'daki önbellenmiş çevirileri kullan
- 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:
- Better i18n platformunda çevirileri güncellersiniz (AI Drawer, editör veya MCP aracılığıyla)
- Değişiklikleri CDN'e yayınlarsınız
- Uygulamanın önbellek TTL'si bir sonraki dolduğunda ve kullanıcı uygulamayı açtığında taze çeviriler getirilir
- 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 exportile 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
- Next.js i18n Rehberi — Better i18n ile web tarafı i18n
- Geliştirici Odaklı Lokalizasyon — Geliştirici odaklı platformların neden kazandığı
- Uluslararasılaştırma Testi — Çok dilli uygulamalar için otomatik test stratejileri
- Better i18n Dokümantasyonu — Tam platform dokümantasyonu