# i18n Companion project for the article **[Internationalization (i18n) in Spring Boot 4: MessageSource, LocaleResolver and Localized ProblemDetail](https://ankurm.com/spring-boot-4-internationalization-messagesource-localeresolver-problemdetail/)** on **[ankurm.com](https://ankurm.com)**. One small web application that answers in English, Marathi and Hindi, plus the test suite that starts it on a real port and records what it says. Every console block quoted in the article came out of `output/`. Transcripts 01-11 are written by the test suite (so a claim that stops being true turns the build red); 12 is read out of the Spring Boot jar by `scripts/capture-facts.sh`. There is deliberately **no `docs/` folder**: the deeper material lives in collapsible "going deeper" sections inside the article itself, next to the paragraph each one extends. ## Versions | | | |---|---| | Spring Boot | 4.1.1 | | Spring Framework | 7.0.9 | | JDK | 25 (Temurin 25.0.4.1+1) | | Maven | 3.9 | ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 mvn test # runs every scenario and rewrites output/01-11 ./scripts/run-all.sh # everything, including 12 (needs python3) ``` The tests pin the JVM to `-Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8` (see `pom.xml`) so the transcripts do not depend on the machine they were generated on, and run each test class in its own JVM (`reuseForks=false`) because Tomcat reads the JVM's default locale once. ## Profiles | Profile | What it turns on | |---|---| | *(none)* | Spring Boot's defaults: `AcceptHeaderLocaleResolver`, basename `messages`, UTF-8 | | `cookie` | `CookieLocaleResolver("lang")` named `localeResolver`, plus a `LocaleChangeInterceptor` for `?lang=` | | `cookiewrongname` | the same resolver under the bean name `cookieLocaleResolver`, which Spring MVC ignores | | `interceptoronly` | the `LocaleChangeInterceptor` with Spring Boot's default resolver | ## Endpoints | Endpoint | What it shows | |---|---| | `GET /api/greeting?name=` | `MessageSource` with a `Locale` controller argument | | `GET /api/cart?items=` | a number formatted by `MessageFormat` in the request locale | | `GET /api/cart-latin?items=` | the same sentence with the number pre-formatted as text | | `GET /api/orders/{id}` | `ErrorResponseException`: `problemDetail.*` keys translate it automatically | | `POST /api/customers` | Bean Validation messages and a localized `MethodArgumentNotValidException` | | `GET /api/pay` | an ordinary exception translated in the `@ExceptionHandler` | | `GET /api/legacy` | an ordinary exception whose handler hard-codes English | ## Source layout | Path | What it holds | |---|---| | `src/main/resources/messages*.properties` | English, Marathi (`_mr`) and Hindi (`_hi`), UTF-8 | | `web/ShopController` | the endpoints above | | `web/CustomerRequest` | Bean Validation with `{message.key}` placeholders | | `web/OrderNotFoundException` | an `ErrorResponseException` with a message-code argument | | `web/ApiExceptionHandler` | `ResponseEntityExceptionHandler` subclass | | `config/LocaleConfig` | one nested configuration per profile | | `src/test/.../LocaleResolutionTests` | transcripts 01, 03, 11 | | `src/test/.../SystemLocaleFallbackTests` | transcript 02 (own JVM) | | `src/test/.../MessagesTests` | transcripts 04-07 and 10 | | `src/test/.../ProblemDetailTests` | transcripts 08, 09 | ## Index of captured output | File | Written by | What it shows | |---|---|---| | `01-accept-language.txt` | `LocaleResolutionTests` | which locale each `Accept-Language` header resolves to, and what a missing header gives | | `02-fallback-to-system-locale.txt` | `SystemLocaleFallbackTests` | a French or English request served in Hindi because the JVM default is Hindi | | `03-cookie-locale-resolver.txt` | `LocaleResolutionTests` | `?lang=mr`, the `Set-Cookie`, the cookie honoured later; and the two wiring mistakes | | `04-messageformat-apostrophes.txt` | `MessagesTests` | `''` vs `'`, with and without arguments | | `05-devanagari-digits.txt` | `MessagesTests` | Marathi renders `1234567` in Devanagari digits, Hindi does not; two ways to keep Latin digits | | `06-missing-keys.txt` | `MessagesTests` | a key present only in English, and a key present nowhere | | `07-properties-encoding.txt` | `MessagesTests` | the same file read as UTF-8 and as ISO-8859-1 | | `08-validation-messages.txt` | `ProblemDetailTests` | a 400 in four languages, validation messages and all | | `09-problemdetail-localized.txt` | `ProblemDetailTests` | three kinds of exception, translated or not | | `10-always-use-message-format.txt` | `MessagesTests` | the apostrophe lookups with `spring.messages.always-use-message-format=true` | | `11-spring-web-locale.txt` | `LocaleResolutionTests` | `spring.web.locale` and `spring.web.locale-resolver=fixed` | | `12-configuration-metadata.txt` | `capture-facts.sh` | every `spring.messages.*` and `spring.web.locale*` property, type and default |