src/utils/locale.js
/*
* OS.js - JavaScript Cloud/Web Desktop Platform
*
* Copyright (c) Anders Evenrud <andersevenrud@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Anders Evenrud <andersevenrud@gmail.com>
* @license Simplified BSD License
*/
import dateformat from 'dateformat';
const FALLBACK_LOCALE = 'en_EN';
// TODO
const prefixMap = {
nb: 'nb_NO'
};
const sprintfRegex = /\{(\d+)\}/g;
const sprintfMatcher = args => (m, n) =>
n in args ? args[n] : m;
const getDefaultLocale = (core, key) => core.config('locale.' + key);
const getUserLocale = (core, key, defaultLocale) => core.make('osjs/settings')
.get('osjs/locale', key, defaultLocale);
/**
* Gest the set localization
* @param {Core} core OS.js Core IoC
* @param {string} key Settings key (locales.*)
* @return {object} An object with defaultLocale and userLocale
*/
export const getLocale = (core, key) => {
const defaultLocale = getDefaultLocale(core, key);
const userLocale = getUserLocale(core, key, defaultLocale);
return {defaultLocale, userLocale};
};
/**
* Gets a raw string from a tree of translations based on key
*
* Note that this function will fall back to a pre-configured locale
* if the given locale names were not found.
*
* @private
* @param {object} list The tree of translations
* @param {string} ul User locale name
* @param {string} dl Default locale name
* @param {string} k The key to translate from tree
* @return {string} The raw string
*/
const getFromList = (list, ul, dl, k) => {
const fallbackList = list[FALLBACK_LOCALE] || {};
const localizedList = list[ul] || list[dl] || {};
if (typeof localizedList[k] === 'undefined') {
return fallbackList[k] || k;
}
return localizedList[k];
};
/**
* Translates a key + arguments from a tree of translations
*
* @example
* translate({en_EN: {foo: 'Hello {0}'}}, 'nb_NO', 'en_EN', 'foo', 'World') => 'Hello World'
*
* @private
* @param {object} list The tree of translations
* @param {string} ul User locale name
* @param {string} dl Default locale name
* @param {string} k The key to translate from tree
* @param {...*} args A list of arguments that are defined in the translation string
* @return {string} The translated string
*/
export const translate = (list, ul, dl, k, ...args) => {
const fmt = getFromList(list, ul, dl, k);
return fmt.replace(sprintfRegex, sprintfMatcher(args));
};
/**
* Translates a given flat tree of locales
*
* Will automatically detect the current locale from the user.
*
* Returns a function that takes a key and returns the correct string.
*
* @example
* translatableFlat({en_EN: 'Hello World'}); // => 'Hello World'
*
* @param {object} list The tree of translations
* @param {string} defaultValue Default value if none found
* @return {string} The translated string
*/
export const translatableFlat = core => (list, defaultValue) => {
const {defaultLocale, userLocale} = getLocale(core, 'language');
return list[userLocale] || list[defaultLocale] || list[FALLBACK_LOCALE] || defaultValue;
};
/**
* Translates a given tree of locales.
*
* Will automatically detect the current locale from the user.
*
* Returns a `translate` function that takes a key and list of arguments.
*
* @see translate
* @example
* translatable({en_EN: {foo: 'Hello {0}'}})
* ('foo', 'World'); // => 'Hello World'
* @param {string} k List key
* @param {...args} Format arguments
* @return {Function} A translation function
*/
export const translatable = core => list => {
const {defaultLocale, userLocale} = getLocale(core, 'language');
return (k, ...args) => translate(
list,
userLocale,
defaultLocale,
k,
...args
);
};
/**
* Formats a given Date to a specified format
*
* Will automatically detect the current locale from the user.
*
* Formats are 'shortDate', 'mediumDate', 'longDate', 'fullDate',
* 'shortTime' and 'longTime'
*
* @param {Date} date Date object
* @param {string} fmt Format
* @return {string}
*/
export const format = core => (date, fmt) => {
const {defaultLocale, userLocale} = getLocale(core, 'format.' + fmt);
const useFormat = userLocale || defaultLocale || fmt;
return dateformat(date, useFormat);
};
/**
* Returns the navigator language
* @param {object} [nav]
* @return {string}
*/
export const browserLocale = (nav = {}) => {
const browserLanguage = nav.userLanguage || nav.language || '';
const get = l => prefixMap[l] ? prefixMap[l] : (l.match(/_/)
? l
: (l ? `${l}_${l.toUpperCase()}` : ''));
const langs = (nav.languages || [browserLanguage])
.map(l => get(l.replace('-', '_')))
.filter(str => !!str);
return langs[langs.length - 1];
};
/**
* Figures out what locale the browser is running as
*
* @param {string} [defaultLocale=en_EN] Default locale if none found
* @return {string} The browser locale
*/
export const clientLocale = (() => {
const lang = browserLocale(navigator);
return (defaultLocale = FALLBACK_LOCALE, acceptedLocales = []) => {
const found = lang || defaultLocale;
if (acceptedLocales.lenght > 0 && acceptedLocales.indexOf(lang) === -1) {
return defaultLocale;
}
return found;
};
})();