Skip to content
39 changes: 30 additions & 9 deletions lib/Runtime/Base/WindowsGlobalizationAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,20 +829,41 @@ if (this->object) \
return ResolveLocaleLookup(scriptContext, locale, resolved);
}

int IcuIntlAdapter::GetUserDefaultLocaleName(_Out_ LPWSTR lpLocaleName, _In_ int cchLocaleName)
size_t IcuIntlAdapter::GetUserDefaultLanguageTag(_Out_ char16* langtag, _In_ size_t cchLangtag)
{
// XPLAT-TODO (doilij): implement GetUserDefaultLocaleName
const auto locale = _u("en-US");
const size_t len = 5;
if (lpLocaleName)
UErrorCode error = UErrorCode::U_ZERO_ERROR;
char bcp47[ULOC_FULLNAME_CAPACITY] = { 0 };
char defaultLocaleId[ULOC_FULLNAME_CAPACITY] = { 0 };

int32_t written = uloc_getName(nullptr, defaultLocaleId, ULOC_FULLNAME_CAPACITY, &error);
if (U_SUCCESS(error) && written > 0 && written < ULOC_FULLNAME_CAPACITY)
{
wcsncpy_s(lpLocaleName, LOCALE_NAME_MAX_LENGTH, locale, len);
defaultLocaleId[written] = 0;
error = UErrorCode::U_ZERO_ERROR;
}
else
{
AssertMsg(false, "uloc_getName: unexpected error getting default localeId");
return 0;
}

// REVIEW (doilij): assuming the return value is the length of the output string in lpLocaleName
return len;
}
written = uloc_toLanguageTag(defaultLocaleId, bcp47, ULOC_FULLNAME_CAPACITY, true, &error);
if (U_FAILURE(error) || written <= 0)
{
AssertMsg(false, "uloc_toLanguageTag: unexpected error getting default langtag");
return 0;
}

if (written < cchLangtag)
{
return utf8::DecodeUnitsIntoAndNullTerminateNoAdvance(langtag, reinterpret_cast<const utf8char_t *>(bcp47), reinterpret_cast<utf8char_t *>(bcp47 + written));
}
else
{
AssertMsg(false, "User default language tag is larger than the provided buffer");
return 0;
}
}
#endif // ENABLE_INTL_OBJECT
#endif // INTL_ICU
} // namespace Js
Expand Down
2 changes: 1 addition & 1 deletion lib/Runtime/Base/WindowsGlobalizationAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ namespace Js
static bool ResolveLocaleLookup(_In_ ScriptContext *scriptContext, _In_z_ const char16 *locale, _Out_ char16 *resolved);
//static bool ResolveLocaleBestFit(_In_ ScriptContext *scriptContext, _In_ JavascriptString *locale, _Out_ char16 *resolved);
static bool ResolveLocaleBestFit(_In_ ScriptContext *scriptContext, _In_z_ const char16 *locale, _Out_ char16 *resolved);
static int GetUserDefaultLocaleName(_Out_ LPWSTR lpLocaleName, _In_ int cchLocaleName);
static size_t GetUserDefaultLanguageTag(_Out_ char16* langtag, _In_ size_t cchLangtag);
};
#endif // ENABLE_INTL_OBJECT
#endif // INTL_ICU
Expand Down
183 changes: 146 additions & 37 deletions lib/Runtime/Library/InJavascript/Intl.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@
// Core intl lib
(function (EngineInterface, InitType) {
var platform = EngineInterface.Intl;

// allow unit tests to disable caching behavior for testing convenience but have this always `true` in real scenarios
platform.useCaches = true;

if (platform.localeLookupCache === undefined) {
platform.localeLookupCache = new platform.Map();
}
if (platform.localeBestFitCache === undefined) {
platform.localeBestFitCache = new platform.Map();
}

// determine what backing library we are using
// making these vars in JS allows us to more change how we
// determine the backing library
let isPlatformUsingICU = !platform.winglob;
let isPlatformUsingWinGlob = platform.winglob;

// constants
const NOT_FOUND = "NOT_FOUND";

Expand Down Expand Up @@ -69,14 +79,23 @@
var isFinite = platform.builtInGlobalObjectEntryIsFinite;
var isNaN = platform.builtInGlobalObjectEntryIsNaN;

let GetDefaultLocale = function () {
let defaultLocale = platform.getDefaultLocale();
if (defaultLocale === null) {
// TODO (doilij): remove this fallback when implemented under ICU
defaultLocale = "en-US";
let __defaultLocale = undefined;
const GetDefaultLocale = function () {
if (__defaultLocale && platform.useCaches) {
return __defaultLocale;
}
return defaultLocale;
}

const locale = platform.getDefaultLocale();
if (!locale) {
// if the system locale is undefined/null/empty string, we have to
// do something or else we will crash
__defaultLocale = "en";
} else {
__defaultLocale = locale;
}

return __defaultLocale;
};

let CacheNumberFormat = function (numberFormat) {
let retVal = platform.cacheNumberFormat(numberFormat);
Expand All @@ -85,7 +104,7 @@
numberFormat.__numberingSystem = "";
}
// no return value
}
};

let CreateDateTimeFormat = function (dateTimeFormat, condition) {
let retVal = platform.createDateTimeFormat(dateTimeFormat, condition);
Expand All @@ -98,7 +117,7 @@
]
}
// no return value
}
};

let IsWellFormedLanguageTag = function (langTag) {
let retVal = platform.isWellFormedLanguageTag(langTag);
Expand All @@ -111,7 +130,7 @@
} else {
return retVal;
}
}
};

var forEachIfPresent = function (obj, length, func) {
let current = 0;
Expand Down Expand Up @@ -162,7 +181,7 @@
}

var resolveLocaleLookup = function (localeWithoutSubtags) {
var resolvedLocale = platform.localeLookupCache.get(localeWithoutSubtags);
let resolvedLocale = platform.localeLookupCache.get(localeWithoutSubtags);
if (resolvedLocale === undefined) {
resolvedLocale = platform.resolveLocaleLookup(localeWithoutSubtags);
if (resolvedLocale === null) {
Expand All @@ -185,19 +204,21 @@
}

var getExtensionSubtags = function (locale) {
if (!LANG_TAG_RE) {
if (!LANG_TAG_EXT_RE) {
InitializeLangTagREs();
}

let matches = platform.builtInRegexMatch(locale, LANG_TAG_RE); // String.prototype.match i.e. string.match(re)
let extensionsString = matches[4];
const match = platform.builtInRegexMatch(locale, LANG_TAG_EXT_RE);
if (!match) {
return undefined;
}

// REVIEW (doilij): leading - might mean we need to filter: // ss.match(rr)[4].split('-').filter((x)=>!!x)
// In that case:
// TODO StringInstanceSplit
// TODO ArrayInstanceFilter
// let extSubtags = ArrayInstanceFilter(extensionsString.split('-'), (x)=>!!x);
let extSubtags = extensionsString.split('-').filter((x) => !!x);
const extSubtags = match[0].split('-').filter((x) => !!x);
// REVIEW (doilij): performance (testing for str[0]==='-' and using the string after that or updating the regex might be faster)

return extSubtags;
Expand All @@ -211,7 +232,9 @@
}

if (subTags) {
callInstanceFunc(ArrayInstanceForEach, subTags, (function (subTag) { locale = callInstanceFunc(StringInstanceReplace, locale, "-" + subTag, ""); }));
callInstanceFunc(ArrayInstanceForEach, subTags, function (subTag) {
locale = callInstanceFunc(StringInstanceReplace, locale, "-" + subTag, "");
});
}

// Instead of using replace, we will match two groups, one capturing, one not. The non capturing group just strips away -u if present.
Expand All @@ -232,10 +255,28 @@
subTags = filtered;
}

// As long as we are using the JS version of getExtensions on ICU, "u" will be considered an extension
// of a locale like "de-u-co-phonebk"
// Thus, we can't add the -u- ourselves here
const withoutSubTags = resolved;
if (resolved) {
if (subTags && getArrayLength(subTags) > 0) {
if (isPlatformUsingICU) {
resolved += "-";
} else {
resolved += "-u-";
}
}

resolved += callInstanceFunc(ArrayInstanceJoin, subTags, "-");
} else {
resolved = undefined;
}

return setPrototype({
locale: resolved ? (resolved + ((subTags && getArrayLength(subTags) > 0) ? "-u-" : "") + callInstanceFunc(ArrayInstanceJoin, subTags, "-")) : undefined,
locale: resolved,
subTags: subTags,
localeWithoutSubtags: resolved
localeWithoutSubtags: withoutSubTags
}, null);
}

Expand All @@ -255,8 +296,33 @@
return resolveLocaleHelper(defaultLocale, fitter, undefined, defaultLocale);
}

// get just the language-script-region from the default locale
let __strippedDefaultLocale = undefined;
var strippedDefaultLocale = function () {
return platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*).*/)[1];
if (__strippedDefaultLocale) {
return __strippedDefaultLocale;
}

if (isPlatformUsingICU) {
if (!LANG_TAG_BASE_RE) {
InitializeLangTagREs();
}

const def = GetDefaultLocale();
const match = platform.builtInRegexMatch(def, LANG_TAG_BASE_RE);
if (match) {
// strip extensions by matching only the base
__strippedDefaultLocale = match[0];
} else {
__strippedDefaultLocale = def;
}
} else {
// the only thing to strip off of a WinGlob locale is the collation,
// which comes after the underscore
__strippedDefaultLocale = platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*).*/)[1];
}

return __strippedDefaultLocale;
};

var Internal = (function () {
Expand Down Expand Up @@ -590,38 +656,82 @@
// "x-IV": ["mathan"]
}, null);

var reverseLocaleAcceptingCollationValues = (function () {
var toReturn = setPrototype({}, null);
// reverses the keys and values in each locale's sub-object in localesAcceptingCollationValues
// localesAcceptingCollationValues[locale][key] = value -> reverseLocalesAcceptingCollationValues[locale][value] = key
Copy link
Contributor

@dilijev dilijev Sep 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the explainer comment!

var reverseLocalesAcceptingCollationValues = (function () {
const toReturn = setPrototype({}, null);
callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(localesAcceptingCollationValues), function (locale) {
var values = localesAcceptingCollationValues[locale];
var newValues = setPrototype({}, null);
const collationValuesForLocale = localesAcceptingCollationValues[locale];
const reversedCollationValues = setPrototype({}, null);

callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(values), function (bcp47Tag) {
var windowsTag = values[bcp47Tag];
callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(collationValuesForLocale), function (collation) {
const windowsTag = collationValuesForLocale[collation];
if (windowsTag !== "") {
newValues[windowsTag] = bcp47Tag;
reversedCollationValues[windowsTag] = collation;
}
});

toReturn[locale] = newValues;
toReturn[locale] = reversedCollationValues;
});
return toReturn;
}());

// mappedDefaultLocale will get the default locale and update any deprecated
// collation/sort order values it may use
let __mappedDefaultLocale = undefined;
var mappedDefaultLocale = function () {
var parts = platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*)_?(.+)?/);
var locale = parts[1];
var collation = parts[2];
var availableBcpTags = reverseLocaleAcceptingCollationValues[locale];
if (__mappedDefaultLocale && platform.useCaches) {
return __mappedDefaultLocale;
}

if (collation === undefined || availableBcpTags === undefined) { return locale; }
let locale = undefined;
let collation = undefined;
if (isPlatformUsingICU) {
// ICU's getDefaultLocale() will return a valid BCP-47/RFC 5646 langtag
locale = GetDefaultLocale();
const match = platform.builtInRegexMatch(locale, /-u(?:-[^\-][^\-]?-[^\-]+)*-co-([^\-]+).*/);
if (match) {
// if the system default locale had a collation, strip it for now
// we will add the collation back later in this function
collation = match[1];
locale = callInstanceFunc(StringInstanceReplace, locale, `-co-${collation}`, "");
}
} else {
// Windows' getDefaultLocale() will return a RFC4646 langtag
const parts = platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*)_?(.+)?/);
locale = parts[1];
collation = parts[2];
}

var bcpTag = availableBcpTags[collation];
if (bcpTag !== undefined) {
return locale + "-u-co-" + bcpTag;
if (collation === undefined) {
__mappedDefaultLocale = locale;
return __mappedDefaultLocale;
}

return locale;
// we stripped the -co-collation or _collation above, so this function adds it back
const createLocaleCollationString = function (finalLocale, finalCollation) {
if (isPlatformUsingICU) {
return `${finalLocale}-co-${finalCollation}`;
} else {
return `${finalLocale}-u-co-${finalCollation}`;
}
};

const collationMapForLocale = reverseLocalesAcceptingCollationValues[locale];
if (collationMapForLocale === undefined) {
// Assume the system wouldn't give us back a bad collation value
__mappedDefaultLocale = createLocaleCollationString(locale, collation);
return __mappedDefaultLocale;
}

const mappedCollation = collationMapForLocale[collation];
if (mappedCollation !== undefined) {
__mappedDefaultLocale = createLocaleCollationString(locale, mappedCollation);
} else {
__mappedDefaultLocale = createLocaleCollationString(locale, collation);
}

return __mappedDefaultLocale;
};

// Intl.Collator, String.prototype.localeCompare
Expand Down Expand Up @@ -787,7 +897,6 @@
// Add the bound compare
hiddenObject.__boundCompare = callInstanceFunc(FunctionInstanceBind, compare, obj);
delete hiddenObject.__boundCompare.name;

return obj;
}
tagPublicFunction("Intl.Collator", Collator);
Expand Down
Loading