Skip to content

Commit 9e52649

Browse files
elylucasctflelylucasCopilot
authored
fix: strip any extensions that might be passed in with the locale [ANDR-14] (#330)
* fix: strip any extensions that might be passed in with the locale [ANDR-14] * Update src/main/java/com/contentful/java/cda/AbsQuery.java Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Ely Lucas <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 7f212ba commit 9e52649

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed

src/main/java/com/contentful/java/cda/AbsQuery.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public Query withContentType(String contentType) {
7575

7676
/**
7777
* Requesting content with specific locale.
78+
* If the locale contains Unicode extension tags (e.g., "en-CA-u-fw-sun-mu-celsius") they will be stripped.
7879
*
7980
* @param locale the locale to be used.
8081
* @return the calling query for chaining.
@@ -85,12 +86,14 @@ public Query withContentType(String contentType) {
8586
public Query withLocale(String locale) {
8687
checkNotNull(locale, "Locale must not be null.");
8788

89+
String normalizedLocale = stripExtensionsFromLocale(locale);
90+
8891
if (params.get(PARAMETER_LOCALE) != null) {
8992
throw new IllegalStateException(format("Locale \"%s\" is already present in query.",
9093
params.get(PARAMETER_LOCALE)));
9194
}
9295

93-
params.put(PARAMETER_LOCALE, locale);
96+
params.put(PARAMETER_LOCALE, normalizedLocale);
9497

9598
return (Query) this;
9699
}
@@ -487,4 +490,37 @@ private int countDots(String text) {
487490
}
488491
return count;
489492
}
493+
494+
/**
495+
* Normalizes a locale string by removing Unicode extension tags.
496+
* <p>
497+
* Starting with Android 14 (API 34), the ICU library provides extended locale strings
498+
* that include user preferences like "en-CA-u-fw-sun-mu-celsius". These extensions are
499+
* not supported by Contentful's API, so we strip them to get the base locale code.
500+
* <p>
501+
* Examples:
502+
* <ul>
503+
* <li>"en-CA-u-fw-sun-mu-celsius" → "en-CA"</li>
504+
* <li>"en-GB-u-hc-h24" → "en-GB"</li>
505+
* <li>"en-US" → "en-US" (unchanged)</li>
506+
* </ul>
507+
*
508+
* @param locale the locale string to normalize
509+
* @return the normalized locale string without Unicode extension tags
510+
*/
511+
private String stripExtensionsFromLocale(String locale) {
512+
if (locale == null || locale.isEmpty()) {
513+
return locale;
514+
}
515+
516+
// Find the Unicode extension separator "-u-"
517+
int extensionIndex = locale.indexOf("-u-");
518+
if (extensionIndex > 0) {
519+
// Strip everything from "-u-" onwards
520+
return locale.substring(0, extensionIndex);
521+
}
522+
523+
// No Unicode extensions found, return as-is
524+
return locale;
525+
}
490526
}

src/test/java/com/contentful/java/cda/AbsQueryTest.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,97 @@ public void settingLocaleTypeTwiceThrowsError() {
7272
query.withLocale("de");
7373
}
7474

75+
@Test
76+
public void localeWithStandardFormat() {
77+
query.withLocale("en-US");
78+
79+
assertThat(query.params).containsEntry("locale", "en-US");
80+
}
81+
82+
@Test
83+
public void localeWithAndroid14ExtensionIsNormalized() {
84+
// Android 14+ may send locales with Unicode extension tags like "en-CA-u-fw-sun-mu-celsius"
85+
query.withLocale("en-CA-u-fw-sun-mu-celsius");
86+
87+
// Should be normalized to just "en-CA"
88+
assertThat(query.params).containsEntry("locale", "en-CA");
89+
}
90+
91+
@Test
92+
public void localeWithMultipleUnicodeExtensions() {
93+
// Test locale with multiple Unicode extension tags
94+
query.withLocale("en-GB-u-hc-h24-fw-mon");
95+
96+
// Should strip all extensions after "-u-"
97+
assertThat(query.params).containsEntry("locale", "en-GB");
98+
}
99+
100+
@Test
101+
public void localeWithSimpleUnicodeExtension() {
102+
// Test with a simple Unicode extension
103+
query.withLocale("en-US-u-ca-gregory");
104+
105+
assertThat(query.params).containsEntry("locale", "en-US");
106+
}
107+
108+
@Test
109+
public void localeWithOnlyLanguageCode() {
110+
// Test with just a language code (no region)
111+
query.withLocale("en");
112+
113+
assertThat(query.params).containsEntry("locale", "en");
114+
}
115+
116+
@Test
117+
public void localeWithThreeLetterRegionCode() {
118+
// Test with three-letter region code
119+
query.withLocale("zh-CHN");
120+
121+
assertThat(query.params).containsEntry("locale", "zh-CHN");
122+
}
123+
124+
@Test
125+
public void localeWithScriptAndRegion() {
126+
// Test with script and region (e.g., Chinese)
127+
query.withLocale("zh-Hans-CN");
128+
129+
assertThat(query.params).containsEntry("locale", "zh-Hans-CN");
130+
}
131+
132+
@Test
133+
public void localeWithScriptRegionAndUnicodeExtension() {
134+
// Test with script, region, and Unicode extension
135+
query.withLocale("zh-Hans-CN-u-ca-chinese");
136+
137+
// Should preserve script and region, but strip Unicode extension
138+
assertThat(query.params).containsEntry("locale", "zh-Hans-CN");
139+
}
140+
141+
@Test
142+
public void localeWithUAtStart() {
143+
// Edge case: "u" at the start should not be treated as extension
144+
query.withLocale("uz-UZ");
145+
146+
assertThat(query.params).containsEntry("locale", "uz-UZ");
147+
}
148+
149+
@Test
150+
public void localeWithUppercaseU() {
151+
// Edge case: uppercase "U" should not be treated as extension separator
152+
query.withLocale("en-US-U-test");
153+
154+
// "-U-" (uppercase) should not be treated as Unicode extension separator
155+
assertThat(query.params).containsEntry("locale", "en-US-U-test");
156+
}
157+
158+
@Test
159+
public void localeEmpty() {
160+
// Empty locale should be handled gracefully (will be caught by checkNotNull)
161+
query.withLocale("");
162+
163+
assertThat(query.params).containsEntry("locale", "");
164+
}
165+
75166
@Test
76167
public void select() {
77168
query.withContentType("foo");

0 commit comments

Comments
 (0)