UNPKG

@dwp/govuk-casa

Version:

Framework for creating basic GOVUK Collect-And-Submit-Applications

161 lines (148 loc) 4.65 kB
/** * I18n utilities. * * When this module is first required, be sure to invoke with the directories * and list of supported locales, e.g: * const I18n = require('govuk-casa/lib/I18n')(localeDirs, supportedLocales); * * This essentially iniialises the I18n singleton object. */ const npath = require('path'); const fs = require('fs'); const logger = require('./Logger')('i18n'); const LOCALE_DATA = Object.create(null); /** * Translator class. */ class Translator { /** * Constructor. * * @param {string} lang Language to use for all translations. */ constructor(lang) { if (typeof lang !== 'string') { throw new TypeError('Translator must be instantiated with a language'); } this.language = lang; } /** * Retrieve the language. * * @returns {string} Language code. */ getLanguage() { return this.language; } /** * Translate string. * * The replacements can be primitives, a function or a object containing such * values. For example, the following are all valid: * t('Hello, %s', 'World') = Hello, World * t('Pay £${fee}', {fee:100}) = Pay £100 * * @param {string} key String to translate * @param {...any} subs Replacements * @returns {string} Translated string * @throws {Error} When a function substitution is used */ t(key, ...subs) { const k = (key || '').split(':'); let str = key; if (typeof LOCALE_DATA[this.language][k[0]] !== 'undefined') { str = typeof LOCALE_DATA[this.language][k[0]][k[1]] === 'undefined' ? key : LOCALE_DATA[this.language][k[0]][k[1]]; } subs.forEach((sub) => { if (typeof sub === 'object') { Object.keys(sub).forEach((subkey) => { str = str.replace(new RegExp(`\\$\\{${subkey}\\}`), sub[subkey]); }); } else if (typeof sub === 'function') { throw new Error('Function substitions are not yet supported'); } else { str = str.replace(/%s/, sub); } }); return str; } } module.exports = function TranslaterInit(localeDirs, supportedLocales) { /** * Flattens a JSON object structure into a "flat" object indexed by paths in a * dot-notation. For example, calling `flatten()` on the structure ... * { * "person": { * "title": { * "label": "Miss" * }. * }. * }. * * ... Results in the new object, written to `tgt`: * { * "person.title.label": "Miss" * } * * @param {string} path Current path (used by recursive element) * @param {object} src Source object to flatten * @param {object} tgt Object into which the resulting data is written * @returns {void} */ function flatten(path, src, tgt) { Object.keys(src).forEach((key) => { const nodepath = path ? [path, key].join('.') : key; if (typeof src[key] === 'object') { flatten(nodepath, src[key], tgt); } else { tgt[nodepath] = src[key]; /* eslint-disable-line no-param-reassign */ } }); } /** * Load the locale into the `LOCALE_DATA` object. * * @param {string} locale Locale to load (e.g. "en"). * @returns {void} * @throws {Error} When locale is missing */ function loadLocale(locale) { try { LOCALE_DATA[locale] = Object.create(null); localeDirs.forEach((localeDir) => { const langDir = npath.resolve(localeDir, locale); if (fs.existsSync(langDir)) { logger.info('Loading %s locale from %s ...', locale, langDir); fs.readdirSync(langDir).forEach((file) => { const ns = npath.basename(file, '.json'); if (typeof LOCALE_DATA[locale][ns] === 'undefined') { LOCALE_DATA[locale][ns] = Object.create(null); } // Load file and strip newlines out, otherwise JSON.parse will fail. // Newlines may be in the file to make them more readable during // content creation, but they are invalid in JSON. let json = fs.readFileSync(npath.resolve(langDir, file), 'utf8'); json = json.replace(/[\r\n]/g, ''); json = JSON.parse(json); flatten(null, json, LOCALE_DATA[locale][ns]); }); } }); } catch (e) { logger.error('Error loading locale: %s', e); throw e; } } // Load data for all supported locales if (!Array.isArray(supportedLocales) || !supportedLocales.length) { throw new Error('You must define at least one supported locale'); } supportedLocales.forEach((l) => { loadLocale(l); }); return { Translator, }; };