src/chronos/locale/locales.ts
// 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;
}
});