i18n: Access locale resolution logic in JavaScript - javascript

I'm developing and internationalized Single Page App with 2 sorts of localized text:
'static' text, typically text in my HTML templates.
'dynamic' text, i.e text that lives in the database, typically the description of a product on an e-commerce site.
It's type 2 I'm having trouble with. Say my app officially supports English, French and German, and I get from my database an object such as :
{
description: {
'en': "It's an awesome product.",
'en_UK': "This product is ace.",
'fr': "C'est un excellent produit."
// German's missing
}
}
Now the challenge is to dynamically choose what locale should be chosen for display, given the user's locale and what locales are available in this particular object.
I assume most i18n JavaScript libraries have their own 'locale resolution' logic built-in, but I haven't found one that exposes this logic for the client to use.
Does anyone know a JS library that addresses this, or a good way to solve this issue? (if it's AngularJs-compatible, it's even better).
Thanks in advance!

Disclaimer: I'm a co-author of L20n and one of the developers of
l10n.js used in Firefox OS.
The term that's commonly used to describe this logic is language negotiation.
Most localization libraries should have some sort of language negotiation algorithm included. It can be as basic as trying to match the value of navigator.language with a list of available languages. More sophisticated approaches will look at both the language tag (en in en-US) and the region tag (US in en-US) to try to find a best match.
There's a proposal to expose a language negotiation method on the ECMAScript's Intl object, but for now it's not possible to use its internal logic for this purpose.
Getting the list of languages preferred by the user is not as easy as it should be. There's navigator.language in most browsers (which is the user's preferred language in Firefox and the language of the browser UI in Chrome), navigator.userLanguage in Internet Explorer, and the new navigator.languages which is an ordered list of user's preferred languages.
A server-side alternative is to use the Accept-Language header of the HTTP request which currently is the most reliable way of finding out what the user's preferences are.
Once you have the list of user's preferred languages you can perform the language negotiation.
Here are a few examples of libraries that perform language negotiation:
in-browser-language,
l10n.js used in Firefox OS,
a non-standard extension of the ECMA-402 Intl object which adds Intl.prioritizeLocales.
For your particular use-case, you can choose to do one of the two following things:
perform the language negotiation on the client side using navigator.language || navigator.userLanguage and send a request to the database specifying which language you're interested in, or
send the request with the user's Accept-Language header and perform the language negotiation on the server side, and then query the database for the correct translation.
Both solutions have the benefit of not sending the entire set of translations to the client when only one will be eventually used.
For solution #1, given that you're using Angular, I can suggest using L20n 1.0.x which integrates with Angular via the ng-l20n module. You should be able to use the supportedLocales property to get the negotiated list of languages and use the first element of that list to query the database.
For solution #2, it all depends on your server-side setup, but if you're using node.js, you can try using one of the following modules:
the afore-mentioned in-browser-language,
locale.

Staś Małolepszy's answer is a good reference of the resources available for this problem domain.
However, I found that none of these libraries suited my needs (either because their language negotiation logic was too simplistic, or because it wasn't really exposed).
Therefore, I implemented my own custom solution for language negotiation. You can experiment with it in this Plunkr: http://plnkr.co/edit/Cfv49ZcQWJcqwOXYcjtw?p=preview
The API is a function negotiate(availableLocales, requestedLocales, defaultLocale), a bit similar to the ECMAScript proposal (with the difference that it returns a single locale value, not a list).
// simple cases
negotiate(["fr"], ["de","fr"], 'en'); // => fr
negotiate(["en"], ["de","en-US","en"], 'en'); // => en
// less natural cases, where the solution is more opinionated
negotiate(["fr-FR"], ["fr"], 'en'); // => fr-FR
negotiate(["en","en-UK","fr"], ["fr-FR"], 'en'); // => fr
negotiate(["fr","de"], ["fr-FR","de"], 'en'); // => fr
I made the choice to account for 'locale inheritance' (e.g en-US is derived from en) in a non-trivial, opinionated manner . I do not claim this is in compliance with whatever standards exist on this topic.
It surprised me that I couldn't find any library that provides this sort of functionality. My best explanation is that i18n of dynamic content isn't needed that often.
Finally, as to where I get the list of requested languages, as I was in an Single Page App configuration I chose to send an AJAX request to my server, with the content of the Accept-Language header in the response.

Related

Using accept language header to guess user's country, if available

I want to use Javascript/PHP to determine if a user's browser has a country (locale variant) defined in the accept-language header.
Knowing that the contents of the accept-language header value can vary, and is not always accurate, is there a way to logically determine if a country present in the accept-language header string? And then extract that via JS?
For example, if a user's accept language header contains en-US, I would want to set a local variable="US".
My intial thought was to create a very large array of all accept header language and locale values and check against that, but that did not seem like a very efficient approach.
The Accept-Language header has a couple of formats it can take, see the documentation on MDN. Getting the preferred languages as a structured list (like an array sorted by q-factor) is just a matter of parsing that list in a way that is resilient to all the possible permutations of the header values.
Instead of trying to write this logic yourself, it's simpler to use a library built for this exact purpose, which will be better at handling all kinds of weird edge-cases. For example, willdurand/Negotiation allows you to parse the Accept-Language header and match it to a list of locales supported by your site to find the best language to use.
You will have to do this on the server-side (in PHP). Once you know the language, you can pass that to client-side JavaScript in a number of ways. For example, you can use the JS tag to include a piece of JavaScript in your page template that sets a global variable with the determined language.

JavaScript's standard API to get the runtime's default locale?

In order to determine the current locale I found different approaches:
Being in the browser, most people suggest looking at the HTTP headers (Accept-Language)
Some people suggest consulting navigator.language
Being in the backend (Node.js) and apart of HTTP, it is suggested to consult the (system dependent) process.env
On the other side, the ECMAScript Internationalization API defines the locales argument to each of the Intl constructors as optional:
If the locales argument is not provided or is undefined, the runtime's
default locale is used.
So, it seems as if there should be a browser-independent and OS independent way to get "the runtime's default locale".
Is there a more straight forward way to get the runtime's default locale than
new Intl.NumberFormat().resolvedOptions().locale
?
The question How/Where does JavaScript detect the default locale? is different as it asks for the implementation of detecting the default locale (in a browser host). In contrast to that, my question is not about implementation but about the existence of a standard API.
I'm not aware of a more straight-forward approach, but as you have already pointed out,
new Intl.NumberFormat().resolvedOptions().locale is a solution
DefaultLocale is an abstract operation, which might or might not be implemented in the future.
Hence, I would argue for polyfilling this function as:
if (typeof DefaultLocale === "undefined") {
function DefaultLocale() {
return new Intl.NumberFormat().resolvedOptions().locale;
}
}
So, your code will reasonably assume the existence and call this function. In some point in the future, when this function is implemented in a standard manner, you will be able to clean up by removing the chunk from above.
Just adding this as a choice, doesn't seem to be much better than the shim from Lajos because I guess one would need to have a request and to interpret the q (see the MDN excerpt below).
If you can get hold of a request's headers, most browsers inject:
Accept-Language
en-US,en;q=0.5
Accept-Language
en-US,en;q=0.9,ro;q=0.8,en-GB;q=0.7
From MDN docs https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language :
A language tag (which is sometimes referred to as a "locale identifier"). This consists of a 2-3 letter base language tag
representing the language, optionally followed by additional subtags
separated by '-'. The most common extra information is the country or
region variant (like 'en-US' or 'fr-CA') or the type of alphabet to
use (like 'sr-Latn'). Other variants like the type of orthography
('de-DE-1996') are usually not used in the context of this header.
Any language; '*' is used as a wildcard.
;q= (q-factor weighting) Any value placed in an order of preference expressed using a relative quality value called weight.
The way I understand it, the highest weight (q-value) should be prefered, if available

Javascript equilent of Java's Locale package, or client side approach to rendering lang/script/country codes

Doing server side rendering of HTML templates I find myself using Java's Locale class (via various helper functions) to render language codes (ISO 639-1), script codes (ISO 15294), and country codes (ISO 3166) as their name (i.e. "en" -> "English") in a manner that respects the current user's selected locale.
Now, moving some stuff to client side rendering (with Angular JS), I'm a bit unsure of how to approach this. Doing the code-to-name conversion on the server side and sending locale-aware JSON is one option, but it would add quite a lot of complexity to how various bits of data get JSONified (by having to pass the locale through lots of layers of functions, mainly.)
On the other hand, I can see how a full-on client side version of the Java Locale class would involve sending lots of data to the client, most of which wouldn't get used most of the time.
Are there any emerging best-practices for doing this kind of stuff with largely single-page apps, or any super libraries that would solve my problem in well fell swoop?
I am not sure this is the "best practice", but I have also developed a single-page app where I loaded a JSON array with useful JS data on load with ajax. That seemed like the best solution to me if the data is not used right at the beginning of the page.
Otherwise you could just add this script at the end of every page, if you don't mind embedded javascript:
<script>
function get_locale(){
return JSON.parse(/* render JSON.encoded data from server here */)
</script>
Sorry if this not really a definitive answer.

JavaScript templating with support for i18n? (and then what?)

In a distant future I'd like to distribute my multilingual single-page-application, containing all sorts of resources (videos, texts, interactive applications), for both the web and for the desktop (offline). That dream scenario would also allow translators to access a unified translator dashboard, from where all translations, for all formats, could be crowdsourced.
But that is the future.
What I'm looking for short-term is instead an efficient solution for integrating i18n into my JavaScript templates. There should also be some method that can be used when templating is not an option, i.e. when SVG text elements (that should hold translated strings) are added dynamically.
I'm not sure about the pros and cons of doing this server-side (i.e with node.js) - please enlighten me - but it would be nice if the data storage could easily be swapped (from database to files etc.) in case the offline delivery mechanism does not come with database support.
Where should I start, and is there any path that will allow me to build the i18n system incrementally without having to shift the whole paradigm when going from small-scale to big?
An idea could be to define a global shortcut like mapping (for instance) _ (underscore) to the translation method of i18n plugin/library whatever s.t. you can then from your JavaScript code (and thus also from within your template) execute calls like
_("textToLocalize")
which will return the correctly localized text based on the current language.
This method could then contact a REST service (with proper caching) for retrieving the localized strings from the server where they're somehow managed in files/db tables.

Locale: Browser or OS Setting?

What does it mean when a "locale" is set to e.g. Japanese? Does it mean the browser is set to be ready to recognize Japanese characters? Or, is it more related to an OS setting?
Related to i18n (internationalization and localization), how do we detect that a user who is visiting our site has a e.g. Japanese locale using JavaScript? Will the following simple check suffice?
var userLocale = navigator.language || navigator.userLanguage;
if (userLocale.toLowerCase() == 'ja-jp') { ... }
Can a Japanese locale browser return something else rather than ja-jp?
Thanks beforehand.
First we need to define what Locale is. In the context you are using it is a ISO639 Language Identifier optionally followed by ISO3166 Country Identifier. This is used to determine end user's preferences (like language of localized content or date and time formats and number formats).
Now, Locale could be set in multiple places. OS usually has multiple settings, for example keyboard layout, formatting preferences, language preferences, code pages (for non-Unicode programs), etc.
Apart from that, web browsers usually allow you to choose your own preferences (Safari is an exception here). These preferences are send along with each request to the web server, via HTTP Accept-Language header. This is something you should somehow read on the server side (which unfortunately means some server-side code in PHP, C#, Java, whatever) and maybe passed to your client-side script.
I must state here that something like navigator.language is not the way to go for two reasons:
This is not cross-browser compatible, that is other web browsers would require different code. That is if they allow reading this information in the first place.
This setting usually refers to web browser's language (the language web browser's user interface is translated to) and has nothing to do with actual user's preference.
So to answer your question: no, this check will not suffice.
That is mostly an OS/browser setting, controlling e.g. language of the OS, date/time format, decimal point/comma, etc. - in other words, settings that vary in different locales of the world. This has no direct correlation with font/character support.
As far as I know, ja-jp is the only standardized Japanese locale/language combination; but as #Siku-siku.com suggested in the comments "(based on real testings using Japanese version of IE/WinXP) also check for ja because that's one of the available options in the IE language setting."

Categories

Resources