Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit f6c809f

Browse files
committed
Conform locale matching with RFC4647 fix jooby-project#273
1 parent 75bc639 commit f6c809f

File tree

20 files changed

+206
-46
lines changed

20 files changed

+206
-46
lines changed

coverage-report/src/test/java/org/jooby/BuiltinBodyParserFeature.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,12 @@ public void optionalBody() throws Exception {
105105
public void localeBody() throws Exception {
106106
request()
107107
.post("/locale")
108-
.body("es_AR", "text/palin")
108+
.body("es-AR", "text/palin")
109109
.expect("es_AR");
110110

111111
request()
112112
.post("/r/locale")
113-
.body("es_AR", "text/palin")
113+
.body("es-AR", "text/palin")
114114
.expect("es_AR");
115115

116116
}

coverage-report/src/test/java/org/jooby/DateTimeFormatterFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public String formatter(final long time, final org.jooby.Request req) {
5555
{
5656
use(ConfigFactory
5757
.empty()
58-
.withValue("application.lang", ConfigValueFactory.fromAnyRef("en_US"))
58+
.withValue("application.lang", ConfigValueFactory.fromAnyRef("en-US"))
5959
.withValue("application.dateFormat", ConfigValueFactory.fromAnyRef("MM/dd/yy H:mm"))
6060
.withValue("application.tz", ConfigValueFactory.fromAnyRef("GMT")));
6161
use(Resource.class);

coverage-report/src/test/java/org/jooby/LocaleFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public String locale(final org.jooby.Request req) {
3232
}
3333

3434
{
35-
use(ConfigFactory.empty().withValue("application.lang", ConfigValueFactory.fromAnyRef("es_ar")));
35+
use(ConfigFactory.empty().withValue("application.lang", ConfigValueFactory.fromAnyRef("es-ar")));
3636
use(Resource.class);
3737
}
3838

coverage-report/src/test/java/org/jooby/NumberFormatterFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public String formatter() {
3939

4040
{
4141
use(ConfigFactory.empty().withValue("application.lang",
42-
ConfigValueFactory.fromAnyRef("en_US")));
42+
ConfigValueFactory.fromAnyRef("en-US")));
4343

4444
use(Resource.class);
4545
}

coverage-report/src/test/java/org/jooby/assets/AssetsBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import com.typesafe.config.ConfigFactory;
1212
import com.typesafe.config.ConfigValueFactory;
1313

14-
public class AssetsBase extends ServerFeature {
14+
public abstract class AssetsBase extends ServerFeature {
1515

1616
public List<Object> list(final Object... args) {
1717
return Arrays.asList(args);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.jooby.issues;
2+
3+
import java.util.Locale;
4+
5+
import org.jooby.test.ServerFeature;
6+
import org.junit.Test;
7+
8+
import com.typesafe.config.ConfigFactory;
9+
import com.typesafe.config.ConfigValueFactory;
10+
11+
public class Issue273 extends ServerFeature {
12+
13+
{
14+
use(ConfigFactory.empty()
15+
.withValue("application.lang", ConfigValueFactory.fromAnyRef("en-us")));
16+
17+
get("/273", req -> {
18+
Locale selected = req.locale();
19+
Locale noMatch = req.locale(Locale.forLanguageTag("de-at"));
20+
Locale match = req.locale(Locale.forLanguageTag("fr"));
21+
return selected + ";" + noMatch + ";" + match;
22+
});
23+
}
24+
25+
@Test
26+
public void shouldHanleComplexLocaleExpressions() throws Exception {
27+
request().get("/273")
28+
.header("Accept-Language", "de-DE,de;q=0.8,fr-CA;q=0.7,fr;q=0.5,en-CA;q=0.3,en;q=0.2")
29+
.expect("de_DE;en_US;fr");
30+
}
31+
32+
}

jooby/src/main/java/org/jooby/Env.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ public String toString() {
126126
*/
127127
Config config();
128128

129+
/**
130+
* @return Default locale from <code>application.lang</code>.
131+
*/
129132
Locale locale();
130133

131134
/**

jooby/src/main/java/org/jooby/Jooby.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,7 +3178,7 @@ private Injector bootstrap(final Consumer<List<Route.Definition>> rcallback) thr
31783178
.orElseGet(
31793179
() -> ConfigFactory.parseResources("application.conf")));
31803180

3181-
final Locale locale = LocaleUtils.toLocale(config.getString("application.lang"));
3181+
final Locale locale = LocaleUtils.parseOne(config.getString("application.lang"));
31823182

31833183
Env env = this.env.build(config, locale);
31843184
String envname = env.name();
@@ -3503,13 +3503,14 @@ private Config defaultConfig(final Config config) {
35033503
String appname = parts[parts.length - 1];
35043504

35053505
// locale
3506-
final Locale locale;
3506+
final List<Locale> locales;
35073507
if (!config.hasPath("application.lang")) {
3508-
locale = Locale.getDefault();
3508+
locales = ImmutableList.of(Locale.getDefault());
35093509
} else {
3510-
locale = LocaleUtils.toLocale(config.getString("application.lang"));
3510+
locales = LocaleUtils.parse(config.getString("application.lang"));
35113511
}
3512-
String lang = locale.getLanguage() + "_" + locale.getCountry();
3512+
Locale locale = locales.iterator().next();
3513+
String lang = locale.toLanguageTag();
35133514

35143515
// time zone
35153516
final String tz;

jooby/src/main/java/org/jooby/Request.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static java.util.Objects.requireNonNull;
2222

2323
import java.nio.charset.Charset;
24+
import java.util.Arrays;
2425
import java.util.List;
2526
import java.util.Locale;
2627
import java.util.Map;
@@ -191,6 +192,21 @@ public Locale locale() {
191192
return req.locale();
192193
}
193194

195+
@Override
196+
public Locale locale(final Iterable<Locale> locales) {
197+
return req.locale(locales);
198+
}
199+
200+
@Override
201+
public Locale locale(final Locale... locales) {
202+
return req.locale(locales);
203+
}
204+
205+
@Override
206+
public List<Locale> locales() {
207+
return req.locales();
208+
}
209+
194210
@Override
195211
public String ip() {
196212
return req.ip();
@@ -609,9 +625,41 @@ default <T> T require(final TypeLiteral<T> type) {
609625
* Get the content of the <code>Accept-Language</code> header. If the request doens't specify
610626
* such header, this method return the global locale: <code>application.lang</code>.
611627
*
628+
* @return List of locale.
629+
*/
630+
List<Locale> locales();
631+
632+
/**
633+
* Get the content of the <code>Accept-Language</code> header. If the request doens't specify
634+
* such header, this method return the global locale: <code>application.lang</code>.
635+
*
636+
* @return A locale.
637+
*/
638+
default Locale locale() {
639+
return locales().get(0);
640+
}
641+
642+
/**
643+
* Select a locale in the provided <code>collection</code> based on the
644+
* content of the <code>Accept-Language</code> header using the lookup
645+
* algorithm of RFC4647. If the request doens't specify such header, this
646+
* method return the global locale: <code>application.lang</code>.
647+
*
612648
* @return A locale.
613649
*/
614-
Locale locale();
650+
Locale locale(Iterable<Locale> locales);
651+
652+
/**
653+
* Select a locale in the provided <code>collection</code> based on the
654+
* content of the <code>Accept-Language</code> header using the lookup
655+
* algorithm of RFC4647. If the request doens't specify such header, this
656+
* method return the global locale: <code>application.lang</code>.
657+
*
658+
* @return A locale.
659+
*/
660+
default Locale locale(final Locale... locales) {
661+
return locale(Arrays.asList(locales));
662+
}
615663

616664
/**
617665
* @return The length, in bytes, of the request body and made available by the input stream, or
@@ -636,7 +684,8 @@ default <T> T require(final TypeLiteral<T> type) {
636684
Route route();
637685

638686
/**
639-
* The fully qualified name of the resource being requested, as obtained from the Host HTTP header.
687+
* The fully qualified name of the resource being requested, as obtained from the Host HTTP
688+
* header.
640689
*
641690
* @return The fully qualified name of the server.
642691
*/

jooby/src/main/java/org/jooby/internal/LocaleUtils.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,28 @@
3232
*/
3333
package org.jooby.internal;
3434

35+
import java.util.Comparator;
36+
import java.util.List;
3537
import java.util.Locale;
38+
import java.util.stream.Collectors;
3639

3740
public class LocaleUtils {
3841

39-
public static Locale toLocale(final String locale) {
40-
final String[] parts = locale.replace("-", "_").split("_");
41-
if (parts.length == 1) {
42-
return new Locale(locale, "");
43-
} else if (parts.length == 2) {
44-
return new Locale(parts[0], parts[1]);
45-
} else {
46-
return new Locale(parts[0], parts[1], parts[2]);
47-
}
42+
public static List<Locale> parse(final String value) {
43+
return range(value).stream()
44+
.map(r -> Locale.forLanguageTag(r.getRange()))
45+
.collect(Collectors.toList());
4846
}
47+
48+
public static Locale parseOne(final String value) {
49+
return parse(value).get(0);
50+
}
51+
52+
public static List<Locale.LanguageRange> range(final String value) {
53+
List<Locale.LanguageRange> range = Locale.LanguageRange.parse(value);
54+
return range.stream()
55+
.sorted(Comparator.comparing(Locale.LanguageRange::getWeight).reversed())
56+
.collect(Collectors.toList());
57+
}
58+
4959
}

0 commit comments

Comments
 (0)