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

Intl.Locale #106

Closed
zbraniecki opened this issue Sep 23, 2016 · 18 comments
Closed

Intl.Locale #106

zbraniecki opened this issue Sep 23, 2016 · 18 comments
Labels
c: locale Component: locale identifiers s: in progress Status: the issue has an active proposal

Comments

@zbraniecki
Copy link
Member

zbraniecki commented Sep 23, 2016

This is the second spin-off from #68.

Proposing a new class Intl.Locale which creates objects with a list of extension keys used by any of the formatters and with toString method which produces a BCP47 locale string with relevant extension keys.

Examples:

let locale = new Intl.Locale('en-US');
locale.toString(); // 'en-US'
locale.calendar; // undefined
let locale = new Intl.Locale('en-US', {
  ca: 'buddhist'
});
locale.toString(); // 'en-US-u-ca-buddhist'
locale.calendar; // 'buddhist'
let locale = new Intl.Locale('en-US-u-ca-buddhist');
locale.toString(); // 'en-US-u-ca-buddhist'
locale.calendar; // 'buddhist'
let locale = new Intl.Locale('en-US-u-ca-buddhist', {
  ca: 'gregory'
});
locale.toString(); // 'en-US-u-ca-gregory'
locale.calendar; // 'gregory'
let locale = new Intl.Locale('en-US-u-ca-buddhist', {
  ca: 'gregory'
});
locale.toString(); // 'en-US-u-ca-gregory'
locale.calendar; // 'gregory'

Proposed list of supported extension keys and their option key names:

  • ca-- calendar
  • nu -- numberingSystem
  • hc -- hourCycle
  • tz -- timeZone
  • cu -- currency
  • co -- collation
  • kn -- numeric
  • kf -- caseFirst

Looking for feedback

@zbraniecki
Copy link
Member Author

@caridy
Copy link
Contributor

caridy commented Oct 10, 2016

Backpointer to a more descriptive issue: #105

@caridy
Copy link
Contributor

caridy commented Oct 10, 2016

I'm not sure of the usefulness of this. Is the goal to provide a factory-kind of structure to just build the locale string with certain options? or just a way to understand the keys in a locale? How will users use this in conjunction with other Intl features and navigator.locales?

@zbraniecki
Copy link
Member Author

The idea would be that navigator.locales returns the list of Intl.Locale objects.

Other Intl APIs can accept a list of Intl.Locale objects much like they can accept a list of strings, because internally we still do toString on them.

Later, we could become smarter and not toString internally when we recognize it's an Intl.Locale object, but that's just an option.

The goal is to make operations on extension keys vs. intl options more interchangeable.
It also helps us close the loop between resolvedOptions, extension keys accepted in locales and options.

We don't have to do this, but I think @littledan's solution is a very elegant way to make any operations between Intl options and extension keys unified.

@caridy
Copy link
Contributor

caridy commented Oct 10, 2016

Still I don't get it! what is the gain here? Definitely not performance IMO. If navigator.locales is just a string value, what's the problem to solve?

@zbraniecki
Copy link
Member Author

zbraniecki commented Oct 10, 2016

The gain here is that you can transition between options and extension keys.

Without it, taking de-hc-h12-ca-buddhist-nu-arab and interpreting it to know that it's a german locale, which h12 clock, buddhist calendar and arab numbersystem is fairly complicated.

Modifying it to just alter, say h12 to h24, is also hard.

And lastly, building the string out of bits that you have (you know that hourCycle is h12, you know that calendar is buddhist, now build the locale string) is hard as well.

With Intl.Locale, one can do things like:

let x = new Intl.DateTimeFormat('de', {
  calendar:  'buddhist',
  hourCycle: 'h12'
});

let loc = new Intl.Locale('de', x.resolvedOptions());

let y = new Intl.DateTimeFormat(loc, {
  hourCycle: 'h24'
});

or

let loc = new Intl.Locale('de-hc-h12', {
  calendar: 'buddhist'
});

let loc2 = new Intl.Locale(loc, {
  calendar: 'gregory'
});

if (loc2.hourCycle === 'h12') {
} else {
}

imagine doing the same without Intl.Locale.

@domenic
Copy link
Member

domenic commented Oct 18, 2016

let locale = new Intl.Locale('en-US');
locale.toString(); // 'en-US'
locale.calendar; // undefined

This example is a bit confusing to me. Why wouldn't it tell you the default calendar system for en-US? I am guessing you are trying to separate out "passed options" from "actual options" (maybe "actual options" = resolvedOptions()??) but if so, why?

@littledan
Copy link
Member

An alternative to creating a class like this is to create a library of string manipulation methods to parse, normalize and construct BCP 47 extended locales. The API presented here accomplishes the same.The difference between the two options is largely ergonomic. Intuitively, to me, a class feels "more JavaScripty", though this is a value judgement.

@zbraniecki
Copy link
Member Author

This example is a bit confusing to me. Why wouldn't it tell you the default calendar system for en-US? I am guessing you are trying to separate out "passed options" from "actual options" (maybe "actual options" = resolvedOptions()??) but if so, why?

I understand the confusion because I fell for it multiple times myself.

Basically, when we deal with locales we may end up with thinking about Intl.Locale class to solve two classes of problems:

  1. localestring2obj and obj2localestring operations
  2. Best "negotiated" value for an option for a given locale

The former is what we're suggesting right now. As @littledan said, we could replace it with Intl.transformLocaleStringToObject and Intl.transformObjectToLocaleString operations and is intended to manipulate between pl-u-ca-gregory and {locale: 'pl', calendar: 'gregory'}.

The latter, is also something that is popping up every now and then in conversations and it's - how to retrieve "negotiated" values for a locale? What is the default calendar for pl? What is the default hour12?

In the latter scenario, you expect to do let loc = Intl.Locale('pl') and be able to retrieve all the default settings for it, including calendar.
In the former, since the string passed to the constructor didn't contain calendar field, the return object doesn't contain it either.

There's also a third level in that we currently only recognize values from the locale string, options or locale defaults, but we're also talking about optionally retrieving host environment preferences where possible before we reach for locale defaults.

I would argue that we should not build APIs that expose user ability to select where they want the data to come from.
Instead we should select the order of sources we look at and build APIs that allow user to learn which option will be negotiated for the given values.

I believe my suggested order would be:

  1. options
  2. Locale string
  3. host environment prerefences
  4. locale defaults
  5. last resort defaults

If we always return 'calendar' field on Locale object, we lose the ability to use this object as intermediary step:

let lang = 'pl';

let loc = new Intl.Locale(lang);
loc.calendar; // 'gregory'

loc.toString(); // 'pl-u-ca-gregory'

loc.toString() !== lang;

One option, would be to do:

let lang = 'pl';

let loc = new Intl.Locale(lang);
loc.calendar; // undefined

loc.resolvedOptions().calendar; // 'gregory'

loc.toString(); // 'pl'

loc.toString() === lang;

The only cost of this is that it would have to resolve all options for all formatters when you ask only for one. We discussed doing this as a set of APIs: Intl.getCalendarInfo, Intl.getLocaleInfo, Intl.getNumberInfo etc.

If we ended up adding Intl.Locale, we could do this as:

let loc = new Intl.Locale('pl');

loc.calendar; // undefined
loc.resolvedDateTimeOptions(); {calendar: 'gregory', ...}
loc.resolvedNumberOptions(); {numberingSystem: 'latn'}

to prevent having to collect all options from all formatters.

Does it make sense @domenic ?

@littledan
Copy link
Member

This makes sense to me. Mark Davis suggested that ECMA 402 should have a library for BCP 47 manipulation, and it seems like this amounts to that plus a bit more. Any more thoughts, @jungshik?

@domenic
Copy link
Member

domenic commented Oct 27, 2016

@zbraniecki: thanks for explaining; it makes sense now. I guess I don't have a preference between the class and the functions except if there are useful methods than of course a class is nice. Maybe your resolvedOptions() is such a method.

I don't understand why it would be bad to resolve all options, and introducing new APIs to avoid that seems unnecessary to me, but I don't know this problem space very well so I'll defer to your judgment there.

@zbraniecki
Copy link
Member Author

I don't understand why it would be bad to resolve all options, and introducing new APIs to avoid that seems unnecessary to me, but I don't know this problem space very well so I'll defer to your judgment there.

The way implementations will have to handle this is to pull those bits from CLDR separately or using some intermediary API like ICU.

It starts fairly small - let's say that the Intl.Locale.prototype.resolvedOptions initially would just return resolved options for Collator, DateTimeFormat and NumberFormat (10-14 elements), but as we add more APIs over time (PluralRules, ListFormat, DurationFormat, RelativeTimeFormat, UnitFormat etc.) we will have to keep adding all of the options to this method slowing it down as collecting them will require more and more time.

I understand that adding methods is not the funniest way to solve the problem, but it's one of them...
Alternatively, we could allow for some specialization:

let loc = new Intl.Locale('pl');
loc.resolvedOptions({type: 'datetime'}); // {calendar: ..., numberingSystem: ..., timeZone: ...}

or a list of options user wants to retrieve:

let loc = new Intl.Locale('pl');
loc.resolvedOptions(['calendar', 'measuringSystem', 'timeZone']); // {calendar: ..., measuringSystem: ..., timeZone: ...}

@littledan
Copy link
Member

littledan commented Oct 28, 2016

You would already be able to get at the resolved options of the locale with respect to e.g. DateTimeFormat by instantiating one. Maybe we should leave the specialized resolvedOptions part for a later stage, and just expose what's resolved/normalized from a BCP 47 perspective.

@zbraniecki
Copy link
Member Author

Intl.Locale achieved Stage 3 as of today!

@sffc sffc added s: in progress Status: the issue has an active proposal c: locale Component: locale identifiers and removed enhancement labels Mar 19, 2019
@zbraniecki
Copy link
Member Author

zbraniecki commented Jul 30, 2019

Intl.Locale has landed in SpiderMonkey for Gecko 70 (behind the flag).

@zbraniecki
Copy link
Member Author

PR for Intl.Locale is #406

Stage 4 reached on February 5th 2020.

@anba
Copy link
Contributor

anba commented Mar 16, 2020

#406 was merged, so this issue can be closed.

@sffc
Copy link
Contributor

sffc commented Mar 16, 2020

Yep, thanks @zbraniecki and @anba!

@sffc sffc closed this as completed Mar 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: locale Component: locale identifiers s: in progress Status: the issue has an active proposal
Projects
None yet
Development

No branches or pull requests

6 participants