On Github leereamsnyder / ibm-fed-globalization
Front-end developer for IBM Containers
On Alchemy Slack: @lee
ljreamsn@us.ibm.com
@leereamsnyder
Put your speaker notes here. You can see them pressing 's'.US: 8/31/2015
China: 2015.08.31
France: 31-08-2015
Canada: ¯\_(ツ)_/¯
Source: Date formats by country
Everyone formats dates just a little bit (or a lot bit) differently. Here's August 31, 2015 with a short datestamp in a couple of countries. Note the different order of the numbers; the different use of 8 or zero-8; different separators expected between elements. I love you, Canada, but you're a hot mess. Canada, depending on application, uses all of the above and then some.US: 4,294,967,295.00
France: 4 294 967 295,000
Italy: 4.294.967.295,000
Source: http://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
Here's a big honking number in a variety of formats. Note the different separators, and sometimes the decimal point is a comma. This stuff is so hard-coded in our heads that I can't even see those other two as large numbers.locales/en/resources.json:
{ "backToDashboard": "Back to Dashboard", […] }
locales/es/resources.json:
{ "backToDashboard": "Volver al Panel de control", […] }So this is what strings look like now. We keep them in a set of JSON files in a bunch of folders by language. Those JSON files have keys like "backToDashboard"; the values are the corresponding text, in that language.
Nope:
<a href="#">Back to Dashboard</a>
Yep:
<a href="#">{ __('backToDashboard') }</a>
Sometimes, strings are obvious. Text in an element that gets displayed. Easy. Every place that you would have text, you will instead have a function that looks for a translated version of that text. Every single time. I'm showing these examples using the Dust template language, and I've created a shortcut "double underscore" function to look up text by key.Nope:
<img alt="A lovely sunset" src="sunset.jpg">
var text = 'A thing I want to say'; throw new Error('The request failed')
Yep:
<img alt="{ __('lovelySunset') }" src="sunset.jpg">
var text = __('thingToSay'); throw new Error( __('errors.requestFailed') )When you start thinking this way, you realize you're using strings everywhere. Attributes, variables, messages… they're everywhere. And you can't just write one anymore. This is why I said it's better to start early.
Nope:
var greeting = 'Hello ' + user.name + '! You are ' + user.age + ' years old'
Yep:
Resources:
"greeting": "Hello __name__! You are __age__ years old"
"greeting": "¡Hola __name__! Tiene __age__ años"
Later…
var greeting = __('greeting', user)You can totally forget about building messages one tiny piece at a time, right? What if subject/verb order is different? What if there are differences in punctuation? You can see in the second example a little bit of that with Spanish. The library I use lets you pass in an object and replace variables that way. Personally, dealing with strings in this manner is a lot more pleasant than piecing them together with "+" sign.
Wha?
Language detection; message formatting
Where?
Node.js and browsers
As far as I know, i18next is the de-facto standard for services on Bluemix. We use it to control the list of supported languages, detect a user's language (more on that soon), store messages, and grab translated messages as I was showing earlier. It runs both server-side and in the browser, which is handy so both your server and client can use the same messages.https://github.com/jquery/globalize
Wha?
Date/time formatting; Numbers; Relative times
Where?
Node.js and browsers
Unfortunately for us i18next doesn't do everything, so I supplement it with a relatively new library from the jQuery Foundation called "Globalize". It helps you format dates, times, numbers, currency, relative times… It's pretty nice. Don't let the name throw you: if you're one of those post-jQuery developers, jQuery is not required. It's just from the same team. There are a bunch of other libraries, but most of the teams in our little world have settled on i18next and Globalize, and it helps to be using the same stuff so you can compare notes.i18next defaults options:
{ useCookie: true, fallbackLng: "dev", fallbackOnNull: false }These are some of the default options for i18next. They are troublesome. By default, i18next tracks languages with a cookie. As we'll discuss in a moment, that's a dumb idea. It also assumes that there will be a language to fallback to named "dev", which is a not a language I've ever heard of. Change that to English. It also doesn't try to use a fallback by default. Maybe you like that.
Nope :(
navigator.language
Nope :(
navigator.languages
Nope :(
navigator.userLanguageHere's an incomplete list of issues around detecting language in the browser. That first one, navigator.language, in Chrome is permanently the language in which you downloaded Chrome. It doesn't change with your preferences. Also, not in IE. That second one, languageS, Chrome will inject extra languages in there against your will. Also, not in IE. that userLanguage one, that one is Internet Explorer, but it reflects your system preference, not the language preference in the browser. Cute. If anyone has a reliable way to detect the preferred language in the browser, I'd love to hear it.
/de /en-US /es /fr /it /ja /ko /pt-BR /zh-CN /zh-TWHere's one example. You'll start out with this, the 10 standard languages we support. But the the testers will do something like set their browser to "English", but not specifically "US English."
/de /en #same as en-US /en-US /es /fr /it /ja /ko /pt-BR /zh-CN /zh-TWThe quick fix there is to copy "en-us" to "en" and call it a day.
/de /en #same as en-US /en-US /es /fr /it /ja /ko /pt #same as pt-BR /pt-BR /zh #same as zh-CN /zh-CN /zh-TWOh, except you have to do that for Portuguese and Chinese also.
/de /en #same as en-US /en-US /es /fr /it /ja /ko /pt #same as pt-BR /pt-BR /zh #same as zh-CN /zh-CN /zh-Hans #same as zh-CN /zh-Hant #same as zh-TW /zh-TWOh, except that there are sometimes two different codes for the same Chinese language, depending on the browser COUGH Internet Explorer COUGH This doesn't even fit any more. You can see how this gets unsustainable. And you have to remember to copy the things every time. Not good.
Better!
/de /en /es /fr /it /ja /ko /pt /zh /zh-TW
Express configuration
app.use('/locales/zh-Hant', express.static('locales/zh-TW'));One trick is to only get specific if you absolutely have to. For example, most translation libraries assume if you say "Portuguese" but don't specify a country, they assume you meant Brazil. Same with English and the United States. So you can work with only the language code instead of languages and countries. If you can't get away with that, have your server serve a single directory of files to multiple requests so you don't have to copy the files all over the place. In that second snippet, the server will use the same files for a request for zh-Hant as it would for the identical zh-TW. One line of code, so much sanity saved forever.
IBM Globalization Design Guidelines
http://www-01.ibm.com/software/globalization/guidelines/index.html
Using i18next for an IBM/Bluemix Service
https://hub.jazz.net/project/ljreamsn/i18next-guide/overview
Gulp i18next Parser task
Some resources out there. That second one is a readme I wrote detailing some of the "Gotchas" of getting an app globalized to run as a part of Bluemix. The last one is the template watcher that I mentioned that keeps our resources updated.IBM Designcamp :: © 2015 IBM Corporation