Skip to content

Commit 7e74f43

Browse files
duonglaiquangrbri
authored andcommitted
Intl: implement getCanonicalLocales()
1 parent d1a8e35 commit 7e74f43

2 files changed

Lines changed: 113 additions & 0 deletions

File tree

  • src

src/main/java/org/htmlunit/javascript/host/intl/Intl.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,27 @@
1717
import static org.htmlunit.BrowserVersionFeatures.JS_INTL_V8_BREAK_ITERATOR;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.ArrayList;
21+
import java.util.IllformedLocaleException;
22+
import java.util.LinkedHashSet;
23+
import java.util.List;
2024
import java.util.Map;
25+
import java.util.Set;
2126

2227
import org.htmlunit.BrowserVersion;
28+
import org.htmlunit.corejs.javascript.Context;
29+
import org.htmlunit.corejs.javascript.Function;
2330
import org.htmlunit.corejs.javascript.FunctionObject;
31+
import org.htmlunit.corejs.javascript.NativeArray;
2432
import org.htmlunit.corejs.javascript.Scriptable;
2533
import org.htmlunit.corejs.javascript.ScriptableObject;
34+
import org.htmlunit.corejs.javascript.TopLevel;
2635
import org.htmlunit.javascript.HtmlUnitScriptable;
2736
import org.htmlunit.javascript.JavaScriptEngine;
2837
import org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
2938
import org.htmlunit.javascript.configuration.ClassConfiguration;
3039
import org.htmlunit.javascript.configuration.JsxClass;
40+
import org.htmlunit.javascript.configuration.JsxStaticFunction;
3141

3242
/**
3343
* A JavaScript object for {@code Intl}.
@@ -95,4 +105,72 @@ private static void defineStaticFunctions(final ClassConfiguration config,
95105
}
96106
}
97107
}
108+
109+
/**
110+
* Returns an array containing the canonical locale names.
111+
* Duplicates will be omitted and elements will be validated as structurally valid language tags.
112+
*
113+
* @param cx the current context
114+
* @param thisObj the scriptable this
115+
* @param args the arguments
116+
* @param funObj the function object
117+
* @return an array of canonical locale names
118+
*
119+
* @see <a href="https://tc39.es/ecma402/#sec-intl.getcanonicallocales">spec</a>
120+
*/
121+
@JsxStaticFunction
122+
public static Object getCanonicalLocales(final Context cx, final Scriptable thisObj,
123+
final Object[] args, final Function funObj) {
124+
if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
125+
return cx.newArray(TopLevel.getTopLevelScope(thisObj), new Object[0]);
126+
}
127+
128+
final Object localesArgument = args[0];
129+
if (localesArgument == null) {
130+
throw JavaScriptEngine.typeError("Cannot convert null to object");
131+
}
132+
133+
final List<String> languageTags = new ArrayList<>();
134+
if (localesArgument instanceof String s) {
135+
languageTags.add(s);
136+
}
137+
else if (localesArgument instanceof Scriptable scriptable) {
138+
if ("String".equals(scriptable.getClassName()) || scriptable instanceof Locale) {
139+
languageTags.add(scriptable.toString());
140+
}
141+
else if (scriptable instanceof NativeArray array) {
142+
for (int i = 0; i < array.getLength(); i++) {
143+
final Object elem = array.get(i);
144+
if (elem instanceof String s) {
145+
languageTags.add(s);
146+
}
147+
else if (elem instanceof Locale) {
148+
languageTags.add(elem.toString());
149+
}
150+
else if (elem instanceof ScriptableObject) {
151+
languageTags.add(JavaScriptEngine.toString(elem));
152+
}
153+
else {
154+
throw JavaScriptEngine.typeError("Invalid element in locales argument");
155+
}
156+
}
157+
}
158+
else {
159+
languageTags.add(JavaScriptEngine.toString(localesArgument));
160+
}
161+
}
162+
163+
final Set<String> canonicalLocales = new LinkedHashSet<>(languageTags.size());
164+
for (final String tag : languageTags) {
165+
try {
166+
canonicalLocales.add(
167+
new java.util.Locale.Builder().setLanguageTag(tag).build().toLanguageTag());
168+
}
169+
catch (final IllformedLocaleException e) {
170+
throw JavaScriptEngine.rangeError("Invalid language tag: " + tag);
171+
}
172+
}
173+
174+
return cx.newArray(TopLevel.getTopLevelScope(thisObj), canonicalLocales.toArray());
175+
}
98176
}

src/test/java/org/htmlunit/javascript/host/intl/IntlTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,39 @@ public void v8BreakIterator() throws Exception {
100100
test("Intl.v8BreakIterator");
101101
}
102102

103+
/**
104+
* @throws Exception if the test fails
105+
*/
106+
@Test
107+
@Alerts({"en-US", "en-US,fr-FR,ja-JP",
108+
"zh-Hant-TW", "en-Latn-US",
109+
"ja-JP", "en-US,fr", "en-US", "ja-JP",
110+
"TypeError", "", "", "RangeError", "RangeError", ""})
111+
public void getCanonicalLocales() throws Exception {
112+
final String html = DOCTYPE_HTML
113+
+ "<html><head>\n"
114+
+ "<script>\n"
115+
+ LOG_TITLE_FUNCTION
116+
+ " function test() {\n"
117+
+ " log(Intl.getCanonicalLocales('EN-us'));\n"
118+
+ " log(Intl.getCanonicalLocales(['en-US', 'fr-FR', 'ja-JP', 'EN-US']));\n"
119+
+ " log(Intl.getCanonicalLocales('zh-hant-tw'));\n"
120+
+ " log(Intl.getCanonicalLocales('en-latn-us'));\n"
121+
+ " log(Intl.getCanonicalLocales(new Intl.Locale('ja-JP')));\n"
122+
+ " log(Intl.getCanonicalLocales([new Intl.Locale('en-US'), new Intl.Locale('fr')]));\n"
123+
+ " log(Intl.getCanonicalLocales(new String('en-US')));\n"
124+
+ " log(Intl.getCanonicalLocales([new String('ja-JP')]));\n"
125+
+ " try { log(Intl.getCanonicalLocales(null)); } catch(e) { logEx(e) }\n"
126+
+ " try { log(Intl.getCanonicalLocales(undefined)); } catch(e) { logEx(e) }\n"
127+
+ " try { log(Intl.getCanonicalLocales([])); } catch(e) { logEx(e) }\n"
128+
+ " try { log(Intl.getCanonicalLocales('')); } catch(e) { logEx(e) }\n"
129+
+ " try { log(Intl.getCanonicalLocales('en-US!@#')); } catch(e) { logEx(e) }\n"
130+
+ " try { log(Intl.getCanonicalLocales(42)); } catch(e) { logEx(e) }\n"
131+
+ " }\n"
132+
+ "</script>\n"
133+
+ "</head><body onload='test()'>\n"
134+
+ "</body></html>";
135+
136+
loadPageVerifyTitle2(html);
137+
}
103138
}

0 commit comments

Comments
 (0)