Locale vs Preferences #1874
Replies: 3 comments 1 reply
-
Discussion:
|
Beta Was this translation helpful? Give feedback.
-
Resuming this thread in relation to the conversation I've been having with Shane in #1833. I explored multiple approaches to API of the constructor between Locale/Options with an aim to remain open minded and avoid cargo culting. For the following reasoning, there are two entities we'll be discussing - Locale and Preferences. Both are effectively structs of keys and values, with the difference being that Locale has a strong string representation and is limited by UTS 35 standard and Preferences are a strongly typed structure with potentially no string representation and may contain any keys and values we want to provide to any constructor. Visualizing it, Locale is:
or: Preferences are very similar in "valid" case, but can be stronger typed:
Notice, that for hour_cycle in Preferences is always valid making the code inside DTF be able to operate on it without having to validate it each time. In case of operating on If we want to encode something not present in UTS35, in Preferences we can just add it:
but in Locale we either cannot or we have to use private extension:
As you can see, the encoding of date style as private extension is at best awkward. It doesn't enforce key-value pair, the order could be flipped, or scattered, and in the end it's quite hard to assume that it will convey the right information and the right value. Locale is effectively a subset of Preferences. All keys and values that Locale encodes are either encodable in Preferences or discarded. The convention used in ECMA-402 is that we separate those two and constructor API accepts both separately and then merge internally. I initially struggled with the "internal merging" as in:
Shane convinced me that it is ok to just discard any unknown values here, so basically What's left is the question if the API is better if keps separate or merged. The alternative that I'm nurturing is:
There are couple reasons why I'm attracted to this diversion from ECMA-402. Primarily it feels to me (intuitively) that it gives us a foundation that is closer to the ontological value of those two types - they are both preferences, and they can be merged. Once we acknowledge that the proposed API yields itself better to different models of merging - it's easy to have The second value I see in it is that it makes it clear that DTF only cares about some merged value of those two. API that takes locale and prefs makes it look like those are two different things that have a value for DTF remaining separate while in fact the first and only thing DTF constructor will do with it is merge. On the flipside Shane argues two things about the separation:
I'm sympathetic to (1) although I have reservations from shaping API with a goal of "educating" developers. I also can imagine scenarios where it may be acceptable: let prefs = get_user_prefs(); // { hour_cycle: HourCycle::H12 };
let dtf = DTF::new(prefs); // user did not specify language or region, all we know that we'll use user specified prefs I'm much less sympathetic to (2) - I think it is presumptuous to assume where which bag comes from and further it can serve as a self-fulfilling prophecy - what ICU/ECMA-402 provides sets boundaries on what developers will do with it. But realization that Locale and Prefs are mostly the same thing and providing API to operate on it I think opens up ability for developers to create more creative strategies of handling more sophisticated scenarios of information/preference sources including chained merges, multi-stage sources etc. Locale can be first provided by Android into an application, the application can merge it with it's "defaults" and then merge further with cached user preferences and finally merge with the particular callsite preferences in the following, admittingly elaborate, example: fn main() {
let loc = OSEnvironment::get_user_preferred_locale();
let defaults = Preferences {
date_style: DateStyle::Long,
};
let app_loc = App::get_user_selected_locale();
let loc = app_loc.merge(os_loc);
{
// UI screen
let screen1_prefs = Preferences {
time_style: TimeStyle::Medium,
};
let prefs = screen1_prefs.merge(loc);
let dtf = DTF::new(prefs);
widget.label = dtf.format_to_string(datetime);
}
} I think we can achieve a good design around it with the following tenants:
The latter can be achieved by either matching 1-1 ECMA-402 or by offering the explicit merge that matches ECMA-402 strategy for invalid values. @sffc - wdyt? |
Beta Was this translation helpful? Give feedback.
-
As a follow up to Jun 30'th ICU4X meeting, here's a table of all constructors in ECMA-402 and all options they have, and how I think of them in the User vs Developer driven. Here's the summary - https://docs.google.com/spreadsheets/d/17eXmqFicLUfDkxgzSnGvZ6g9fEO4XbQhN6IoRtCQnyI/edit?usp=sharing In particular, I believe that there are no direct overlaps between User Preferences and Developer Settings. I know of scenarios where a developer wants to force a particular preferences (say, numberingSystem to be let hardcodedPrefs = DateTimeFormatPreferences {
numberingSystem: Some(NumberingSystem::Latn),
..Default::default()
};
loc.merge_prefs(hardcodedPrefs, true);
let dtf = DateTimeFormat::try_new(loc, Default::default(); |
Beta Was this translation helpful? Give feedback.
-
In #1833 I started working on generalized
icu_preferences
utility and wanted to discuss how we want to approach this abstraction.The outline is that there are three types of information that are passed to the constructor of a formatter with three sources of authority:
2.a) In form of a Locale
2.b) In form of preferences
An example (shaped after ECMA-402 Intl.DateTimeFormat) may look like this:
The division between locale and preferences may be, depending on how we architect it, seen as arbitrary, ergonomic of functional.
The locale itself is a set of preferences - preferred language, region, script, variants, hour cycle, calendar, numbering system, etc. Preferences could be seen as a shadow of the same information.
In such approach, the duality of those two in the API serve as an ergonomic API for the developer who can freely pass the locale they retrieved from the user or embedded system and use the preferences to adjust any overrides they'd like to make.
If we approach it this way, then there's little difference between:
and:
except that the developer may have to clone the locale to apply the adjustment before passing to the constructor.
If we were to give up on
preferences
we'd need to think about the impact on ECMA-402 bindings which would then basically require us to always clone theLocale
wheneverpreferences
in ECMA-402 constructor are set, modify the clonedLocale
and pass it to the constructor.The value is that now we don't need a resolution between
Locale
andpreferences
and have a single location for all such data.We could even then implement ergonomic API for
ecma-402
preferences to be applied in-bulk onto Locale.But this approach has a scaling tradeoff.
Historically,
ecma-402
preferences and relevant unicode extensions have not been completely matching. The binding would have to guard against that.This is slightly problematic when we consider preferences that have been intentionally not included because it is confusing. An example of such is
cu
in Locale is an extension stating what is the preferred currency. But inNumberFormat
options it is an information about what is the currency of the value.I hope that in ICU4X the information about the currency will be associated with the type, not formatter (
CurrencyFormatter
will receiveCurrency
which will be a number+currency).Similar problem is with
tz
.Conceptually, my larger concern is that we'd link any preferences to unicode extension keys. We'd design the API to never allow for a preference to exist that has no equivalent as unicode extension key.
Are we comfortable with it?
I feel that there are user specified preferences such as preferred patterns for given lengths or skeletons, that will never make it to Unicode Extensions. I don't know if we'd ever want to support it, but I imagine a world in which we'd eventually want to support something like:
If we lock ourselves in unicode extensions, supporting that would require us to add that to UE and I doubt it'll ever happen. This is not an extreme scenario. Operating Systems like Windows and MacOS allow users to override wide range of preferences - date/time patterns for lengths, decimal separators, sign symbols etc.
With the current split between
preferences
andLocale
we can take information fromLocale
, but allow forpreferences
to store more, and potentially any of the ones from their OS.Would we want to give up on this?
Beta Was this translation helpful? Give feedback.
All reactions