The Env API: Do we need env.request?
====================================
Next up, env.request and the supported languages. Similar to
env.register, we run into the ordering problem:
// env.request hasn't been called yet; should env.require throw
// or wait internally?
var ctx = env.require(['res.{locale}.l20n']);
// or, maybe ctx.get should wait for env.request internally?
ctx.get('foo').then(…);
Again, that's a bit too much internal magic for my taste. I see two
potential solutions:
1. pass the user preferred languages as the third argument to the
Env's constructor, or
2. pass the user preferred languages as an argument to env.require.
Let's look at each of those solutions in detail.
#1. An argument to the Env constructor
--------------------------------------
Imagine the following signature of the Env constructor:
var env = new Env('
http://example.com', manifest, userLangs);
Now we have all the necessary information to create a fully-operational
env. userLangs is a list of user's preferred languages which is passed
to the language negotiator function which in turn returns the list of
supported languages. This list is stored in the Env instance.
We can now safely create contexts; the env's supported languages (or
a promise to have them) will be used to determine which resources will
be fetched:
var ctx = env.require(['res.{locale}.l20n']);
Since the supported languages are now part of env's state, we also need
a way to modify this state in case the user changes their preferred
languages.
env.request(newUserLangs);
As I mentioned before, I'd like env.request to be async for the sake of
forward-compatibility and also because it could then accept an array of
user preferred languages or a promise which resolves to such an array
(in case user prefs are stored in the Settings API or IndexDB or such
like).
However, an asynchronous env.request method which changes the env's
internal state is a sure recipe for race conditions:
var env = new Env('
http://example.com', manifest, ['de']);
var ctx = env.require(['res.{locale}.l20n']);
ctx.get('foo').then(…); // supported languages are: de, en-US
env.addEventListener('languagechange', function() {
ctx.get('foo').then(…); // supported languages are: pl, en-US
});
env.request(['pl']);
ctx.get('foo').then(…); // supported languages at the time
// this line is run are still: de, en-US.
Another problem of this approach is that it's only possible to have one
list of supported locales per env. I'm not sure how much of an
edge-case multi-lingual interfaces are, but I wouldn't like to dismiss
them from the get-go either.
#2. An argument to env.require
------------------------------
As a thought excercise, I tried to imagine a stateless API in which
contexts are bound to a single immutable list of supported languages.
Branch:
https://github.com/stasm/l20n.js/tree/immutable
Diff:
https://github.com/stasm/l20n.js/compare/85ca771...9e7d466
I experimented with completely removing env.request as well as the
env's languagechange event. Instead, solutions native to the platform
can be used (e.g. navigator.languages and window.onlanguagechange in
browsers).
var ctx = env.require(['de'], ['res.{locale}.l20n']);
ctx.get('foo').then(…); // ctx's supported languages are: de, en-US
In this approach, contexts are immutable: once they're ready for the
list of supported languages they're passed, they remain ready forever.
If the user changes their language preferences, a new context must be
created:
window.addEventListener('languagechange', function retranslate() {
var ctx = env.require(navigator.languages, ['res.{locale}.l20n']);
myApp.render(ctx);
});
Note that since we're removing env.request, there's no need for
a custom languagechange event and instead we can use the platform's
native one.
env.require could also accept a promise as the first argument, which
would play nicely with async storages like mozSettings and IndexDB:
var userLangsPromise = PromisifiedSettings.get('languages.current');
var ctx = env.require(userLangsPromise, ['res.{locale}.l20n']);
I like this solution for its explicit nature and immutability which
makes it impossible to run into race conditions. It also allows to
create contexts with different lists of supported languages.
In my next email I'll talk about the context API.
-stas
--
@stas