Tarihlerin ve Sayıların Neden Yerelleştirilmesi Gerekiyor
TABLE OF CONTENTS
Eğer küresel olarak gönderim yapıyorsanız, aynı dize farklı kullanıcılara farklı şeyler ifade edebilir: 12/01/2025, 1.234, 1,234, 12:00, 00:00. Bu küçük farklar kozmetik değildir—güven, anlama ve hatta yasal uyumluluğu etkiler. Bu kılavuz, tarihlerin ve sayıların neden yerelleştirilmesi gerektiğini, yerel olarak nelerin değiştiğini ve ürününüzde bunu doğru bir şekilde nasıl uygulayacağınızı açıklar.
Yerel Olarak Neler Değişir
- Tarihler: sıralama (MDY vs DMY vs YMD), ayırıcılar, ay/gün isimleri; bazı pazarlar Gregoryen olmayan takvimler kullanır.
- Saatler: 12 saatlik vs 24 saatlik saatler, AM/PM işaretleri, zaman dilimleri, yaz saati uygulama kuralları, haftanın başlangıcı (Pazar vs Pazartesi).
- Sayılar: ondalık ayırıcılar (
.vs,), gruplama stilleri (1,234vs1.234vs1 234), Hint gruplama (1,23,456), kesmeyen boşluklar, eksi/artı stilleri (tire vs gerçek eksi−). - Para birimi: sembol vs ISO kodu, sembol yerleşimi (ön ek/son ek), dar vs geniş boşluk, ondalık hassasiyet (0, 2 veya 3+), muhasebe negatifleri (örneğin,
(1 234,56 €)). - Yüzde ve birimler: boşluk ve işaret yerleşimi (
50%vs50 %), birimler için yerel adlar, metrik vs emperyal.
İş Etkisi
- Güven ve dönüşüm: “Yanlış görünen” fiyatlar, ücretler ve tarihler satın alımları azaltır ve müşteri kaybını artırır.
- Operasyonel risk: Yanlış okunan tarihler rezervasyonları veya son tarihleri kaydırabilir; ayrıştırma hataları faturaları, ihracatları ve analizleri bozabilir.
- Uyumluluk: Yanlış formatlara sahip finansal belgeler faturalama, vergi veya raporlama kurallarını ihlal edebilir.
- Destek yükü: Kullanıcılar, beklentilere uymayan zamanlar, para birimleri ve sayı girişlerini açıklığa kavuşturmak için bilet açar.
Gerçek Dünya Olayı
Bir Avrupa seyahat rezervasyon platformu, kalkış tarihlerini yerel bağlam olmadan 01/03/2024 olarak gösterdi. ABD müşterileri bunu “Ocak 3” olarak yorumlarken, İngiltere ve Avrupa müşterileri “Mart 1” olarak okudu.
Etkisi:
- Uluslararası rezervasyonların %12’si yanlış tarihler için yapıldı
- Müşteri hizmetleri bir haftada 3.400’den fazla açıklama talebi aldı
- $2.3M iade ve yeniden rezervasyon ücretleri
- Etkilenen müşterilerin %8’i rakiplere yöneldi
Temel neden: E-posta onaylarında sabit kodlanmış GG/AA/YYYY formatı, web sitesi ABD kullanıcıları için AA/GG/YYYY formatını kullanıyordu. Düzeltme sadece 3 saat mühendislik zamanı gerektirdi—belirsizlik, önlemenin 800 katı maliyetine neden oldu.
Ders: Bağlamdan yoksun tarihler, küresel bir ürün için bir zaman bombasıdır.
Yaygın Tuzaklar
- Sabit kodlanmış formatlar:
AA/GG/YYYYveya1,234.56UI, e-postalar, PDF’ler veya CSV dışa aktarımlarına gömülmüş. - Yerelleştirilmiş dizgilerin saklanması: ISO zamanı yerine “Dec 1, 2025” kaydetmek ayrıştırma hatalarına ve zaman dilimi kaymasına neden olur.
- Naif ayrıştırma: Sunucu yerel ayar varsayılanlarını kullanma; ayırıcıları veya 12/24 saatlik saatleri varsayma.
- Dizgi birleştirme: “miktar + para birimi”
"$" + 1234.56gibi oluşturma yerine yerel ayar farkındalığı olan formatlama. - Zaman dilimi belirsizliği: Zaman duyarlı eylemler için açık bölgeler olmadan yerel zamanları gösterme.
- Tarayıcı tutarsızlığı: Safari, Firefox ve Chrome tarihleri/sayıları farklı biçimlendirebilir; her zaman tüm hedef tarayıcılarda test edin.
- Sunucu tarafı render boşlukları: Tam ICU verisi olmadan Node.js (
node --with-intl=full-icu) eksik veya yanlış biçimlendirme üretir. - Performans tuzakları: Döngülerde veya render döngülerinde yeni
Intl.*örnekleri oluşturma yerine formatlayıcıları önbelleğe alma.
En İyi Uygulamalar
- Kanonik değerleri saklayın: Tarih/saatleri UTC’de ISO‑8601 olarak; parayı küçük birimler (kuruşlar) veya yüksek hassasiyetli ondalıklar olarak.
- Kullanıcı yerel ayarına göre render edin: Görüntüleme için yerel ayar farkındalıklı API’leri (örneğin,
Intl.DateTimeFormat,Intl.NumberFormat) kullanın. - Yerel ayara göre doğrulayın ve ayrıştırın: Uygun olduğunda yerel ayar girdisini kabul edin; kullanıcıları yönlendirmek için yer tutucular/örnekler gösterin.
- Zaman konusunda açık olun: Zaman dilimi kısaltmalarını veya ofsetlerini gösterin; sonuçların zamana bağlı olduğu durumlarda kullanıcı seçimine izin verin.
- Para birimi açıklığı: Belirsizlik olduğunda ISO kodlarını tercih edin; yerel ayara göre sembol yerleşimini ve boşlukları dikkate alın.
- Formatlamayı merkezileştirin: Tutarlılığı sağlamak için tüm yüzeyler (UI, e-postalar, PDF’ler, dışa aktarımlar) için tek bir yardımcı katman.
- Formatlayıcıları önbelleğe alın:
Intl.*yapıcı çağrıları pahalıdır; bir kez oluşturun ve yerel ayar başına örnekleri yeniden kullanın.
Ne Zaman Yerelleştirilmemeli
Her şey yerelleştirilmemelidir. İşte tutarlı, makine tarafından okunabilir formatların daha iyi olduğu senaryolar:
- Makine tarafından okunabilir dışa aktarımlar: Veri hatları veya analiz araçları tarafından tüketilen CSV dosyaları sabit bir format kullanmalıdır (örneğin, ISO-8601 tarihleri, ondalık ayırıcı olarak nokta). Bu formatı dışa aktarma başlıklarında veya README dosyalarında açıkça belgeleyin.
- API yanıtları: REST/GraphQL API’leri tarihleri ISO-8601 dizeleri olarak ve sayıları standart JSON formatında döndürmelidir. İstemcilerin kullanıcılarının tercihlerine göre yerelleştirmeyi ele almasına izin verin.
- Dahili günlükler ve metrikler: Günlükler, izleme panoları ve veritabanı sorguları, ayrıştırma, toplama ve uyarı için tutarlı formatlardan yararlanır. ISO-8601 ve standart sayısal formatları kullanın.
- Kanonik tanımlayıcılar: Kullanıcıların yerel ayarlar arasında iletişim kurması gerekebilecek işlem kimlikleri, sipariş numaraları veya referanslar yerel ayara özgü formatlamadan kaçınmalıdır.
Kural: İçeriği okuyan insanlar için yerelleştirin; veriyi işleyen makineler için standart formatlar kullanın.
Uygulama Planı
- Denetim yüzeyleri: Envanter tarihleri, saatler, sayılar, kullanıcı arayüzünde para birimi, e-postalar, PDF’ler, CSV/Excel dışa aktarımları, analizler, günlükler.
- Yerel ayarları tanımlayın: Desteklenen yerel ayarları ve varsayılan yedekleri listeleyin; her yerel ayar için 12/24 saat politikasını belirtin.
- Yardımcı programlar oluşturun:
Intl.*API’lerini (veya bir kütüphaneyi) paylaşılan yardımcılar ve testlerle sarın; biçimlendirici önbelleklemesini uygulayın. - Giriş stratejisi: Formları yerel ayar formatlarını kabul edecek ve doğrulayacak şekilde güncelleyin; ham girişin yanında kanonik değerleri saklayın.
- İçerik kuralları: Kısa/uzun tarihlerin, göreceli tarihlerin, para birimi gösteriminin ve yüzde biçimlendirmenin stilini belgeleyin.
- Yayın: Önce en yüksek trafik ve en yüksek riskli yüzeyleri dönüştürün; gerekirse bir özellik bayrağı arkasında yayınlayın.
Test Stratejisi
Manuel QA’nın ötesinde, gerilemeleri önlemek için otomatik testler uygulayın:
- Anlık görüntü testleri: Her desteklenen yerel ayar için biçimlendirilmiş çıktıları dondurun. Değişiklikler, kasıtlı olduklarından emin olmak için incelemeyi tetikler.
- Gidiş-dönüş testleri:
format → parse → formatişleminin istikrarlı sonuçlar ürettiğini doğrulayın. Hassasiyet kaybını veya belirsiz formatları yakalar. - Köşe durumları: Negatif sayılar, sıfır, çok büyük sayılar (milyarlar/trilyonlar), DST geçişleri, artık yıllar ve sınır tarihleri (1 Ocak, 31 Aralık) test edin.
- Tarayıcılar arası doğrulama: Chrome, Firefox, Safari ve Edge üzerinde otomatik testler yaparak render farklılıklarını yakalayın.
- Yerel ayar yedekleme: Desteklenmeyen yerel ayarlar istendiğinde zarif bozulmayı doğrulayın.
- Performans karşılaştırmaları: Biçimlendirici oluşturma maliyetini ölçün; önbelleklemenin üretimde çalıştığından emin olun.
Performans İpuçları
Intl.* API’leri güçlüdür ancak yanlış kullanılırsa yavaş olabilir. Bu yönergeleri izleyin:
// ❌ KÖTÜ: Her çağrıda yeni biçimlendirici oluşturur
function formatPrice(amount, locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD'
}).format(amount);
}
```js
// ✅ İYİ: Yerel ayar başına biçimlendiricileri önbelleğe al
const formatters = new Map();
function getFormatter(locale) {
if (!formatters.has(locale)) {
formatters.set(locale, new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'USD'
}));
}
return formatters.get(locale);
}
function formatPrice(amount, locale) {
return getFormatter(locale).format(amount);
}
- Biçimlendirici örneklerini önbelleğe al:
Intl.*oluşturma maliyetlidir (~1–5ms); mevcut bir örnekle biçimlendirme hızlıdır (~0.01ms). - Döngülerden kaçının: Asla
map(),forEach()veya render döngüleri içinde biçimlendirici oluşturmayın. - Yerel ayar verilerini tembel yükleyin: Web uygulamaları için, yalnızca gerekli yerel ayarları yüklemek üzere kod bölmeyi düşünün, paket boyutunu azaltın.
- Sunucu tarafı: Her istek/yanıt döngüsü için tek bir biçimlendirici kullanın; büyük veri kümelerinde öğe başına oluşturma yapmaktan kaçının.
QA Kontrol Listesi
- Tarihler
en-US,en-GB,de-DE,fr-FR,hi-IN,ja-JP,ar-EGdillerinde belirsiz değildir. - Saat doğru 12/24 saat düzenini kullanır; AM/PM işaretçileri beklenen yerde görünür; zaman dilimleri zaman kritik akışlar için gösterilir.
- Sayılar doğru ondalık ve gruplama ayırıcılarını kullanır; standart olan yerlerde kesintisiz boşluklar (örneğin,
fr-FR). - Para birimi her yerel ayar için doğru sembol/kod, yerleştirme ve ondalık hassasiyeti gösterir; negatifler doğru görüntülenir.
- Girdiler kullanıcı yerel ayar verilerini kabul eder ve ayrıştırır (veya desteklenen bir formatı açıkça zorlar); doğrulama mesajları yerel ayar farkındadır.
- İhracatlar (CSV/PDF) yerel ayar beklentilerini karşılar veya makine tüketimi için sabit formatları açıkça belgeler.
- Tarayıcılar arası: Tarihler, saatler ve sayılar Chrome, Firefox, Safari ve Edge’de tutarlı bir şekilde görüntülenir.
- Performans: Döngülerde biçimlendirici oluşturma yok; önbelleğe alınmış örnekler renderlar arasında yeniden kullanılır.
Kod Örnekleri
JavaScript
// Tarihler
const date = new Date('2025-01-12T00:00:00Z');
console.log(new Intl.DateTimeFormat('en-US', { dateStyle: 'short', timeZone: 'UTC' }).format(date));
// → 1/12/25
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'short', timeZone: 'UTC' }).format(date));
// → 12/01/2025 (gün/ay/yıl)
console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'medium', timeZone: 'UTC' }).format(date));
// → 2025/01/12
// Zaman dilimi gösterimi ile zamanlar
const time = new Date('2025-01-12T18:05:00Z');
console.log(new Intl.DateTimeFormat('en-US', {
dateStyle: 'short',
timeStyle: 'short',
timeZone: 'America/New_York',
timeZoneName: 'short'
}).format(time));
// → 1/12/25, 1:05 PM EST
console.log(new Intl.DateTimeFormat('de-DE', {
timeStyle: 'short',
timeZone: 'Europe/Berlin',
hour12: false
}).format(time));
// → 19:05
// Sayılar
const n = 1234.56;
console.log(new Intl.NumberFormat('en-US').format(n)); // → 1,234.56
console.log(new Intl.NumberFormat('de-DE').format(n)); // → 1.234,56
console.log(new Intl.NumberFormat('fr-FR').format(n)); // → 1 234,56 (grup ayırıcı olarak NBSP)
// Hint numaralandırma
console.log(new Intl.NumberFormat('hi-IN').format(1234567.89)); // → 12,34,567.89
// Para birimi
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n));
// → $1,234.56
console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n));
// → 1.234,56 €
console.log(new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(1234));
// → ¥1,234 (ondalık yok)
// Yüzde ve birimler
console.log(new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 0 }).format(0.5));
// → 50%
console.log(new Intl.NumberFormat('fr-FR', { style: 'percent', maximumFractionDigits: 0 }).format(0.5));
// → 50 %
// Biçimlendirici önbellekleme örneği
class LocaleFormatter {
constructor() {
this.cache = new Map();
}
getDateFormatter(locale, options) {
const key = `date:${locale}:${JSON.stringify(options)}`;
if (!this.cache.has(key)) {
this.cache.set(key, new Intl.DateTimeFormat(locale, options));
}
return this.cache.get(key);
}
}
formatDate(date, locale, options = { dateStyle: 'medium' }) {
return this.getDateFormatter(locale, options).format(date);
}
}
const formatter = new LocaleFormatter();
console.log(formatter.formatDate(new Date(), 'en-US')); // → Jan 12, 2025
console.log(formatter.formatDate(new Date(), 'fr-FR')); // → 12 janv. 2025
Python
from babel.dates import format_date, format_time, format_datetime
from babel.numbers import format_number, format_currency, format_percent
from datetime import datetime
import pytz
# Tarihler
date = datetime(2025, 1, 12)
print(format_date(date, format='short', locale='en_US')) # → 1/12/25
print(format_date(date, format='short', locale='en_GB')) # → 12/01/2025
print(format_date(date, format='medium', locale='ja_JP')) # → 2025/01/12
# Saatler ve zaman dilimi
tz_ny = pytz.timezone('America/New_York')
tz_berlin = pytz.timezone('Europe/Berlin')
time = datetime(2025, 1, 12, 18, 5, tzinfo=pytz.UTC)
print(format_datetime(time.astimezone(tz_ny), 'short', tzinfo=tz_ny, locale='en_US'))
# → 1/12/25, 1:05 PM
print(format_time(time.astimezone(tz_berlin), format='short', tzinfo=tz_berlin, locale='de_DE'))
# → 19:05
# Sayılar
n = 1234.56
print(format_number(n, locale='en_US')) # → 1,234.56
print(format_number(n, locale='de_DE')) # → 1.234,56
print(format_number(n, locale='fr_FR')) # → 1 234,56
# Hint numaralandırması
print(format_number(1234567.89, locale='hi_IN')) # → 12,34,567.89
# Para birimi
print(format_currency(n, 'USD', locale='en_US')) # → $1,234.56
print(format_currency(n, 'EUR', locale='de_DE')) # → 1.234,56 €
print(format_currency(1234, 'JPY', locale='ja_JP')) # → ¥1,234
# Yüzde
print(format_percent(0.5, locale='en_US')) # → 50%
print(format_percent(0.5, locale='fr_FR')) # → 50 %
Java
import java.text.NumberFormat;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import java.util.Currency;
// Tarihler
ZonedDateTime date = ZonedDateTime.of(2025, 1, 12, 0, 0, 0, 0, ZoneId.of("UTC"));
DateTimeFormatter usFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.US);
System.out.println(usFormatter.format(date)); // → 1/12/25
DateTimeFormatter gbFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.UK);
System.out.println(gbFormatter.format(date)); // → 12/01/2025
DateTimeFormatter jpFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.JAPAN);
System.out.println(jpFormatter.format(date)); // → 2025/01/12
// Saatler
ZonedDateTime time = ZonedDateTime.of(2025, 1, 12, 18, 5, 0, 0, ZoneId.of("UTC"));
ZonedDateTime timeNY = time.withZoneSameInstant(ZoneId.of("America/New_York"));
DateTimeFormatter usTimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.US);
System.out.println(usTimeFormatter.format(timeNY)); // → 1:05 PM
// Sayılar
double n = 1234.56;
NumberFormat usFormat = NumberFormat.getInstance(Locale.US);
System.out.println(usFormat.format(n)); // → 1,234.56
NumberFormat deFormat = NumberFormat.getInstance(Locale.GERMANY);
System.out.println(deFormat.format(n)); // → 1.234,56
NumberFormat frFormat = NumberFormat.getInstance(Locale.FRANCE);
System.out.println(frFormat.format(n)); // → 1 234,56
// Para Birimi
NumberFormat usCurrency = NumberFormat.getCurrencyInstance(Locale.US);
usCurrency.setCurrency(Currency.getInstance("USD"));
System.out.println(usCurrency.format(n)); // → $1,234.56
NumberFormat deCurrency = NumberFormat.getCurrencyInstance(Locale.GERMANY);
deCurrency.setCurrency(Currency.getInstance("EUR"));
System.out.println(deCurrency.format(n)); // → 1.234,56 €```
NumberFormat jpCurrency = NumberFormat.getCurrencyInstance(Locale.JAPAN);
jpCurrency.setCurrency(Currency.getInstance("JPY"));
System.out.println(jpCurrency.format(1234)); // → ¥1,234
Go
package main
import (
"fmt"
"time"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
)
func main() {
// Sayılar
n := 1234.56
pUS := message.NewPrinter(language.AmericanEnglish)
fmt.Println(pUS.Sprintf("%.2f", n)) // → 1,234.56
pDE := message.NewPrinter(language.German)
fmt.Println(pDE.Sprintf("%.2f", n)) // → 1.234,56
pFR := message.NewPrinter(language.French)
fmt.Println(pFR.Sprintf("%.2f", n)) // → 1 234,56
// Para birimi (number paketi kullanılarak)
fmt.Println(pUS.Sprint(number.Decimal(n, number.Scale(2)))) // → 1,234.56
// Tarihler - Go'nun time paketi yerel ayar formatları yerine düzenler kullanır
// Tam i18n tarih formatlaması için github.com/goodsign/monday veya benzeri kullanın
date := time.Date(2025, 1, 12, 0, 0, 0, 0, time.UTC)
fmt.Println(date.Format("01/02/2006")) // ABD: 01/12/2025
fmt.Println(date.Format("02/01/2006")) // AB: 12/01/2025
fmt.Println(date.Format("2006/01/02")) // ISO: 2025/01/12
}
Not: Go’nun standart kütüphanesi sınırlı yerel ayar desteğine sahiptir. Üretim kullanımı için düşünün:
golang.org/x/textsayı formatlaması içingithub.com/goodsign/mondayyerelleştirilmiş tarih/saat formatlaması içingithub.com/bojanz/currencypara birimi işlemleri için
Faydalı Kaynaklar
- Standartlar: Unicode CLDR (yerel veri standardı), IETF BCP 47 (yerel tanımlayıcılar)
- Dokümantasyon: MDN Intl Referansı
- Kütüphaneler:
- Luxon (i18n ile modern tarih/zaman kütüphanesi)
- date-fns yerel modüllerle
- Globalize.js (CLDR’ye dayalı kapsamlı i18n)
- Format.js (ICU mesaj sözdizimi ile React odaklı i18n)
- Test: Intl polyfill eski tarayıcılar için
Kapat
Tarihleri ve sayıları doğru almak, düşük çaba, yüksek etki sağlayan bir kazançtır: daha yüksek güven, daha az hata ve daha sorunsuz bir küresel deneyim. Formatlamayı merkezileştirin, kanonik değerleri saklayın, kullanıcılarınızın zaman, para veya sayı okuduğu veya yazdığı her yerde yerel farkındalıklı API’ler kullanın—ve makine tarafından okunabilir veriler için yerelleştirmeyi ne zaman atlayacağınızı bilin. Doğru önbellekleme ve test ile, performanstan ödün vermeden yerel diller arasında ölçeklenen sağlam bir sistem oluşturacaksınız.


