I18N and L10N


Internationalization and localization, often abbreviated to the numeronyms i18n and l10n. I18n is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting internationalized software for a specific region or language by adding locale-specific components and translating text. Localization (which is potentially performed multiple times, for different locales) uses the infrastructure or flexibility provided by internationalization (which is ideally performed only once, or as an integral part of ongoing development). Take Moment.js as an example, it supports setLocale to switch to different language is the way it implement I18N, each locale configure file specific the time format and other regional related behaviors is what called localization.


i18n solution in thinkjs 3.0, implement base on Jed, Moment and Numeral.


npm install think-i18n --save


// thinkjs config/extend.js

const createI18n = require('think-i18n');
const path = require('path');

module.exports = [
    i18nFolder: path.resolve(__dirname, '../i18n'),
    localesMapping(locales) {return 'en';}


complete options

Locale Settings

Each locale settings is a javascript file. see example

module.exports = {
    app: think.app, // if not passed in, __ will not be auto `assign` into `think-view` instance
    dateFormat, // optional
    numeralFormat, // optional
  • localeId: The unique name of your locale.

  • dateFormat: Will apply to moment.local(localeId, dateFormat); if empty you will get a moment instance with 'en' locale.

  • numeralFormat: Will apply numeral.locales[localeId] = numeralFormat; if empty you will get numeral instance with 'en' locale.

  • translation: Equivalent to Jed locale_data, if you use .po file, jed suggest use po2json which support jed format transform.

Controller and View (nunjucks)


You can get i18n instance or current locale in controller.

    async indexAction(){


If you used think-view , think-i18n will auto-inject an i18n instance into __ field, like this.assign('__', this.getI18n()).

{{ __('some key') }} translation
{{ __.jed.dgettext('domain', 'some key') }} translation in specify domain
{{ __.moment().format('llll') }} datetime format
{{ __.numeral(1000).format('currency') }} number format (see numberFormat.formats)

Complete Options

  • i18nFolder:string Directory path where you put all locale settings files

  • localesMapping:function(locales){return localeId;} Given a list of possible locales, return a localeId

  • getLocale default logic is to extract header['accept-language'] if set to falsy value. To get from url query locale, set to {by: 'query', name: 'locale'}. To get from cookie of locale, set to {by: 'cookie', name: 'locale'} To implement your own logic, function(ctx) {return locale;}

  • debugLocale set to value of localeId

  • jedOptions You can set domain and missing_key_callback, for details refer to jed doc.

    default value is {}, the final jed options will be

   Object.assign(jedOptions, {locale_data: <your locale translation>})

Some Thoughs Behine

Why combine all there libraries' i18n config into one? the answer is for transparent but most importantly, flexibility to compose you date, number and message's behavior per locale, for example, you have a website in China and want to provide English translation, but keep the chinese currency symbol, so you can compose english translation and chinese date and currency in your en.js locale setting.

  // locale setting of en.js
  module.exports = {
    localeId: 'en_ch',
    translation: require('../english.po.json'),
    dateFormat: require('../moment/en.json'),
    numeralFormat: require('../numeral/en.json')

We should always use customize format.

  • use __.moment().format('llll') instead of moment().format('YYYY-MM-dd HH:mm').
  • use __.numeral(value).format('customFormat') instead of numeral(value).format('00.00$'),


  • locale id must be lower case.
  • If you defined en locale, witch will override the default en locale in Numeral.
  • In case you also want to use moment and numeral in your system, we isolated the Moment and Numeral by use bundledDependencies in package.json.
  • Numeral doesn't not support per locale custom format, this is done using some tricks and you can config each locale's customFormat in config.numeralFormat.formats.