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:

  var env = new Env('http://example.com', manifest);

  // 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 env = new Env('http://example.com', manifest);

  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:

  var env = new Env('http://example.com', manifest);

  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
_______________________________________________
tools-l10n mailing list
[email protected]
https://lists.mozilla.org/listinfo/tools-l10n

Reply via email to