Skip to main content

Spring Boot i18n: Tutorial Penyiapan Internasionalisasi

Konfigurasikan MessageSource, buat file properties spesifik bahasa, pilih bahasa, dan render templat Thymeleaf multibahasa—lalu otomatiskan penerjemahan dengan AI.

1

Tambahkan Dependensi

Spring Boot Starter Web langsung menyertakan konfigurasi otomatis MessageSource. Tambahkan Thymeleaf untuk templat i18n yang dirender server dan starter validasi untuk pesan kesalahan lokal.

Spring Boot mengonfigurasi bean MessageSource secara otomatis untuk membaca messages.properties dari classpath. Konfigurasi eksplisit hanya diperlukan jika Anda ingin menyesuaikan basename, pengodean, atau perilaku cache.
pom.xml
<!-- pom.xml — Spring Boot Starter Web includes MessageSource auto-config -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Thymeleaf for server-side rendered templates with i18n -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- Validation (for localized error messages) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2

Konfigurasikan MessageSource & LocaleResolver

MessageSource Spring memuat terjemahan dari file .properties menggunakan konvensi basename: messages.properties (default), messages_de.properties (Jerman), messages_ja.properties (Jepang). Konfigurasikan LocaleResolver untuk menentukan bahasa yang digunakan per permintaan.

File Terjemahan

messages.properties
# src/main/resources/messages.properties (default / English)
nav.home=Home
nav.about=About
nav.settings=Settings

greeting=Hello, {0}!
cart.itemCount={0,choice,0#No items|1#1 item|1<{0,number} items}

error.notFound=Page not found
error.serverError=Something went wrong. Please try again.

Konfigurasi MessageSource

I18nConfig.java
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class I18nConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource source =
            new ReloadableResourceBundleMessageSource();
        source.setBasename("classpath:messages");
        source.setDefaultEncoding("UTF-8");
        source.setCacheSeconds(3600); // reload interval in dev
        return source;
    }

    // Wire MessageSource into Bean Validation
    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        bean.setValidationMessageSource(messageSource);
        return bean;
    }
}
Jika terjemahan mengembalikan nama kunci alih-alih teks terjemahan, penyebab paling umum adalah basename yang salah. Default-nya 'messages', yang dipetakan ke messages.properties di classpath. Jika nama file berbeda atau berada di subdirektori, atur spring.messages.basename secara eksplisit.

Pemilihan Bahasa

Konfigurasikan cara Spring menentukan bahasa aktif untuk setiap permintaan. CookieLocaleResolver menyimpan pilihan pengguna di seluruh sesi. LocaleChangeInterceptor memungkinkan pengguna mengalihkan bahasa melalui parameter kueri seperti ?lang=de.

LocaleConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

import java.util.Locale;

@Configuration
public class LocaleConfig implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver resolver = new CookieLocaleResolver("lang");
        resolver.setDefaultLocale(Locale.ENGLISH);
        resolver.setCookieMaxAge(3600 * 24 * 365); // 1 year
        return resolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang"); // ?lang=de switches locale
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}
3

Gunakan Terjemahan dalam Kode

Akses pesan terjemahan dalam controller melalui injeksi MessageSource, dalam templat Thymeleaf dengan sintaks #{...}, dan dalam REST API menggunakan parameter Locale yang dipilih secara otomatis.

Controller dengan MessageSource

HomeController.java
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.Locale;

@Controller
public class HomeController {

    private final MessageSource messageSource;

    public HomeController(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/")
    public String home(Model model, Locale locale) {
        // Spring injects the resolved Locale automatically
        String greeting = messageSource.getMessage(
            "greeting",
            new Object[]{"World"},
            locale
        );
        model.addAttribute("greeting", greeting);
        return "home";
    }
}

Templat Thymeleaf

Ekspresi #{...} Thymeleaf memilih kunci pesan dari file .properties secara otomatis. Teruskan parameter dengan sintaks #{key(arg0, arg1)}. Templat menggunakan bahasa yang dipilih LocaleResolver.

home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="#{nav.home}">Home</title>
</head>
<body>
    <!-- Simple message lookup -->
    <h1 th:text="#{greeting('World')}">Hello, World!</h1>

    <!-- Navigation with i18n -->
    <nav>
        <a href="/" th:text="#{nav.home}">Home</a>
        <a href="/about" th:text="#{nav.about}">About</a>
        <a href="/settings" th:text="#{nav.settings}">Settings</a>
    </nav>

    <!-- Parameterized messages -->
    <p th:text="#{cart.itemCount(3)}">3 items</p>

    <!-- Language switcher -->
    <div>
        <a th:href="@{/(lang=en)}">English</a>
        <a th:href="@{/(lang=de)}">Deutsch</a>
        <a th:href="@{/(lang=ja)}">日本語</a>
    </div>

    <!-- Conditional text based on locale -->
    <p th:if="${#locale.language == 'ja'}"
       th:text="#{greeting('ユーザー')}">
        こんにちは、ユーザーさん!
    </p>
</body>
</html>
Ekspresi Thymeleaf seperti #{greeting('World')} meneruskan argumen ke MessageFormat. Teks statis di dalam tag HTML berfungsi sebagai fallback saat melihat templat tanpa Spring—berguna bagi desainer yang mengerjakan templat secara langsung.

Lokalisasi REST API

Untuk REST API, Spring memilih Locale dari header Accept-Language secara otomatis. Injeksi sebagai parameter metode dan teruskan ke MessageSource. Klien mengalihkan bahasa dengan mengirim header Accept-Language berbeda.

ApiController.java
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Locale;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class ApiController {

    private final MessageSource messageSource;

    public ApiController(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/greeting/{name}")
    public ResponseEntity<Map<String, String>> greeting(
            @PathVariable String name,
            Locale locale) {  // Resolved from Accept-Language header
        String msg = messageSource.getMessage(
            "greeting", new Object[]{name}, locale
        );
        return ResponseEntity.ok(Map.of("message", msg));
    }

    // curl -H "Accept-Language: de" localhost:8080/api/greeting/Max
    // → {"message": "Hallo, Max!"}
}
REST API biasanya menggunakan AcceptHeaderLocaleResolver (berbasis header), sedangkan aplikasi web menggunakan CookieLocaleResolver (berbasis cookie). Jika keduanya disajikan dari aplikasi yang sama, pertimbangkan LocaleResolver khusus yang memeriksa cookie terlebih dahulu, lalu beralih ke header Accept-Language.

Pesan Validasi Bean

Spring memilih pesan batasan validasi dari MessageSource secara otomatis. Gunakan placeholder kurung kurawal seperti {validation.name.required} dalam anotasi batasan dan tentukan terjemahannya di file .properties.

Bean Validation i18n
import jakarta.validation.constraints.*;

public class CreateUserRequest {

    @NotBlank(message = "{validation.name.required}")
    @Size(min = 2, max = 50, message = "{validation.name.size}")
    private String name;

    @Email(message = "{validation.email.invalid}")
    private String email;
}

// In messages.properties:
// validation.name.required=Name is required
// validation.name.size=Name must be between {min} and {max} characters
// validation.email.invalid=Please enter a valid email address
//
// In messages_de.properties:
// validation.name.required=Name ist erforderlich
// validation.name.size=Name muss zwischen {min} und {max} Zeichen lang sein
// validation.email.invalid=Bitte geben Sie eine gültige E-Mail-Adresse ein
4

Tangani Bentuk Jamak & Variabel

Spring menggunakan java.text.MessageFormat untuk interpolasi dan bentuk jamak. Pola ChoiceFormat menangani aturan bentuk jamak dasar, tetapi untuk dukungan bentuk jamak ICU lengkap (6 bentuk bahasa Arab, 3 bentuk Rusia), tambahkan library ICU4J.

MessageFormat Plurals
# MessageFormat plural syntax in messages.properties
# Uses java.text.ChoiceFormat — NOT ICU plural rules
cart.itemCount={0,choice,0#No items|1#1 item|1<{0,number} items}

# For more complex plurals, use ICU4J:
# 1. Add dependency: com.ibm.icu:icu4j
# 2. Use ICUMessageSource instead of ResourceBundleMessageSource
#
# Then you can write ICU-style plurals:
# cart.items={count, plural, one {# item} other {# items}}

# Variables with MessageFormat:
welcome.message=Welcome, {0}! You have {1,number} new {1,choice,1#notification|1<notifications}.
order.total=Order total: {0,number,currency}
event.date=Event date: {0,date,long}
ChoiceFormat tidak sama dengan aturan bentuk jamak ICU. Format ini menggunakan rentang numerik (0#, 1#, 1<), bukan kategori CLDR (zero, one, two, few, many, other). Untuk bahasa dengan aturan bentuk jamak kompleks seperti Arab, Polski, atau Rusia, ChoiceFormat tidak cukup—gunakan MessageFormat ICU4J.

Otomatiskan Penerjemahan

Setelah penyiapan i18n selesai, terjemahkan file .properties dengan AI. Di IDE, minta asisten AI menerjemahkan file sumber atau gunakan CLI i18n Agent dalam pipeline CI/CD.

Terminal
# Translate your .properties files with AI
# In your IDE, ask your AI assistant:
> Translate src/main/resources/messages.properties to German, Japanese, and Spanish

✓ messages_de.properties created (1.2s)
✓ messages_ja.properties created (1.5s)
✓ messages_es.properties created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate src/main/resources/messages.properties --lang de,ja,es
Terjemahkan secara bertahap—saat menambahkan kunci baru ke messages.properties, terjemahkan hanya kunci baru, bukan membuat ulang semua file bahasa. Ini mempertahankan terjemahan yang telah ditinjau manusia dalam file yang ada.

Otomatiskan Kualitas Terjemahan

Temukan kunci hilang dan placeholder rusak sebelum dirilis dengan i18n-validate. Uji UI dengan terjemahan semu menggunakan i18n-pseudo sebelum terjemahan asli tersedia.

Tanpa Konfigurasi dengan spring-locale-chain

spring-locale-chain adalah starter Spring Boot sumber terbuka yang mengonfigurasi LocaleResolver, LocaleChangeInterceptor, dan validasi bahasa yang didukung secara otomatis dalam satu dependensi. Tentukan bahasa yang didukung di application.yml dan library menangani sisanya.

pom.xml
<!-- Add spring-locale-chain for zero-config locale resolution -->
<dependency>
    <groupId>io.github.i18n-agent</groupId>
    <artifactId>spring-locale-chain</artifactId>
    <version>1.0.0</version>
</dependency>

Struktur File yang Disarankan

Project Structure
my-spring-app/
├── src/main/
│   ├── java/com/example/
│   │   ├── config/
│   │   │   ├── I18nConfig.java          # MessageSource bean
│   │   │   └── LocaleConfig.java        # LocaleResolver + interceptor
│   │   ├── controller/
│   │   │   └── HomeController.java      # Uses MessageSource
│   │   └── MyApplication.java
│   └── resources/
│       ├── messages.properties          # Default (English)
│       ├── messages_de.properties       # German
│       ├── messages_ja.properties       # Japanese
│       ├── messages_es.properties       # Spanish
│       ├── application.yml              # Spring config
│       └── templates/
│           └── home.html                # Thymeleaf with #{...}
├── pom.xml
└── build.gradle

Kesalahan Umum

Karakter Non-ASCII Tampil Kacau

File Java .properties menggunakan pengodean ISO-8859-1 secara default, bukan UTF-8. Karakter seperti umlaut (ü) atau CJK tampil kacau. Perbaikan: atur spring.messages.encoding=UTF-8 di application.yml atau gunakan escape Unicode seperti \u00FC dalam file .properties. ReloadableResourceBundleMessageSource Spring Boot menggunakan UTF-8 secara default, tetapi ResourceBundleMessageSource tidak.

ChoiceFormat Rusak untuk Bentuk Jamak Non-Inggris

ChoiceFormat Java ({0,choice,0#|1#|1<}) hanya mendukung rentang numerik—format ini tidak dapat menyatakan kategori bentuk jamak CLDR seperti 'few' atau 'many'. Bahasa seperti Arab (6 bentuk), Polski (3 bentuk), dan Rusia (3 bentuk) memerlukan ICU4J agar bentuk jamaknya benar. Jangan berasumsi ChoiceFormat menangani semua bahasa.

Perubahan Terjemahan Tidak Tercermin

ResourceBundleMessageSource menyimpan cache bundle tanpa batas secara default. Selama pengembangan, gunakan ReloadableResourceBundleMessageSource dengan cacheSeconds=0 untuk melihat perubahan tanpa memulai ulang. Di produksi, atur durasi cache yang wajar (misalnya 3.600 detik) untuk menyeimbangkan performa dan kecepatan pembaruan.

Fallback Tidak Terduga ke Bahasa JVM

Secara default, Spring beralih ke bahasa default JVM (Locale.getDefault()), bukan file messages.properties. Atur spring.messages.fallback-to-system-locale=false di application.yml agar selalu menggunakan bundle default. Jika tidak, server dengan bahasa JVM 'fr' akan menampilkan bahasa Prancis, bukan Inggris, saat kunci hilang dalam bahasa yang diminta.

Coba i18n Agent Sekarang

Lepaskan file terjemahan Anda di sini

JSON, YAML, PO, XML, CSV, Markdown, Properties

atau klik untuk menjelajahi

Bahasa target

Tidak perlu mendaftarEstimasi instan

Fallback Bahasa dengan spring-locale-chain

Saat kunci terjemahan tidak ada dalam bahasa regional seperti pt-BR, Spring Boot langsung beralih ke bahasa default alih-alih memeriksa bahasa induk pt terlebih dahulu.

Terminal
<!-- Maven -->
<dependency>
  <groupId>ai.i18nagent</groupId>
  <artifactId>spring-locale-chain</artifactId>
</dependency>
Configuration
# application.yml
locale-chain:
  fallbacks:
    pt-BR:
      - pt
      - en
    zh-Hant-HK:
      - zh-Hant
      - zh
      - en

Lihat Panduan Fallback Bahasa kami untuk daftar lengkap framework yang didukung dan 75 rantai bawaan. Learn more →

Pertanyaan yang Sering Diajukan