Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display names #15

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ if test "$PHP_ECMA_INTL" != "no"; then
src/php/classes/locale_text_info.c \
src/php/classes/locale_week_day.c \
src/php/classes/locale_week_info.c \
src/php/classes/options.c \
src/php/classes/supportedlocalesoptions.c \
src/php/ecma_intl.c \
"

Expand Down Expand Up @@ -130,6 +132,21 @@ if test "$PHP_ECMA_INTL" != "no"; then
tests/criterion/ecma402/error_test.c \
tests/criterion/ecma402/hour_cycle_test.c \
tests/criterion/ecma402/language_tag_test.cpp \
tests/criterion/ecma402/locale_canonicalization_test.c \
tests/criterion/ecma402/locale_getBaseName_test.c \
tests/criterion/ecma402/locale_getCalendar_test.c \
tests/criterion/ecma402/locale_getCaseFirst_test.c \
tests/criterion/ecma402/locale_getCollation_test.c \
tests/criterion/ecma402/locale_getCurrency_test.c \
tests/criterion/ecma402/locale_getHourCycle_test.c \
tests/criterion/ecma402/locale_getLanguage_test.c \
tests/criterion/ecma402/locale_getNumberingSystem_test.c \
tests/criterion/ecma402/locale_getRegion_test.c \
tests/criterion/ecma402/locale_getScript_test.c \
tests/criterion/ecma402/locale_initLocale_test.c \
tests/criterion/ecma402/locale_isNumeric_test.c \
tests/criterion/ecma402/locale_min_max_test.c \
tests/criterion/ecma402/locale_options_test.c \
tests/criterion/ecma402/locale_test.c \
tests/criterion/ecma402/numbering_system_test.c \
tests/criterion/ecma402/time_zone_test.c \
Expand Down
1 change: 1 addition & 0 deletions docs/readthedocs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ Reference
reference/ecma-intl-locale-textinfo
reference/ecma-intl-locale-weekday
reference/ecma-intl-locale-weekinfo
reference/ecma-intl-supportedlocalesoptions
47 changes: 47 additions & 0 deletions docs/readthedocs/reference/ecma-intl-supportedlocalesoptions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.. _ecma.intl.supportedlocalesoptions:

===================================
Ecma\\Intl\\SupportedLocalesOptions
===================================

.. php:namespace:: Ecma\Intl
.. php:class:: SupportedLocalesOptions

Options to use when evaluating supported locales.

ECMA-402 defines a ``supportedLocalesOf()`` static method that is available
on many of the classes provided in the specification. For example, see
`Intl.Collator.supportedLocalesOf <https://tc39.es/ecma402/#sec-intl.collator.supportedlocalesof>`_.
Each of these methods accepts an ``options`` object that specifies the
locale matcher algorithm to use when evaluating supported locales. In this
PHP implementation, :php:class:`Ecma\\Intl\\SupportedLocalesOptions` provides
a type for this options object.

.. php:attr:: localeMatcher: string | null, readonly

The locale-matching algorithm to use when evaluating supported locales.

.. php:method:: __construct([$localeMatcher = null])

:param Stringable | string | null $localeMatcher: The locale-matching
algorithm to use when evaluating supported locales. This may be one
of the values ``"best fit"`` or ``"lookup"``. Other implementations
may define additional values.

.. php:method:: jsonSerialize(): object

Returns an object of these options, suitable for serializing to JSON.

Please note that any options with a ``null`` value will not be included
in the object this method returns. This allows the JSON to be passed to
JavaScript contexts, where these properties are considered ``undefined``.

.. php:method:: current(): string | bool

.. php:method:: next(): void

.. php:method:: key(): string

.. php:method:: valid(): bool

.. php:method:: rewind(): void
191 changes: 135 additions & 56 deletions src/ecma402/locale.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ int getMaxOrMin(enum maxOrMin type, const char *localeId, char *value,
ecma402_errorStatus *status, bool isCanonicalized);
int getNumberingSystemsForLocale(char *localeId, const char **values);
int getTimeZonesForLocale(char *localeId, const char **values);
int languageTagForLocaleId(const char *localeId, char *languageTag,
ecma402_errorStatus *status);

} // namespace

Expand Down Expand Up @@ -154,6 +156,60 @@ ecma402_locale *ecma402_applyLocaleOptions(
return ecma402_initLocale(builtLocale.c_str());
}

int ecma402_bestAvailableLocale(char **availableLocales,
int availableLocalesCount, const char *localeId,
char *bestAvailable, bool isCanonicalized) {
std::string candidate;
char *languageTag;

if (localeId == nullptr) {
return -1;
}

if (isCanonicalized) {
candidate = localeId;
} else {
ecma402_errorStatus *status = ecma402_initErrorStatus();
languageTag = (char *)malloc(sizeof(char) * ULOC_FULLNAME_CAPACITY);

languageTagForLocaleId(localeId, languageTag, status);

if (ecma402_hasError(status)) {
free(languageTag);
ecma402_freeErrorStatus(status);
return -1;
}

candidate = languageTag;
free(languageTag);
ecma402_freeErrorStatus(status);
}

while (!candidate.empty()) {
char **item = availableLocales;
for (int i = 0; i < availableLocalesCount; i++) {
if (candidate == *item) {
strcpy(bestAvailable, *item);
return strlen(bestAvailable);
}
++item;
}

size_t hyphenPos = candidate.rfind('-');
if (hyphenPos == std::string::npos) {
return -1;
}

if (hyphenPos >= 2 && candidate[hyphenPos - 2] == '-') {
hyphenPos -= 2;
}

candidate = candidate.substr(0, hyphenPos);
}

return -1;
}

int ecma402_canonicalizeLocaleList(const char **locales, int localesLength,
char **canonicalized,
ecma402_errorStatus *status) {
Expand Down Expand Up @@ -186,11 +242,6 @@ int ecma402_canonicalizeLocaleList(const char **locales, int localesLength,
int ecma402_canonicalizeUnicodeLocaleId(const char *localeId,
char *canonicalized,
ecma402_errorStatus *status) {
icu::Locale canonicalLocale;
UErrorCode icuStatus = U_ZERO_ERROR;
UBool const strict = 1;
char *unicodeLocaleId;

if (localeId == nullptr) {
return -1;
}
Expand All @@ -201,57 +252,7 @@ int ecma402_canonicalizeUnicodeLocaleId(const char *localeId,
return -1;
}

canonicalLocale = icu::Locale::createCanonical(localeId);
if (canonicalLocale == nullptr) {
ecma402_ecmaError(status, CANNOT_CREATE_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
return -1;
}

std::string const locale =
canonicalLocale.toLanguageTag<std::string>(icuStatus);
if (U_FAILURE(icuStatus) != U_ZERO_ERROR) {
ecma402_icuError(status, icuStatus, "Invalid language tag \"%s\"",
localeId);
return -1;
}

// If the input localeId is not "und," but we got "und," then return 0.
if (strcasecmp(locale.c_str(), UNDETERMINED_LANGUAGE) == 0 &&
strcasecmp(localeId, UNDETERMINED_LANGUAGE) != 0) {
ecma402_ecmaError(status, UNDEFINED_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
return -1;
}

// This additional conversion step forces tags like "en-latn-us-co-foo" and
// "de-de_euro" to result in failures, which is the desired result.
unicodeLocaleId = (char *)malloc(sizeof(char) * ULOC_FULLNAME_CAPACITY);
int const length =
uloc_toLanguageTag(locale.c_str(), unicodeLocaleId,
ULOC_FULLNAME_CAPACITY, strict, &icuStatus);

if (U_FAILURE(icuStatus) != U_ZERO_ERROR || strlen(unicodeLocaleId) == 0 ||
unicodeLocaleId == nullptr) {
if (U_FAILURE(icuStatus) != U_ZERO_ERROR) {
ecma402_icuError(status, icuStatus, "Invalid language tag \"%s\"",
localeId);
} else {
ecma402_ecmaError(status, INVALID_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
}

if (unicodeLocaleId != nullptr) {
free(unicodeLocaleId);
}

return -1;
}

memcpy(canonicalized, unicodeLocaleId, length + 1);
free(unicodeLocaleId);

return length;
return languageTagForLocaleId(localeId, canonicalized, status);
}

void ecma402_freeLocale(ecma402_locale *locale) {
Expand Down Expand Up @@ -496,6 +497,24 @@ ecma402_locale *ecma402_initLocale(const char *localeId) {
return locale;
}

int ecma402_intlAvailableLocales(char **locales) {
ecma402_errorStatus *status = ecma402_initErrorStatus();
const int count = uloc_countAvailable();
int i, counted = 0;

for (i = 0; i < count; i++) {
char *locale = (char *)malloc(sizeof(char) * ULOC_FULLNAME_CAPACITY);
languageTagForLocaleId(uloc_getAvailable(i), locale, status);
if (strcmp(locale, "") != 0) {
locales[counted] = strdup(locale);
counted++;
}
free(locale);
}

return counted;
}

bool ecma402_isNumeric(const char *localeId, ecma402_errorStatus *status,
bool isCanonicalized) {
char *numeric;
Expand Down Expand Up @@ -889,4 +908,64 @@ int getTimeZonesForLocale(char *localeId, const char **values) {
return count;
}

int languageTagForLocaleId(const char *localeId, char *languageTag,
ecma402_errorStatus *status) {
icu::Locale canonicalLocale;
UErrorCode icuStatus = U_ZERO_ERROR;
UBool const strict = 1;
char *unicodeLocaleId;

canonicalLocale = icu::Locale::createCanonical(localeId);
if (canonicalLocale == nullptr) {
ecma402_ecmaError(status, CANNOT_CREATE_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
return -1;
}

std::string const locale =
canonicalLocale.toLanguageTag<std::string>(icuStatus);
if (U_FAILURE(icuStatus) != U_ZERO_ERROR) {
ecma402_icuError(status, icuStatus, "Invalid language tag \"%s\"",
localeId);
return -1;
}

// If the input localeId is not "und," but we got "und," then return 0.
if (strcasecmp(locale.c_str(), UNDETERMINED_LANGUAGE) == 0 &&
strcasecmp(localeId, UNDETERMINED_LANGUAGE) != 0) {
ecma402_ecmaError(status, UNDEFINED_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
return -1;
}

// This additional conversion step forces tags like "en-latn-us-co-foo" and
// "de-de_euro" to result in failures, which is the desired result.
unicodeLocaleId = (char *)malloc(sizeof(char) * ULOC_FULLNAME_CAPACITY);
int const length =
uloc_toLanguageTag(locale.c_str(), unicodeLocaleId,
ULOC_FULLNAME_CAPACITY, strict, &icuStatus);

if (U_FAILURE(icuStatus) != U_ZERO_ERROR || strlen(unicodeLocaleId) == 0 ||
unicodeLocaleId == nullptr) {
if (U_FAILURE(icuStatus) != U_ZERO_ERROR) {
ecma402_icuError(status, icuStatus, "Invalid language tag \"%s\"",
localeId);
} else {
ecma402_ecmaError(status, INVALID_LOCALE_ID,
"Invalid language tag \"%s\"", localeId);
}

if (unicodeLocaleId != nullptr) {
free(unicodeLocaleId);
}

return -1;
}

memcpy(languageTag, unicodeLocaleId, length + 1);
free(unicodeLocaleId);

return length;
}

} // namespace
60 changes: 60 additions & 0 deletions src/ecma402/locale.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ ecma402_applyLocaleOptions(ecma402_locale *locale, const char *calendar,
const char *language, const char *numberingSystem,
int numeric, const char *region, const char *script);

/**
* Returns the best available supported locale for the locale identifier given.
*
* The bestAvailable parameter should already be allocated on the stack with
* enough memory to store the buffer. Typically, this should use
* ULOC_FULLNAME_CAPACITY. For example:
*
* malloc(sizeof(char) * ULOC_FULLNAME_CAPACITY)
*
* @link https://tc39.github.io/ecma402/#sec-bestavailablelocale
*
* @param availableLocales A list of available locales, possibly generated from
* ecma402_intlAvailableLocales.
* @param availableLocalesCount The number of items in availableLocales.
* @param localeId The locale identifier to get the best available locale for.
* @param bestAvailable A buffer in which to store the best available locale.
* @param isCanonicalized Whether localeId is already canonicalized. If not,
* this function will call ecma402_canonicalizeUnicodeLocaleId on localeId.
*
* @return The length of the string stored to the bestAvailable buffer, or -1 if
* no best available supported locale is found.
*/
int ecma402_bestAvailableLocale(char **availableLocales,
int availableLocalesCount, const char *localeId,
char *bestAvailable, bool isCanonicalized);

/**
* Canonicalizes a list of locales.
*
Expand Down Expand Up @@ -262,6 +288,24 @@ int ecma402_getCaseFirst(const char *localeId, char *caseFirst,
int ecma402_getCollation(const char *localeId, char *collation,
ecma402_errorStatus *status, bool isCanonicalized);

/**
* Returns the value of the currency (cu) keyword for the given locale ID.
*
* The currency parameter should already be allocated on the stack with
* enough memory to store the buffer. Typically, this should use
* ULOC_KEYWORDS_CAPACITY. For example:
*
* malloc(sizeof(char) * ULOC_KEYWORDS_CAPACITY)
*
* @param localeId The locale identifier to get the currency value of.
* @param currency A buffer in which to store the currency value.
* @param status A status object to pass error messages back to the caller.
* @param isCanonicalized Whether localeId is already canonicalized. If not,
* this function will call ecma402_canonicalizeUnicodeLocaleId on localeId.
*
* @return The length of the string stored to the currency buffer, or -1 if the
* localeId has no currency value.
*/
int ecma402_getCurrency(const char *localeId, char *currency,
ecma402_errorStatus *status, bool isCanonicalized);

Expand Down Expand Up @@ -386,6 +430,22 @@ ecma402_locale *ecma402_initEmptyLocale(void);
*/
ecma402_locale *ecma402_initLocale(const char *localeId);

/**
* Returns a list of locales available to this implementation.
*
* The locales parameter should already be allocated on the stack with
* enough memory to store the buffer. Typically, this should use
* uloc_countAvailable(). For example:
*
* malloc(sizeof(char *) * uloc_countAvailable())
*
* @param locales A pointer in which to store the resulting char array of
* available locales.
*
* @return The number of items stored to the locales array.
*/
int ecma402_intlAvailableLocales(char **locales);

/**
* Returns true if the localeId has the colnumeric (kn) keyword with a value
* that evaluates to true.
Expand Down
Loading
Loading