Software and Application Internationalization

Software and Application Internationalization

With the growing demand for digital solutions worldwide, catering to audiences from diverse locations has become increasingly necessary. One of the most popular practices to achieve wider reach is Internationalization. Internationalization, also known as i18n (because there are 18 letters between "i" and "n" in the word), is a process gaining strength in solution development. Aiming to be prepared for a maximum number of users, platforms of all kinds have incorporated language-sensitive APIs to accommodate not only a large number of languages but also their variations between locations.

To better understand Internationalization, it is important to grasp Localization. Localization, also known as l10n (because there are 10 letters between "l" and "n" in the word), refers to the process of adapting a product to comply with the language, culture, and other requirements of a specific target market or location. Some mistakenly perceive the process as simple translation, but in reality, it includes number, currency, dates and times formatting, changes in keyboard layouts, organizational practices, and writing orientations, among other aspects. With this in mind, let's think of Localization applied globally, that would be Internationalization.

Currently, Internationalization has already reached a wide market. The group of platforms that support i18n includes Android and iOS mobile devices; frontend frameworks like React, Angular, and Vue; backend languages like Python, Java, PHP, Ruby, and Go, and frameworks based on these languages, it also includes the cross-platform language Flutter. Additionally, independent libraries have offered options for implementing the process.

As an example, let's observe the native npm (Node Package Manager) API for JavaScript applications:

The Intl object

In JavaScript, internationalization can be applied using the Intl object, which stores constructors for various implementation features. (If you are not familiar with constructors, I invite you to read my article on Javascript Contexts.) The constructors within the Intl object receive two arguments: locale and options.

  • Locale: A string corresponding to a BCP 47 language tag or a list of such tags. If this argument is not passed, the default locale used is that of the runtime.

  • Options: An optional object with properties that vary depending on the constructor in use.

Intl.Collator

The Intl.Collator constructor enables linguistic-sensitive string comparison. A great use case is organizing lists based on linguistic comparisons.

const characters = ['a', '手に', '大人', 'b', '金魚', 'きんぎょ'];

function compare(language, characters) {
    return characters.sort(new Intl.Collator(language).compare);
}

console.log(compare('pt', characters));
console.log(compare('zh', characters));
console.log(compare('ja', characters));

// Expected Output
// [ 'a', 'b', 'きんぎょ', '大人', '手に', '金魚' ]
// [ '大人', '金魚', '手に', 'a', 'b', 'きんぎょ' ]
// [ 'a', 'b', 'きんぎょ', '金魚', '手に', '大人' ]

Intl.DateTimeFormat

The Intl.DateTimeFormat constructor allows for linguistic-sensitive date formatting. Below, we can observe the variations in configuration, for the display of full written representation and time zones.

const date = new Date(Date.UTC(1994, 08, 14, 15, 45, 13));

function format(language, options) {
    return new Intl.DateTimeFormat(language, options).format(date);
}

console.log(format('en-US'));
console.log(format('ar', { dateStyle: 'full' }));
console.log(format('pt', { dateStyle: 'full', timeStyle: 'long', timeZone: 'America/Bahia' }));

// Expected Output:
// "9/14/1994"
// "الأربعاء، 14 سبتمبر 1994"
// "quarta-feira, 14 de setembro de 1994 às 12:45:13 BRT"

Intl.ListFormat

The Intl.ListFormat constructor enables linguistic-sensitive list formatting. In the specific case below, we see the differences between languages that do not use conjunctions in the same way.

const fruits = ['Banana', 'Grape', 'Lime'];

function format(language, options) {
    return new Intl.ListFormat(language, options).format(fruits);
}

console.log(format('en', { type: 'conjunction' }));
console.log(format('tr', { type: 'conjunction' }));
console.log(format('ar-SU', { type: 'conjunction' }));

// Expected Output:
// "Banana, Grape, and Lime"
// "Banana, Grape ve Lime"
// "Banana وGrape وLime"

Intl.NumberFormat

The Intl.NumberFormat constructor enables linguistic-sensitive number formatting. With this constructor, it becomes easy to format currency in various styles and languages.

const number = 123456.789;

console.log(new Intl.NumberFormat("ar-EG").format(number));
console.log(new Intl.NumberFormat("ja-JP", { style: 'currency', currency: 'JPY'}).format(number));

// Expected Output:
// "١٢٣٤٥٦٫٧٨٩"
// "¥123,457"

Intl.PluralRules

The Intl.PluralRules constructor enables the formatting of plurals and rules related to language plurals. Each location has its rules, something extremely variable and relative to the locale.

const arCardinalRules = new Intl.PluralRules("ar-EG");
console.log(arCardinalRules.select(0)); // "zero"
console.log(arCardinalRules.select(1)); // "one"
console.log(arCardinalRules.select(2)); // "two"
console.log(arCardinalRules.select(6)); // "few"
console.log(arCardinalRules.select(18)); // "many"

Intl.RelativeTimeFormat

The Intl.RelativeTimeFormat constructor enables linguistic-sensitive time formatting. With this, we can easily achieve linguistic variations based on the concept of relative time.

function createFormatter(language, options){
    return new Intl.RelativeTimeFormat(language, options);
}

const ptFormatter = createFormatter('pt', { style: 'long' });
const enFormatter = createFormatter('en', { style: 'long' });
const esFormatter = createFormatter('es', { numeric: 'auto' });

console.log(ptFormatter.format(3, 'quarter'));
console.log(enFormatter.format(-1, 'day'));
console.log(esFormatter.format(2, 'day'));

// Expected Output
// "em 3 trimestres"
// "1 day ago"
// "pasado mañana"

Intl.Segmenter

The Intl.Segmenter constructor enables text segmentation, allowing us to extract significant items such as words and sentences.

const segmenterPt = new Intl.Segmenter('pt', { granularity: 'sentence' });
const string = 'Olha lá. Que homem alto!';

const iterator1 = segmenterFr.segment(string1)[Symbol.iterator]();

console.log(iterator1.next().value.segment);
console.log(iterator1.next().value.segment);

// Expected Output
// "Olha lá. "
// "Que homem alto!"

As we can see, the Internationalization process involves careful adaptation to target locations. Finally, it is necessary to know the implementation details in the target programming language. That's all, folks!