зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1287677 - Add mozIntl.getDisplayNames API. r=Waldo
MozReview-Commit-ID: GYVlvSv3Yd9 --HG-- extra : rebase_source : 75f24ca61ab87529f72bcba3aab2157bc29bed98
This commit is contained in:
Родитель
e65067f730
Коммит
6fa9d29045
|
@ -104,6 +104,18 @@ Char16ToUChar(char16_t* chars)
|
|||
MOZ_CRASH("Char16ToUChar: Intl API disabled");
|
||||
}
|
||||
|
||||
inline char16_t*
|
||||
UCharToChar16(UChar* chars)
|
||||
{
|
||||
MOZ_CRASH("UCharToChar16: Intl API disabled");
|
||||
}
|
||||
|
||||
inline const char16_t*
|
||||
UCharToChar16(const UChar* chars)
|
||||
{
|
||||
MOZ_CRASH("UCharToChar16: Intl API disabled");
|
||||
}
|
||||
|
||||
struct UEnumeration;
|
||||
|
||||
int32_t
|
||||
|
@ -352,6 +364,27 @@ enum UCalendarDateFields {
|
|||
UCAL_DAY_OF_MONTH = UCAL_DATE
|
||||
};
|
||||
|
||||
enum UCalendarMonths {
|
||||
UCAL_JANUARY,
|
||||
UCAL_FEBRUARY,
|
||||
UCAL_MARCH,
|
||||
UCAL_APRIL,
|
||||
UCAL_MAY,
|
||||
UCAL_JUNE,
|
||||
UCAL_JULY,
|
||||
UCAL_AUGUST,
|
||||
UCAL_SEPTEMBER,
|
||||
UCAL_OCTOBER,
|
||||
UCAL_NOVEMBER,
|
||||
UCAL_DECEMBER,
|
||||
UCAL_UNDECIMBER
|
||||
};
|
||||
|
||||
enum UCalendarAMPMs {
|
||||
UCAL_AM,
|
||||
UCAL_PM
|
||||
};
|
||||
|
||||
UCalendar*
|
||||
ucal_open(const UChar* zoneID, int32_t len, const char* locale,
|
||||
UCalendarType type, UErrorCode* status)
|
||||
|
@ -416,6 +449,13 @@ ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* statu
|
|||
MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
|
||||
}
|
||||
|
||||
enum UDateTimePatternField {
|
||||
UDATPG_YEAR_FIELD,
|
||||
UDATPG_MONTH_FIELD,
|
||||
UDATPG_WEEK_OF_YEAR_FIELD,
|
||||
UDATPG_DAY_FIELD,
|
||||
};
|
||||
|
||||
typedef void* UDateTimePatternGenerator;
|
||||
|
||||
UDateTimePatternGenerator*
|
||||
|
@ -432,6 +472,14 @@ udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
|
|||
MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
|
||||
}
|
||||
|
||||
static const UChar *
|
||||
udatpg_getAppendItemName(const UDateTimePatternGenerator *dtpg,
|
||||
UDateTimePatternField field,
|
||||
int32_t *pLength)
|
||||
{
|
||||
MOZ_CRASH("udatpg_getAppendItemName: Intl API disabled");
|
||||
}
|
||||
|
||||
void
|
||||
udatpg_close(UDateTimePatternGenerator* dtpg)
|
||||
{
|
||||
|
@ -484,10 +532,46 @@ enum UDateFormatField {
|
|||
};
|
||||
|
||||
enum UDateFormatStyle {
|
||||
UDAT_FULL,
|
||||
UDAT_LONG,
|
||||
UDAT_MEDIUM,
|
||||
UDAT_SHORT,
|
||||
UDAT_DEFAULT = UDAT_MEDIUM,
|
||||
UDAT_PATTERN = -2,
|
||||
UDAT_IGNORE = UDAT_PATTERN
|
||||
};
|
||||
|
||||
enum UDateFormatSymbolType {
|
||||
UDAT_ERAS,
|
||||
UDAT_MONTHS,
|
||||
UDAT_SHORT_MONTHS,
|
||||
UDAT_WEEKDAYS,
|
||||
UDAT_SHORT_WEEKDAYS,
|
||||
UDAT_AM_PMS,
|
||||
UDAT_LOCALIZED_CHARS,
|
||||
UDAT_ERA_NAMES,
|
||||
UDAT_NARROW_MONTHS,
|
||||
UDAT_NARROW_WEEKDAYS,
|
||||
UDAT_STANDALONE_MONTHS,
|
||||
UDAT_STANDALONE_SHORT_MONTHS,
|
||||
UDAT_STANDALONE_NARROW_MONTHS,
|
||||
UDAT_STANDALONE_WEEKDAYS,
|
||||
UDAT_STANDALONE_SHORT_WEEKDAYS,
|
||||
UDAT_STANDALONE_NARROW_WEEKDAYS,
|
||||
UDAT_QUARTERS,
|
||||
UDAT_SHORT_QUARTERS,
|
||||
UDAT_STANDALONE_QUARTERS,
|
||||
UDAT_STANDALONE_SHORT_QUARTERS,
|
||||
UDAT_SHORTER_WEEKDAYS,
|
||||
UDAT_STANDALONE_SHORTER_WEEKDAYS,
|
||||
UDAT_CYCLIC_YEARS_WIDE,
|
||||
UDAT_CYCLIC_YEARS_ABBREVIATED,
|
||||
UDAT_CYCLIC_YEARS_NARROW,
|
||||
UDAT_ZODIAC_NAMES_WIDE,
|
||||
UDAT_ZODIAC_NAMES_ABBREVIATED,
|
||||
UDAT_ZODIAC_NAMES_NARROW
|
||||
};
|
||||
|
||||
int32_t
|
||||
udat_countAvailable()
|
||||
{
|
||||
|
@ -561,6 +645,13 @@ udat_close(UDateFormat* format)
|
|||
|
||||
} // anonymous namespace
|
||||
|
||||
static int32_t
|
||||
udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symbolIndex,
|
||||
UChar *result, int32_t resultLength, UErrorCode *status)
|
||||
{
|
||||
MOZ_CRASH("udat_getSymbols: Intl API disabled");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -2922,6 +3013,296 @@ js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
inline bool
|
||||
MatchPart(const char** pattern, const char (&part)[N])
|
||||
{
|
||||
if (strncmp(*pattern, part, N - 1))
|
||||
return false;
|
||||
|
||||
*pattern += N - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
// 1. Assert: locale is a string.
|
||||
MOZ_ASSERT(args[0].isString());
|
||||
// 2. Assert: style is a string.
|
||||
MOZ_ASSERT(args[1].isString());
|
||||
// 3. Assert: keys is an Array.
|
||||
MOZ_ASSERT(args[2].isObject());
|
||||
|
||||
JSAutoByteString locale(cx, args[0].toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
JSAutoByteString style(cx, args[1].toString());
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
|
||||
if (!keys)
|
||||
return false;
|
||||
|
||||
// 4. Let result be ArrayCreate(0).
|
||||
RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
UDateFormat* fmt =
|
||||
udat_open(UDAT_DEFAULT, UDAT_DEFAULT, icuLocale(locale.ptr()),
|
||||
nullptr, 0, nullptr, 0, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
|
||||
|
||||
// UDateTimePatternGenerator will be needed for translations of date and
|
||||
// time fields like "month", "week", "day" etc.
|
||||
UDateTimePatternGenerator* dtpg = udatpg_open(icuLocale(locale.ptr()), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
|
||||
|
||||
RootedValue keyValue(cx);
|
||||
RootedString keyValStr(cx);
|
||||
RootedValue wordVal(cx);
|
||||
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
|
||||
if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
|
||||
return false;
|
||||
|
||||
// 5. For each element of keys,
|
||||
for (uint32_t i = 0; i < keys->length(); i++) {
|
||||
/**
|
||||
* We iterate over keys array looking for paths that we have code
|
||||
* branches for.
|
||||
*
|
||||
* For any unknown path branch, the wordVal will keep NullValue and
|
||||
* we'll throw at the end.
|
||||
*/
|
||||
|
||||
if (!GetElement(cx, keys, keys, i, &keyValue))
|
||||
return false;
|
||||
|
||||
JSAutoByteString pattern;
|
||||
keyValStr = keyValue.toString();
|
||||
if (!pattern.encodeUtf8(cx, keyValStr))
|
||||
return false;
|
||||
|
||||
wordVal.setNull();
|
||||
|
||||
// 5.a. Perform an implementation dependent algorithm to map a key to a
|
||||
// corresponding display name.
|
||||
const char* pat = pattern.ptr();
|
||||
|
||||
if (!MatchPart(&pat, "dates")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MatchPart(&pat, "fields")) {
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
UDateTimePatternField fieldType;
|
||||
|
||||
if (MatchPart(&pat, "year")) {
|
||||
fieldType = UDATPG_YEAR_FIELD;
|
||||
} else if (MatchPart(&pat, "month")) {
|
||||
fieldType = UDATPG_MONTH_FIELD;
|
||||
} else if (MatchPart(&pat, "week")) {
|
||||
fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
|
||||
} else if (MatchPart(&pat, "day")) {
|
||||
fieldType = UDATPG_DAY_FIELD;
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
// This part must be the final part with no trailing data.
|
||||
if (*pat != '\0') {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t resultSize;
|
||||
|
||||
const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
|
||||
if (U_FAILURE(status)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
JSString* word = NewStringCopyN<CanGC>(cx, UCharToChar16(value), resultSize);
|
||||
if (!word)
|
||||
return false;
|
||||
|
||||
wordVal.setString(word);
|
||||
} else if (MatchPart(&pat, "gregorian")) {
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
UDateFormatSymbolType symbolType;
|
||||
int32_t index;
|
||||
|
||||
if (MatchPart(&pat, "months")) {
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (equal(style, "narrow")) {
|
||||
symbolType = UDAT_STANDALONE_NARROW_MONTHS;
|
||||
} else if (equal(style, "short")) {
|
||||
symbolType = UDAT_STANDALONE_SHORT_MONTHS;
|
||||
} else {
|
||||
MOZ_ASSERT(equal(style, "long"));
|
||||
symbolType = UDAT_STANDALONE_MONTHS;
|
||||
}
|
||||
|
||||
if (MatchPart(&pat, "january")) {
|
||||
index = UCAL_JANUARY;
|
||||
} else if (MatchPart(&pat, "february")) {
|
||||
index = UCAL_FEBRUARY;
|
||||
} else if (MatchPart(&pat, "march")) {
|
||||
index = UCAL_MARCH;
|
||||
} else if (MatchPart(&pat, "april")) {
|
||||
index = UCAL_APRIL;
|
||||
} else if (MatchPart(&pat, "may")) {
|
||||
index = UCAL_MAY;
|
||||
} else if (MatchPart(&pat, "june")) {
|
||||
index = UCAL_JUNE;
|
||||
} else if (MatchPart(&pat, "july")) {
|
||||
index = UCAL_JULY;
|
||||
} else if (MatchPart(&pat, "august")) {
|
||||
index = UCAL_AUGUST;
|
||||
} else if (MatchPart(&pat, "september")) {
|
||||
index = UCAL_SEPTEMBER;
|
||||
} else if (MatchPart(&pat, "october")) {
|
||||
index = UCAL_OCTOBER;
|
||||
} else if (MatchPart(&pat, "november")) {
|
||||
index = UCAL_NOVEMBER;
|
||||
} else if (MatchPart(&pat, "december")) {
|
||||
index = UCAL_DECEMBER;
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
} else if (MatchPart(&pat, "weekdays")) {
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (equal(style, "narrow")) {
|
||||
symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
|
||||
} else if (equal(style, "short")) {
|
||||
symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
|
||||
} else {
|
||||
MOZ_ASSERT(equal(style, "long"));
|
||||
symbolType = UDAT_STANDALONE_WEEKDAYS;
|
||||
}
|
||||
|
||||
if (MatchPart(&pat, "monday")) {
|
||||
index = UCAL_MONDAY;
|
||||
} else if (MatchPart(&pat, "tuesday")) {
|
||||
index = UCAL_TUESDAY;
|
||||
} else if (MatchPart(&pat, "wednesday")) {
|
||||
index = UCAL_WEDNESDAY;
|
||||
} else if (MatchPart(&pat, "thursday")) {
|
||||
index = UCAL_THURSDAY;
|
||||
} else if (MatchPart(&pat, "friday")) {
|
||||
index = UCAL_FRIDAY;
|
||||
} else if (MatchPart(&pat, "saturday")) {
|
||||
index = UCAL_SATURDAY;
|
||||
} else if (MatchPart(&pat, "sunday")) {
|
||||
index = UCAL_SUNDAY;
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
} else if (MatchPart(&pat, "dayperiods")) {
|
||||
if (!MatchPart(&pat, "/")) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
symbolType = UDAT_AM_PMS;
|
||||
|
||||
if (MatchPart(&pat, "am")) {
|
||||
index = UCAL_AM;
|
||||
} else if (MatchPart(&pat, "pm")) {
|
||||
index = UCAL_PM;
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
// This part must be the final part with no trailing data.
|
||||
if (*pat != '\0') {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t resultSize =
|
||||
udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
|
||||
INITIAL_CHAR_BUFFER_SIZE, &status);
|
||||
if (status == U_BUFFER_OVERFLOW_ERROR) {
|
||||
if (!chars.resize(resultSize))
|
||||
return false;
|
||||
status = U_ZERO_ERROR;
|
||||
udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
|
||||
resultSize, &status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
JSString* word = NewStringCopyN<CanGC>(cx, chars.begin(), resultSize);
|
||||
if (!word)
|
||||
return false;
|
||||
|
||||
wordVal.setString(word);
|
||||
} else {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(wordVal.isString());
|
||||
|
||||
// 5.b. Append the result string to result.
|
||||
if (!DefineElement(cx, result, i, wordVal))
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. Return result.
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/******************** Intl ********************/
|
||||
|
||||
const Class js::IntlClass = {
|
||||
|
|
|
@ -389,6 +389,48 @@ intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
|
|||
extern MOZ_MUST_USE bool
|
||||
intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns an Array with CLDR-based fields display names.
|
||||
* The function takes three arguments:
|
||||
*
|
||||
* locale
|
||||
* BCP47 compliant locale string
|
||||
* style
|
||||
* A string with values: long or short or narrow
|
||||
* keys
|
||||
* An array or path-like strings that identify keys to be returned
|
||||
* At the moment the following types of keys are supported:
|
||||
*
|
||||
* 'dates/fields/{year|month|week|day}'
|
||||
* 'dates/gregorian/months/{january|...|december}'
|
||||
* 'dates/gregorian/weekdays/{sunday|...|saturday}'
|
||||
* 'dates/gregorian/dayperiods/{am|pm}'
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* let info = intl_ComputeDisplayNames(
|
||||
* 'en-US',
|
||||
* 'long',
|
||||
* [
|
||||
* 'dates/fields/year',
|
||||
* 'dates/gregorian/months/january',
|
||||
* 'dates/gregorian/weekdays/monday',
|
||||
* 'dates/gregorian/dayperiods/am',
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* Returned value:
|
||||
*
|
||||
* [
|
||||
* 'year',
|
||||
* 'January',
|
||||
* 'Monday',
|
||||
* 'AM'
|
||||
* ]
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
#if ENABLE_INTL_API
|
||||
/**
|
||||
* Cast char16_t* strings to UChar* strings used by ICU.
|
||||
|
@ -404,6 +446,18 @@ Char16ToUChar(char16_t* chars)
|
|||
{
|
||||
return reinterpret_cast<UChar*>(chars);
|
||||
}
|
||||
|
||||
inline char16_t*
|
||||
UCharToChar16(UChar* chars)
|
||||
{
|
||||
return reinterpret_cast<char16_t*>(chars);
|
||||
}
|
||||
|
||||
inline const char16_t*
|
||||
UCharToChar16(const UChar* chars)
|
||||
{
|
||||
return reinterpret_cast<const char16_t*>(chars);
|
||||
}
|
||||
#endif // ENABLE_INTL_API
|
||||
|
||||
} // namespace js
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
|
||||
JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false,
|
||||
JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
|
||||
JSMSG_DATE_NOT_FINITE: false,
|
||||
JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false,
|
||||
JSMSG_INVALID_KEY: false,
|
||||
intl_Collator_availableLocales: false,
|
||||
intl_availableCollations: false,
|
||||
intl_CompareStrings: false,
|
||||
|
@ -3004,3 +3005,126 @@ function Intl_getCalendarInfo(locales) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a custom method designed after Intl API, but currently
|
||||
* not part of the spec or spec proposal.
|
||||
* We want to use it internally to retrieve translated values from CLDR in
|
||||
* order to ensure they're aligned with what Intl API returns.
|
||||
*
|
||||
* This API may one day be a foundation for an ECMA402 API spec proposal.
|
||||
*
|
||||
* The function takes two arguments - locales which is a list of locale strings
|
||||
* and options which is an object with two optional properties:
|
||||
*
|
||||
* keys:
|
||||
* an Array of string values that are paths to individual terms
|
||||
*
|
||||
* style:
|
||||
* a String with a value "long", "short" or "narrow"
|
||||
*
|
||||
* It returns an object with properties:
|
||||
*
|
||||
* locale:
|
||||
* a negotiated locale string
|
||||
*
|
||||
* style:
|
||||
* negotiated style
|
||||
*
|
||||
* values:
|
||||
* A key-value pair list of requested keys and corresponding
|
||||
* translated values
|
||||
*
|
||||
*/
|
||||
function Intl_getDisplayNames(locales, options) {
|
||||
// 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
|
||||
const requestedLocales = CanonicalizeLocaleList(locales);
|
||||
|
||||
// 2. If options is undefined, then
|
||||
if (options === undefined)
|
||||
// a. Let options be ObjectCreate(%ObjectPrototype%).
|
||||
options = {};
|
||||
// 3. Else,
|
||||
else
|
||||
// a. Let options be ? ToObject(options).
|
||||
options = ToObject(options);
|
||||
|
||||
const DateTimeFormat = dateTimeFormatInternalProperties;
|
||||
|
||||
// 4. Let localeData be %DateTimeFormat%.[[localeData]].
|
||||
const localeData = DateTimeFormat.localeData;
|
||||
|
||||
// 5. Let opt be a new Record.
|
||||
const localeOpt = new Record();
|
||||
// 6. Set localeOpt.[[localeMatcher]] to "best fit".
|
||||
localeOpt.localeMatcher = "best fit";
|
||||
|
||||
// 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
|
||||
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
|
||||
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
|
||||
requestedLocales,
|
||||
localeOpt,
|
||||
DateTimeFormat.relevantExtensionKeys,
|
||||
localeData);
|
||||
|
||||
// 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
|
||||
const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
|
||||
// 9. Let keys be ? Get(options, "keys").
|
||||
let keys = options.keys;
|
||||
|
||||
// 10. If keys is undefined,
|
||||
if (keys === undefined) {
|
||||
// a. Let keys be ArrayCreate(0).
|
||||
keys = [];
|
||||
} else if (!IsObject(keys)) {
|
||||
// 11. Else,
|
||||
// a. If Type(keys) is not Object, throw a TypeError exception.
|
||||
ThrowTypeError(JSMSG_INVALID_KEYS_TYPE);
|
||||
}
|
||||
|
||||
// 12. Let processedKeys be ArrayCreate(0).
|
||||
// (This really should be a List, but we use an Array here in order that
|
||||
// |intl_ComputeDisplayNames| may infallibly access the list's length via
|
||||
// |ArrayObject::length|.)
|
||||
let processedKeys = [];
|
||||
// 13. Let len be ? ToLength(? Get(keys, "length")).
|
||||
let len = ToLength(keys.length);
|
||||
// 14. Let i be 0.
|
||||
// 15. Repeat, while i < len
|
||||
for (let i = 0; i < len; i++) {
|
||||
// a. Let processedKey be ? ToString(? Get(keys, i)).
|
||||
// b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey).
|
||||
callFunction(std_Array_push, processedKeys, ToString(keys[i]));
|
||||
}
|
||||
|
||||
// 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys).
|
||||
const names = intl_ComputeDisplayNames(r.locale, style, processedKeys);
|
||||
|
||||
// 17. Let values be ObjectCreate(%ObjectPrototype%).
|
||||
const values = {};
|
||||
|
||||
// 18. Set i to 0.
|
||||
// 19. Repeat, while i < len
|
||||
for (let i = 0; i < len; i++) {
|
||||
// a. Let key be ? Get(processedKeys, i).
|
||||
const key = processedKeys[i];
|
||||
// b. Let name be ? Get(names, i).
|
||||
const name = names[i];
|
||||
// c. Assert: Type(name) is string.
|
||||
assert(typeof name === "string", "unexpected non-string value");
|
||||
// d. Assert: the length of name is greater than zero.
|
||||
assert(name.length > 0, "empty string value");
|
||||
// e. Perform ? DefinePropertyOrThrow(values, key, name).
|
||||
_DefineDataProperty(values, key, name);
|
||||
}
|
||||
|
||||
// 20. Let options be ObjectCreate(%ObjectPrototype%).
|
||||
// 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]).
|
||||
// 22. Perform ! DefinePropertyOrThrow(result, "style", style).
|
||||
// 23. Perform ! DefinePropertyOrThrow(result, "values", values).
|
||||
const result = { locale: r.locale, style, values };
|
||||
|
||||
// 24. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -477,6 +477,8 @@ MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1}
|
|||
MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
|
||||
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
|
||||
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
|
||||
MSG_DEF(JSMSG_INVALID_KEY, 1, JSEXN_RANGEERR, "invalid key: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG, 1, JSEXN_RANGEERR, "invalid language tag: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
|
||||
MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
|
||||
|
|
|
@ -907,6 +907,7 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
|
|||
|
||||
static const JSFunctionSpec funcs[] = {
|
||||
JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
|
||||
JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Tests the getCalendarInfo function with a diverse set of arguments.
|
||||
|
||||
/*
|
||||
* Test if getDisplayNames return value matches expected values.
|
||||
*/
|
||||
function checkDisplayNames(names, expected)
|
||||
{
|
||||
assertEq(Object.getPrototypeOf(names), Object.prototype);
|
||||
|
||||
assertEq(names.locale, expected.locale);
|
||||
assertEq(names.style, expected.style);
|
||||
|
||||
const nameValues = names.values;
|
||||
const expectedValues = expected.values;
|
||||
|
||||
const nameValuesKeys = Object.getOwnPropertyNames(nameValues).sort();
|
||||
const expectedValuesKeys = Object.getOwnPropertyNames(expectedValues).sort();
|
||||
|
||||
assertEqArray(nameValuesKeys, expectedValuesKeys);
|
||||
|
||||
for (let key of expectedValuesKeys)
|
||||
assertEq(nameValues[key], expectedValues[key]);
|
||||
}
|
||||
|
||||
addIntlExtras(Intl);
|
||||
|
||||
let gDN = Intl.getDisplayNames;
|
||||
|
||||
assertEq(gDN.length, 2);
|
||||
|
||||
checkDisplayNames(gDN('en-US', {
|
||||
}), {
|
||||
locale: 'en-US',
|
||||
style: 'long',
|
||||
values: {}
|
||||
});
|
||||
|
||||
checkDisplayNames(gDN('en-US', {
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/wednesday'
|
||||
],
|
||||
style: 'narrow'
|
||||
}), {
|
||||
locale: 'en-US',
|
||||
style: 'narrow',
|
||||
values: {
|
||||
'dates/gregorian/weekdays/wednesday': 'W'
|
||||
}
|
||||
});
|
||||
|
||||
checkDisplayNames(gDN('en-US', {
|
||||
keys: [
|
||||
'dates/fields/year',
|
||||
'dates/fields/month',
|
||||
'dates/fields/week',
|
||||
'dates/fields/day',
|
||||
'dates/gregorian/months/january',
|
||||
'dates/gregorian/months/february',
|
||||
'dates/gregorian/months/march',
|
||||
'dates/gregorian/weekdays/tuesday'
|
||||
]
|
||||
}), {
|
||||
locale: 'en-US',
|
||||
style: 'long',
|
||||
values: {
|
||||
'dates/fields/year': 'year',
|
||||
'dates/fields/month': 'month',
|
||||
'dates/fields/week': 'week',
|
||||
'dates/fields/day': 'day',
|
||||
'dates/gregorian/months/january': 'January',
|
||||
'dates/gregorian/months/february': 'February',
|
||||
'dates/gregorian/months/march': 'March',
|
||||
'dates/gregorian/weekdays/tuesday': 'Tuesday',
|
||||
}
|
||||
});
|
||||
|
||||
checkDisplayNames(gDN('fr', {
|
||||
keys: [
|
||||
'dates/fields/year',
|
||||
'dates/fields/day',
|
||||
'dates/gregorian/months/october',
|
||||
'dates/gregorian/weekdays/saturday',
|
||||
'dates/gregorian/dayperiods/pm'
|
||||
]
|
||||
}), {
|
||||
locale: 'fr',
|
||||
style: 'long',
|
||||
values: {
|
||||
'dates/fields/year': 'année',
|
||||
'dates/fields/day': 'jour',
|
||||
'dates/gregorian/months/october': 'octobre',
|
||||
'dates/gregorian/weekdays/saturday': 'samedi',
|
||||
'dates/gregorian/dayperiods/pm': 'PM'
|
||||
}
|
||||
});
|
||||
|
||||
checkDisplayNames(gDN('it', {
|
||||
style: 'short',
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/thursday',
|
||||
'dates/gregorian/months/august',
|
||||
'dates/gregorian/dayperiods/am',
|
||||
'dates/fields/month',
|
||||
]
|
||||
}), {
|
||||
locale: 'it',
|
||||
style: 'short',
|
||||
values: {
|
||||
'dates/gregorian/weekdays/thursday': 'gio',
|
||||
'dates/gregorian/months/august': 'ago',
|
||||
'dates/gregorian/dayperiods/am': 'AM',
|
||||
'dates/fields/month': 'mese'
|
||||
}
|
||||
});
|
||||
|
||||
checkDisplayNames(gDN('ar', {
|
||||
style: 'long',
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/thursday',
|
||||
'dates/gregorian/months/august',
|
||||
'dates/gregorian/dayperiods/am',
|
||||
'dates/fields/month',
|
||||
]
|
||||
}), {
|
||||
locale: 'ar',
|
||||
style: 'long',
|
||||
values: {
|
||||
'dates/gregorian/weekdays/thursday': 'الخميس',
|
||||
'dates/gregorian/months/august': 'أغسطس',
|
||||
'dates/gregorian/dayperiods/am': 'ص',
|
||||
'dates/fields/month': 'الشهر'
|
||||
}
|
||||
});
|
||||
|
||||
/* Invalid input */
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
gDN('en-US', {
|
||||
style: '',
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/thursday',
|
||||
]
|
||||
});
|
||||
}, RangeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
gDN('en-US', {
|
||||
style: 'bogus',
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/thursday',
|
||||
]
|
||||
});
|
||||
}, RangeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
gDN('foo-X', {
|
||||
keys: [
|
||||
'dates/gregorian/weekdays/thursday',
|
||||
]
|
||||
});
|
||||
}, RangeError);
|
||||
|
||||
const typeErrorKeys = [
|
||||
null,
|
||||
'string',
|
||||
Symbol.iterator,
|
||||
15,
|
||||
1,
|
||||
3.7,
|
||||
NaN,
|
||||
Infinity
|
||||
];
|
||||
|
||||
for (let keys of typeErrorKeys) {
|
||||
assertThrowsInstanceOf(() => {
|
||||
gDN('en-US', {
|
||||
keys
|
||||
});
|
||||
}, TypeError);
|
||||
}
|
||||
|
||||
const rangeErrorKeys = [
|
||||
[''],
|
||||
['foo'],
|
||||
['dates/foo'],
|
||||
['/dates/foo'],
|
||||
['dates/foo/foo'],
|
||||
['dates/fields'],
|
||||
['dates/fields/'],
|
||||
['dates/fields/foo'],
|
||||
['dates/fields/foo/month'],
|
||||
['/dates/foo/faa/bar/baz'],
|
||||
['dates///bar/baz'],
|
||||
['dates/gregorian'],
|
||||
['dates/gregorian/'],
|
||||
['dates/gregorian/foo'],
|
||||
['dates/gregorian/months'],
|
||||
['dates/gregorian/months/foo'],
|
||||
['dates/gregorian/weekdays'],
|
||||
['dates/gregorian/weekdays/foo'],
|
||||
['dates/gregorian/dayperiods'],
|
||||
['dates/gregorian/dayperiods/foo'],
|
||||
['dates/gregorian/months/الشهر'],
|
||||
[3],
|
||||
[null],
|
||||
['d', 'a', 't', 'e', 's'],
|
||||
['datesEXTRA'],
|
||||
['dates/fieldsEXTRA'],
|
||||
['dates/gregorianEXTRA'],
|
||||
['dates/gregorian/monthsEXTRA'],
|
||||
['dates/gregorian/weekdaysEXTRA'],
|
||||
['dates/fields/dayperiods/amEXTRA'],
|
||||
['dates/gregori\u1161n/months/january'],
|
||||
["dates/fields/year/"],
|
||||
["dates/fields/month/"],
|
||||
["dates/fields/week/"],
|
||||
["dates/fields/day/"],
|
||||
["dates/gregorian/months/january/"],
|
||||
["dates/gregorian/weekdays/saturday/"],
|
||||
["dates/gregorian/dayperiods/am/"],
|
||||
["dates/fields/months/january/"],
|
||||
];
|
||||
|
||||
for (let keys of rangeErrorKeys) {
|
||||
assertThrowsInstanceOf(() => {
|
||||
gDN('en-US', {
|
||||
keys
|
||||
});
|
||||
}, RangeError);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === 'function')
|
||||
reportCompare(0, 0);
|
|
@ -2477,6 +2477,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
|
||||
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
|
||||
JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
|
||||
JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
|
||||
JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
|
||||
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
|
||||
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
|
||||
|
|
|
@ -44,6 +44,32 @@ MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
|
||||
{
|
||||
if (!val.isObject()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
|
||||
if (!realIntlObj) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
JSAutoCompartment ac(cx, realIntlObj);
|
||||
|
||||
static const JSFunctionSpec funcs[] = {
|
||||
JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
|
||||
NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
|
||||
|
||||
|
|
|
@ -9,4 +9,5 @@
|
|||
interface mozIMozIntl : nsISupports
|
||||
{
|
||||
[implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
|
||||
[implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ function run_test() {
|
|||
|
||||
test_this_global(mozIntl);
|
||||
test_cross_global(mozIntl);
|
||||
test_methods_presence(mozIntl);
|
||||
|
||||
ok(true);
|
||||
}
|
||||
|
@ -30,3 +31,16 @@ function test_cross_global(mozIntl) {
|
|||
equal(waivedX.getCalendarInfo() instanceof Object, false);
|
||||
equal(waivedX.getCalendarInfo() instanceof global.Object, true);
|
||||
}
|
||||
|
||||
function test_methods_presence(mozIntl) {
|
||||
equal(mozIntl.addGetCalendarInfo instanceof Function, true);
|
||||
equal(mozIntl.addGetDisplayNames instanceof Function, true);
|
||||
|
||||
let x = {};
|
||||
|
||||
mozIntl.addGetCalendarInfo(x);
|
||||
equal(x.getCalendarInfo instanceof Function, true);
|
||||
|
||||
mozIntl.addGetDisplayNames(x);
|
||||
equal(x.getDisplayNames instanceof Function, true);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче