// internal storage for locale config files import { Locale, LocaleData } from './locale.class'; import { baseConfig } from './locale.defaults'; import { hasOwnProp, isArray, isObject, isString, isUndefined, toInt } from '../utils/type-checks'; import { compareArrays } from '../utils/compare-arrays'; const locales: { [key: string]: Locale } = {}; const localeFamilies: { [key: string]: {name: string; config: LocaleData}[] } = {}; let globalLocale: Locale; function normalizeLocale(key: string): string { return key ? key.toLowerCase().replace('_', '-') : key; } // pick the locale from the array // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each // substring from most specific to least, // but move to the next array item if it's a more specific variant than the current root function chooseLocale(names: string[]): Locale { let next; let locale; let i = 0; while (i < names.length) { const split = normalizeLocale(names[i]).split('-'); let j = split.length; next = normalizeLocale(names[i + 1]); next = next ? next.split('-') : null; while (j > 0) { locale = loadLocale(split.slice(0, j).join('-')); if (locale) { return locale; } if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { // the next array item is better than a shallower substring of this one break; } j--; } i++; } return null; } export function mergeConfigs(parentConfig: LocaleData, childConfig: LocaleData) { const res: LocaleData = Object.assign({}, parentConfig); for (const childProp in childConfig) { if (!hasOwnProp(childConfig, childProp)) { continue; } if (isObject(parentConfig[childProp]) && isObject(childConfig[childProp])) { res[childProp] = {}; Object.assign(res[childProp], parentConfig[childProp]); Object.assign(res[childProp], childConfig[childProp]); } else if (childConfig[childProp] != null) { res[childProp] = childConfig[childProp]; } else { delete res[childProp]; } } let parentProp; for (parentProp in parentConfig) { if ( hasOwnProp(parentConfig, parentProp) && !hasOwnProp(childConfig, parentProp) && isObject(parentConfig[parentProp as keyof LocaleData]) ) { // make sure changes to properties don't modify parent config res[parentProp as keyof LocaleData] = Object.assign({}, res[parentProp as keyof LocaleData]); } } return res; } function loadLocale(name: string): Locale { // no way! /* var oldLocale = null; // TODO: Find a better way to register and load all the locales in Node if (!locales[name] && (typeof module !== 'undefined') && module && module.exports) { try { oldLocale = globalLocale._abbr; var aliasedRequire = require; aliasedRequire('./locale/' + name); getSetGlobalLocale(oldLocale); } catch (e) {} }*/ if (!locales[name]) { // tslint:disable-next-line console.error(`Khronos locale error: please load locale "${name}" before using it`); // throw new Error(`Khronos locale error: please load locale "${name}" before using it`); } return locales[name]; } // This function will load locale and then set the global locale. If // no arguments are passed in, it will simply return the current global // locale key. export function getSetGlobalLocale(key?: string | string[], values?: LocaleData): string { let data: Locale; if (key) { if (isUndefined(values)) { data = getLocale(key); } else if (isString(key)) { data = defineLocale(key, values); } if (data) { globalLocale = data; } } return globalLocale && globalLocale._abbr; } export function defineLocale(name: string, config?: LocaleData): Locale { if (config === null) { // useful for testing delete locales[name]; globalLocale = getLocale('en'); return null; } if (!config) { return; } let parentConfig = baseConfig; config.abbr = name; if (config.parentLocale != null) { if (locales[config.parentLocale] != null) { parentConfig = locales[config.parentLocale]._config; } else { if (!localeFamilies[config.parentLocale]) { localeFamilies[config.parentLocale] = []; } localeFamilies[config.parentLocale].push({ name, config }); return null; } } locales[name] = new Locale(mergeConfigs(parentConfig, config)); if (localeFamilies[name]) { localeFamilies[name].forEach(function (x) { defineLocale(x.name, x.config); }); } // backwards compat for now: also set the locale // make sure we set the locale AFTER all child locales have been // created, so we won't end up with the child locale set. getSetGlobalLocale(name); return locales[name]; } export function updateLocale(name: string, config?: LocaleData): Locale { let _config = config; if (_config != null) { let parentConfig = baseConfig; // MERGE const tmpLocale = loadLocale(name); if (tmpLocale != null) { parentConfig = tmpLocale._config; } _config = mergeConfigs(parentConfig, _config); const locale = new Locale(_config); locale.parentLocale = locales[name]; locales[name] = locale; // backwards compat for now: also set the locale getSetGlobalLocale(name); } else { // pass null for config to unupdate, useful for tests if (locales[name] != null) { if (locales[name].parentLocale != null) { locales[name] = locales[name].parentLocale; } else if (locales[name] != null) { delete locales[name]; } } } return locales[name]; } // returns locale data export function getLocale(key?: string | string[]): Locale { if (!key) { return globalLocale; } // let locale; const _key = isArray(key) ? key : [key]; return chooseLocale(_key); } export function listLocales(): string[] { return Object.keys(locales); } // define default locale getSetGlobalLocale('en', { dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal(num: number): string { const b = num % 10; const output = toInt((num % 100) / 10) === 1 ? 'th' : b === 1 ? 'st' : b === 2 ? 'nd' : b === 3 ? 'rd' : 'th'; return num + output; } });