English, Marathi and Hindi bundles behind one small web app, with tests that run it on a real port and write the transcripts (01-11) quoted in the article, plus a jar-metadata capture (12). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
4.9 KiB
i18n
Companion project for the article Internationalization (i18n) in Spring Boot 4: MessageSource, LocaleResolver and Localized ProblemDetail on 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
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 |