Add i18n module: MessageSource, LocaleResolver and localized ProblemDetail
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
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# GET /api/greeting under different Accept-Language headers (Spring Boot's default LocaleResolver)
|
||||
|
||||
Accept-Language: mr-IN -> status=200 locale=mr_IN message=नमस्कार, Asha!
|
||||
Accept-Language: hi -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
Accept-Language: mr;q=0.4, hi;q=0.9 -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
Accept-Language: en-GB -> status=200 locale=en_GB message=Hello, Asha!
|
||||
Accept-Language: fr -> status=200 locale=fr message=Hello, Asha!
|
||||
Accept-Language: (no header) -> status=200 locale=en_US message=Hello, Asha!
|
||||
@@ -0,0 +1,11 @@
|
||||
# The JVM's default locale is Hindi: what does a French or an English request get?
|
||||
|
||||
Locale.getDefault() while these ran: hi
|
||||
|
||||
--- spring.messages.fallback-to-system-locale left at its default ---
|
||||
Accept-Language: fr -> status=200 locale=fr message=नमस्ते, Asha!
|
||||
Accept-Language: en -> status=200 locale=en message=नमस्ते, Asha!
|
||||
|
||||
--- spring.messages.fallback-to-system-locale=false ---
|
||||
Accept-Language: fr -> status=200 locale=fr message=Hello, Asha!
|
||||
Accept-Language: en -> status=200 locale=en message=Hello, Asha!
|
||||
@@ -0,0 +1,20 @@
|
||||
# Letting the user pick a language: ?lang=mr with a cookie resolver, and two ways to get it wrong
|
||||
|
||||
|
||||
--- A. CookieLocaleResolver named localeResolver + LocaleChangeInterceptor (profile 'cookie') ---
|
||||
no cookie yet, Accept-Language: hi -> status=200 locale=en message=Hello, Asha!
|
||||
?lang=mr, Accept-Language: hi -> status=200 locale=mr message=नमस्कार, Asha!
|
||||
Set-Cookie: lang=mr; Path=/; Max-Age=2592000; SameSite=Lax
|
||||
Cookie: lang=mr, Accept-Language: hi -> status=200 locale=mr message=नमस्कार, Asha!
|
||||
|
||||
--- B. the same resolver under the bean name cookieLocaleResolver (profile 'cookiewrongname') ---
|
||||
Accept-Language: hi -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
?lang=mr -> status=500 (Tomcat's own HTML error page)
|
||||
bean 'cookieLocaleResolver' exists: true
|
||||
bean 'localeResolver' is a: AcceptHeaderLocaleResolver
|
||||
what the interceptor triggers: UnsupportedOperationException: Cannot change HTTP Accept-Language header - use a different locale resolution strategy
|
||||
|
||||
--- C. only the interceptor, Spring Boot's default resolver (profile 'interceptoronly') ---
|
||||
?lang=mr -> status=500 (Tomcat's own HTML error page)
|
||||
bean 'localeResolver' is a: AcceptHeaderLocaleResolver
|
||||
what the interceptor triggers: UnsupportedOperationException: Cannot change HTTP Accept-Language header - use a different locale resolution strategy
|
||||
@@ -0,0 +1,9 @@
|
||||
# The same sentence with a doubled and a single apostrophe, with and without arguments
|
||||
|
||||
apos.doubled = Order {0} can''t be shipped yet.
|
||||
apos.single = Order {0} can't be shipped yet.
|
||||
|
||||
with an argument: doubled -> Order A-1 can't be shipped yet.
|
||||
with an argument: single -> Order A-1 cant be shipped yet.
|
||||
without arguments: doubled -> Order {0} can''t be shipped yet.
|
||||
without arguments: single -> Order {0} can't be shipped yet.
|
||||
@@ -0,0 +1,11 @@
|
||||
# GET /api/cart?items=1234567 in three languages, and two ways to keep Latin digits
|
||||
|
||||
Accept-Language: en -> You have 1,234,567 items in your cart.
|
||||
Accept-Language: hi -> आपकी कार्ट में 1,234,567 वस्तुएँ हैं।
|
||||
Accept-Language: mr -> तुमच्या कार्टमध्ये १,२३४,५६७ वस्तू आहेत.
|
||||
|
||||
--- fix 1: the client asks for Latin digits (Unicode extension -u-nu-latn) ---
|
||||
Accept-Language: mr-IN-u-nu-latn -> locale=mr_IN_#u-nu-latn तुमच्या कार्टमध्ये 1,234,567 वस्तू आहेत.
|
||||
|
||||
--- fix 2: the server formats the number itself and passes text (GET /api/cart-latin) ---
|
||||
Accept-Language: mr -> तुमच्या कार्टमध्ये 1,234,567 वस्तू आहेत.
|
||||
@@ -0,0 +1,4 @@
|
||||
# A key that exists only in English, and a key that exists nowhere
|
||||
|
||||
only.english.key, locale mr -> This sentence has not been translated yet.
|
||||
no.such.key, locale mr -> org.springframework.context.NoSuchMessageException: No message found under code 'no.such.key' for locale 'mr'.
|
||||
@@ -0,0 +1,4 @@
|
||||
# The same Marathi greeting, with Spring Boot's default encoding and with spring.messages.encoding=ISO-8859-1
|
||||
|
||||
default (UTF-8): नमस्कार, Asha!
|
||||
ISO-8859-1: नमसà¥à¤à¤¾à¤°, Asha!
|
||||
@@ -0,0 +1,39 @@
|
||||
# POST /api/customers with an invalid body, four Accept-Language values
|
||||
|
||||
body: {"name":"","email":"x","age":10}
|
||||
|
||||
--- Accept-Language: en ---
|
||||
status=400 Content-Type=application/problem+json
|
||||
title: Validation failed
|
||||
detail: The request has invalid fields.
|
||||
error: age: You must be at least 18 years old.
|
||||
error: city: must not be null
|
||||
error: email: Enter a valid email address.
|
||||
error: name: Name is required.
|
||||
|
||||
--- Accept-Language: mr ---
|
||||
status=400 Content-Type=application/problem+json
|
||||
title: पडताळणी अयशस्वी
|
||||
detail: विनंतीतील काही फील्ड चुकीची आहेत.
|
||||
error: age: तुमचे वय किमान 18 वर्षे असणे आवश्यक आहे.
|
||||
error: city: रिक्त नसावे.
|
||||
error: email: कृपया वैध ईमेल पत्ता द्या.
|
||||
error: name: नाव आवश्यक आहे.
|
||||
|
||||
--- Accept-Language: hi ---
|
||||
status=400 Content-Type=application/problem+json
|
||||
title: सत्यापन विफल
|
||||
detail: अनुरोध के कुछ फ़ील्ड अमान्य हैं।
|
||||
error: age: आपकी आयु कम से कम 18 वर्ष होनी चाहिए।
|
||||
error: city: खाली नहीं होना चाहिए।
|
||||
error: email: कृपया सही ईमेल पता दर्ज करें।
|
||||
error: name: नाम आवश्यक है।
|
||||
|
||||
--- Accept-Language: fr ---
|
||||
status=400 Content-Type=application/problem+json
|
||||
title: Validation failed
|
||||
detail: The request has invalid fields.
|
||||
error: age: You must be at least 18 years old.
|
||||
error: city: ne doit pas être nul
|
||||
error: email: Enter a valid email address.
|
||||
error: name: Name is required.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Three kinds of exception: an ErrorResponse, an ordinary exception handled through the MessageSource, and one handled with English text in the source
|
||||
|
||||
|
||||
--- an ErrorResponse (OrderNotFoundException) ---
|
||||
Accept-Language: en
|
||||
status=404 Content-Type=application/problem+json
|
||||
title: Order not found
|
||||
detail: No order with id 7 exists.
|
||||
Accept-Language: mr
|
||||
status=404 Content-Type=application/problem+json
|
||||
title: ऑर्डर सापडली नाही
|
||||
detail: ७ क्रमांकाची ऑर्डर अस्तित्वात नाही.
|
||||
Accept-Language: hi
|
||||
status=404 Content-Type=application/problem+json
|
||||
title: ऑर्डर नहीं मिला
|
||||
detail: क्रमांक 7 वाला ऑर्डर मौजूद नहीं है।
|
||||
|
||||
--- an ordinary exception, translated in the handler (PaymentDeclinedException) ---
|
||||
Accept-Language: en
|
||||
status=402 Content-Type=application/problem+json
|
||||
title: Payment declined
|
||||
detail: Payment of 499.00 was declined.
|
||||
Accept-Language: mr
|
||||
status=402 Content-Type=application/problem+json
|
||||
title: पेमेंट नाकारले
|
||||
detail: 499.00 चे पेमेंट नाकारले गेले.
|
||||
Accept-Language: hi
|
||||
status=402 Content-Type=application/problem+json
|
||||
title: भुगतान अस्वीकृत
|
||||
detail: 499.00 का भुगतान अस्वीकार कर दिया गया।
|
||||
|
||||
--- an ordinary exception, English in the handler (LegacyStateException) ---
|
||||
Accept-Language: en
|
||||
status=409 Content-Type=application/problem+json
|
||||
title: Conflict
|
||||
detail: The order is in a state that does not allow this.
|
||||
Accept-Language: mr
|
||||
status=409 Content-Type=application/problem+json
|
||||
title: Conflict
|
||||
detail: The order is in a state that does not allow this.
|
||||
Accept-Language: hi
|
||||
status=409 Content-Type=application/problem+json
|
||||
title: Conflict
|
||||
detail: The order is in a state that does not allow this.
|
||||
@@ -0,0 +1,6 @@
|
||||
# The same four lookups with spring.messages.always-use-message-format=true
|
||||
|
||||
with an argument: doubled -> Order A-1 can't be shipped yet.
|
||||
with an argument: single -> Order A-1 cant be shipped yet.
|
||||
without arguments: doubled -> Order {0} can't be shipped yet.
|
||||
without arguments: single -> Order {0} cant be shipped yet.
|
||||
@@ -0,0 +1,12 @@
|
||||
# spring.web.locale on its own, and with spring.web.locale-resolver=fixed
|
||||
|
||||
|
||||
--- spring.web.locale=hi (spring.web.locale-resolver left at accept-header) ---
|
||||
Accept-Language: (no header) -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
Accept-Language: mr -> status=200 locale=mr message=नमस्कार, Asha!
|
||||
Accept-Language: fr -> status=200 locale=fr message=Hello, Asha!
|
||||
|
||||
--- spring.web.locale=hi and spring.web.locale-resolver=fixed ---
|
||||
Accept-Language: (no header) -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
Accept-Language: mr -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
Accept-Language: fr -> status=200 locale=hi message=नमस्ते, Asha!
|
||||
@@ -0,0 +1,31 @@
|
||||
# spring.messages.* and spring.web.locale* in the configuration metadata shipped with Spring Boot 4.1.1
|
||||
|
||||
jar: spring-boot-autoconfigure-4.1.1.jar
|
||||
|
||||
spring.messages.always-use-message-format
|
||||
type: java.lang.Boolean
|
||||
default: false
|
||||
spring.messages.basename
|
||||
type: java.util.List<java.lang.String>
|
||||
default: ["messages"]
|
||||
spring.messages.cache-duration
|
||||
type: java.time.Duration
|
||||
default: (none)
|
||||
spring.messages.common-messages
|
||||
type: java.util.List<org.springframework.core.io.Resource>
|
||||
default: (none)
|
||||
spring.messages.encoding
|
||||
type: java.nio.charset.Charset
|
||||
default: "UTF-8"
|
||||
spring.messages.fallback-to-system-locale
|
||||
type: java.lang.Boolean
|
||||
default: true
|
||||
spring.messages.use-code-as-default-message
|
||||
type: java.lang.Boolean
|
||||
default: false
|
||||
spring.web.locale
|
||||
type: java.util.Locale
|
||||
default: (none)
|
||||
spring.web.locale-resolver
|
||||
type: org.springframework.boot.autoconfigure.web.WebProperties$LocaleResolver
|
||||
default: "accept-header"
|
||||
Reference in New Issue
Block a user