Eğitim Portalı/Spring Boot/Uluslararasılaştırma (i18n)
Spring Boot03-spring-boot/18-internationalization

Uluslararasılaştırma (i18n)

Uygulaman birden çok dilde kullanıcıya hitap ediyorsa, metinleri koda gömmek yerine dile göre dışarıdan sağlamalısın. i18n (internationalization — i + 18 harf + n), uygulamayı farklı dil ve bölgelere uyarlayabilir hal…

Uluslararasılaştırma (i18n)

Uygulaman birden çok dilde kullanıcıya hitap ediyorsa, metinleri koda gömmek yerine dile göre dışarıdan sağlamalısın. i18n (internationalization — i + 18 harf + n), uygulamayı farklı dil ve bölgelere uyarlayabilir hale getirme işidir. Spring Boot bunu MessageSource ve dosya tabanlı mesaj paketleriyle (messages_xx.properties) destekler.

MessageSource ve mesaj paketleri

Metinleri anahtar → dile göre değer olarak tutarsın. Gerçek projede bunlar dosyalardadır:

# messages.properties (varsayılan)
selam=Hello, {0}!
# messages_tr.properties
selam=Merhaba, {0}!
# messages_en.properties
selam=Hello, {0}!

{0}, {1} yer tutucuları çalışma anında doldurulur. Koddan:

messageSource.getMessage("selam", new Object[]{ad}, locale);

Not: Bu portal tek dosya çalıştırır (.properties dosyaları yok); bu yüzden örnek, mesajları StaticMessageSource ile programatik ekler. Gerçek projede messages_tr.properties gibi dosyalar ve ResourceBundleMessageSource kullanılır (Spring Boot bunu otomatik yapılandırır).

İsteğin dilini belirlemek: Locale

Hangi dilin kullanılacağını Locale belirler. Spring Boot varsayılan olarak AcceptHeaderLocaleResolver kullanır: isteğin Accept-Language başlığına bakar. Bir controller metoduna Locale parametresi eklersen, Spring onu otomatik enjekte eder:

@GetMapping("/selam")
String selam(@RequestParam String ad, Locale locale) {
    return messageSource.getMessage("selam", new Object[]{ad}, locale);
}

Örnek 1 (./Ornek1.java) aynı ucun Accept-Language: tr ile "Merhaba, Ada!", Accept-Language: en ile "Hello, Ada!" döndürdüğünü gösterir.

Locale çözümleme stratejileri

AcceptHeaderLocaleResolver dışında:

  • SessionLocaleResolver: Dili oturumda saklar (kullanıcı seçer).
  • CookieLocaleResolver: Dili çerezde saklar.
  • LocaleChangeInterceptor: ?lang=tr parametresiyle dili değiştirmeye izin verir.
@Bean LocaleResolver localeResolver() {
    var r = new SessionLocaleResolver();
    r.setDefaultLocale(Locale.of("tr"));
    return r;
}

Nerede kullanılır?

  • Web UI metinleri (Thymeleaf şablonlarında #{anahtar}).
  • Hata/doğrulama mesajları: Bean Validation mesajları i18n'lenebilir ({javax...}).
  • REST API yanıtları: Hata mesajlarını istemcinin diline göre döndürme.
  • Tarih/sayı/para biçimleri: Locale'e göre biçimlendirme (topic 88'deki DateTimeFormatter, NumberFormat).

İyi uygulamalar

  • Metinleri asla koda gömme; hepsi mesaj paketlerinde olsun (çevirmen koda dokunmasın).
  • Varsayılan bir dil/paket (messages.properties) bulundur (eksik çeviri için yedek).
  • Yer tutucu sırasına dikkat ({0} farklı dillerde farklı yerde olabilir).
  • UTF-8 kullan (Türkçe/özel karakterler — topic 87).

Özet

i18n ile uygulamayı çok dilli yapmayı öğrendik: MessageSource ve mesaj paketleri (anahtar→dile göre değer, {0} yer tutucular); isteğin dilini Accept-Language ile çözen Locale ve controller'a otomatik enjeksiyonu (Örnek 1); Locale çözümleme stratejileri (session/cookie/param) ve iyi uygulamalar. Bununla yapılandırma & temel web batch'i tamamlandı. Sırada — sonraki turda — test/güvenlik/DB derinleştirme ve mikroservis/cloud konuları.

Kod Örnekleri(1)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: Uluslararasılaştırma (i18n) — MessageSource ile dile göre mesaj.
2// İstek dilini Accept-Language başlığı belirler (varsayılan AcceptHeaderLocaleResolver).
3// Çalıştırma: portal gömülü Tomcat ile başlatır, self-test çıktısını alır.
4package com.egitim.springboot.i18n;
5
6import org.springframework.boot.CommandLineRunner;
7import org.springframework.boot.SpringApplication;
8import org.springframework.boot.autoconfigure.SpringBootApplication;
9import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
10import org.springframework.context.MessageSource;
11import org.springframework.context.annotation.Bean;
12import org.springframework.context.support.StaticMessageSource;
13import org.springframework.web.bind.annotation.*;
14import org.springframework.web.client.RestClient;
15
16import java.util.Locale;
17
18@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
19public class Ornek1 {
20
21    public static void main(String[] args) { SpringApplication.run(Ornek1.class, args); }
22
23    // Mesaj kaynağı: anahtar + dile göre metin. Gerçekte messages_tr.properties / messages_en.properties dosyaları.
24    @Bean
25    MessageSource messageSource() {
26        StaticMessageSource ms = new StaticMessageSource();
27        ms.addMessage("selam", Locale.of("tr"), "Merhaba, {0}!");
28        ms.addMessage("selam", Locale.ENGLISH, "Hello, {0}!");
29        ms.addMessage("sepet", Locale.of("tr"), "Sepetinizde {0} ürün var.");
30        ms.addMessage("sepet", Locale.ENGLISH, "You have {0} items in your cart.");
31        ms.setUseCodeAsDefaultMessage(true); // anahtar bulunamazsa anahtarı döndür
32        return ms;
33    }
34
35    @RestController
36    static class SelamController {
37        private final MessageSource mesajlar;
38        SelamController(MessageSource mesajlar) { this.mesajlar = mesajlar; }
39
40        // 'Locale locale' parametresi, isteğin Accept-Language başlığından OTOMATİK gelir.
41        @GetMapping("/selam")
42        String selam(@RequestParam String ad, Locale locale) {
43            return mesajlar.getMessage("selam", new Object[]{ad}, locale);
44        }
45        @GetMapping("/sepet")
46        String sepet(@RequestParam int adet, Locale locale) {
47            return mesajlar.getMessage("sepet", new Object[]{adet}, locale);
48        }
49    }
50
51    @Bean
52    CommandLineRunner selfTest() {
53        return args -> {
54            RestClient c = RestClient.create("http://localhost:8080");
55            System.out.println("\n========= i18n SELF-TEST =========");
56            // Accept-Language başlığına göre AYNI uç farklı dilde yanıt verir.
57            System.out.println("Accept-Language: tr -> "
58                    + c.get().uri("/selam?ad=Ada").header("Accept-Language", "tr").retrieve().body(String.class));
59            System.out.println("Accept-Language: en -> "
60                    + c.get().uri("/selam?ad=Ada").header("Accept-Language", "en").retrieve().body(String.class));
61            System.out.println("sepet (tr) -> "
62                    + c.get().uri("/sepet?adet=3").header("Accept-Language", "tr").retrieve().body(String.class));
63            System.out.println("sepet (en) -> "
64                    + c.get().uri("/sepet?adet=3").header("Accept-Language", "en").retrieve().body(String.class));
65            System.out.println("==================================");
66        };
67    }
68}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.