(function (moduleFactory) {
if(typeof exports === "object") {
module.exports = moduleFactory(require("lodash"));
} else if (typeof define === "function" && define.amd) {
define(["lodash"], moduleFactory);
}
}(function (_) {
/**
* @module handlebars%phrase
* @description Provides helper to allow strings to be externalised
*
* var Handlebars = require("handlebars");
* var Phrase = require("handlebars.phrase");
* Phrase.registerHelpers(Handlebars);
* Phrase.setLanguages(langs);
* Phrase.locale(lang);
* @returns {object} Phrase instance
*/
var Handlebars;
var languageRegister;
var phraseRegister = {};
var phraseStrings = {}; // raw lang strings that can be returned as is
var phraseTemplates = {}; // compiled lang template cache
var locale = "default";
var pendDelimiter = ".";
var defaultLocaleProvidesFallback = false;
var phraseFilter;
var Phrase = (function () {
/**
* @template phrase
* @block helper
* @description Outputs a phrase corresponding to key provided
*
* {{phrase key}}
*
* Output a phrase, interpolating a variable from the context
*
* {{phrase key var=var}}
*
* Output a phrase for a particular locale
*
* {{phrase key lang}}
* {{phrase key locale=lang}}
*
* Output a phrase for an appended key
*
* {{phrase key _append=append}}
* {{phrase key _append=append _appendix=appendix}}
*
* Output a phrase for an prepended key
*
* {{phrase key _prepend=prepend}}
* {{phrase key _prepend=prepend _prependix=prependix}}
*
* Output a phrase for debugging purposes
*
* {{phrase key _debug=true}}
*
* Use simpliflied syntax
* ((key))
* ((key var=var))
*
*/
var PhraseHelper = function () {
var args = Array.prototype.slice.call(arguments);
var isGet = false;
if (args[0] === "get") {
isGet = true;
args.shift();
}
// if we don't pass key, how can we tell that arg isn't a locale?
var key = args[0];
var originalKey = key;
var params = {};
var options = args[args.length-1];
// allow helper context to be overridden
try {
// add in this before opions.hash.params
// however, that means all vars rampage through all scopes
params = _.extend({}, (options.hash.params || {}), options.hash);
delete options.hash.params;
} catch (e) {
console.log(e, options, arguments);
}
// Determine which locale is in effect
var tmpLocale;
if (args.length === 3) {
tmpLocale = args[1];
} else {
tmpLocale = params.locale;
}
if (!tmpLocale && !isGet) {
tmpLocale = this.locale;
}
if (!tmpLocale || tmpLocale === "default") {
tmpLocale = locale;
}
// ensure phraseRegister entries exists for locale and tmpLocale
if (!phraseRegister[locale]) {
phraseRegister[locale] = {};
phraseStrings[locale] = {};
phraseTemplates[locale] = {};
}
if (!phraseRegister[tmpLocale]) {
phraseRegister[tmpLocale] = {};
phraseStrings[tmpLocale] = {};
phraseTemplates[tmpLocale] = {};
}
if (params._append) {
key += pendDelimiter + params._append;
if (params._appendix) {
key += pendDelimiter + params._appendix;
}
}
if (params._prepend) {
key = params._prepend + pendDelimiter + key;
if (params._prependix) {
key = params._prependix + pendDelimiter + key;
}
}
if (!phraseRegister[tmpLocale][key]) {
var templateString = languageRegister[tmpLocale][key];
if (templateString === undefined && tmpLocale !== locale) {
languageRegister[tmpLocale][key] = languageRegister[locale][key];
templateString = languageRegister[tmpLocale][key];
}
if (templateString !== undefined) {
if (templateString.indexOf("{{") === -1 && templateString.indexOf("((") === -1) {
phraseStrings[tmpLocale][key] = templateString;
// possible to cache if compilable but
// just reference to another static phrase?
} else {
phraseTemplates[tmpLocale][key] = Handlebars.compile(templateString);
}
}
phraseRegister[tmpLocale][key] = true;
}
var langValue;
// || (defaultLocaleProvidesFallback && phraseStrings[locale][key])
if (phraseStrings[tmpLocale][key]) {
langValue = phraseStrings[tmpLocale][key];
} else if (phraseTemplates[tmpLocale][key]) {
// could possibly cache the output using key created from input
// but would that be any quicker?
// ensure that tmpLocale gets passed on to nested phrases
params.locale = tmpLocale;
var data = _.extend({}, this, params);
var template = phraseTemplates[tmpLocale][key];
langValue = template.call(this, data);
} else {
if (!isGet && !params["_allow-empty"]) {
// return the key itself
langValue = key;
}
}
if (langValue && params._debug) {
// output a value for debugging
langValue = '<phrase data-phrase-key="' + key + '">' + langValue + "</phrase>";
}
if (langValue && phraseFilter) {
// apply any filter that has been set
langValue = phraseFilter(langValue);
}
return langValue ? new Handlebars.SafeString(langValue) : "";
};
var external = {
/**
* @method locale
* @static
* @param {string} [loc] Locale to change to
* @description Get or set default locale used by Phrase
*
* If called without loc parameter, returns locale
*
* If called with loc parameter, sets locale
*
* @returns {string} Phrase’s locale
*/
locale: function (loc) {
if (loc) {
locale = loc;
}
return locale;
},
/**
* @method get
* @static
* @param {object} phrase Lookup key
* @param {object} args Any arguments to pass to phrase
* @param {object} context Specific context to use
* @param {string} [locale] Locale to use
* @description Get HandlebarsSafeString object for a key
*
* @returns {HandlebarsSafeString} phrase
*/
get: function (phrase, args, context, locale) {
if (!args) {
args = {};
}
context = context || this;
var params = ["get", phrase, {hash: args}];
if (locale) {
params.splice(2, 0, locale);
}
return PhraseHelper.apply(context, params);
},
/**
* @method getString
* @static
* @param {object} phrase Lookup key
* @param {object} args Any arguments to pass to phrase
* @param {object} context Specific context to use
* @param {string} [locale] Locale to use
* @description Get phrase value for a key
*
* @returns {string} phrase
*/
getString: function (phrase, args, context, locale) {
var value = external.get(phrase, args, context, locale);
return value ? value.toString() : undefined;
},
/**
* @method setLanguages
* @static
* @param {object} [languages] Object of language phrases
* @param {object} [options]
* @param {boolean} [options.localefallback=false] Whether default language should provide missing values for other languages
* @param {string} [options.penddelimiter=.] Delimiter to use when appending or prepending keys
* @description Set languages object without which Phrase cannot work
*
* Clears any previously set languages
*
*/
setLanguages: function (languages, options) {
options = options || {};
phraseRegister = {};
phraseStrings = {};
phraseTemplates = {};
if (languages) {
languageRegister = languages;
}
if (options.penddelimiter) {
pendDelimiter = options.penddelimiter;
}
if (options.localefallback !== undefined) {
defaultLocaleProvidesFallback = !!options.localefallback;
}
},
/**
* @method addLanguages
* @static
* @param {object} [languages] Object of language phrases
* @description Add keys to existing languages object
*
* Clears any previously cached strings or templates for languages passed
*/
addLanguages: function (languages) {
languageRegister = languageRegister || {};
if (languages) {
for (var lang in languages) {
phraseRegister[lang] = {};
phraseStrings[lang] = {};
phraseTemplates[lang] = {};
languageRegister[lang] = _.extend({}, languageRegister[lang], languages[lang]);
}
}
},
/**
* @method setLanguage
* @static
* @param {string} lang Language to be set
* @param {object} phrases Phrases object for the language
* @description Set language individually
*
* Clears any previously cached strings or templates for language
*
*/
setLanguage: function (lang, phrases) {
languageRegister = languageRegister || {};
phraseRegister[lang] = {};
phraseStrings[lang] = {};
phraseTemplates[lang] = {};
languageRegister[lang] = phrases;
},
/**
* @method addLanguage
* @static
* @param {string} lang Language to add to
* @param {object} phrases Phrases object for the language
* @description Add phrases to individual language
*
* Clears any previously cached strings or templates for language
*
*/
addLanguage: function (lang, phrases) {
languageRegister = languageRegister || {};
phraseRegister[lang] = {};
phraseStrings[lang] = {};
phraseTemplates[lang] = {};
languageRegister[lang] = _.extend({}, languageRegister[lang], phrases);
},
/**
* @method registerHelper
* @static
* @param {object} hbars Handlebars instance
* @description Register Phrase helper with Handlebars
*
* - {@link template:phrase}
*/
registerHelper: function (hbars) {
Handlebars = hbars;
Handlebars.registerHelper("phrase", PhraseHelper);
var Hparse = Handlebars.parse;
Handlebars.parse = function (input) {
input = input.replace(/\(\(\(([^ ]+?)( .+?)*\)\)\)/g, function (m, m1, m2) {
return '{{{phrase "' + m1 + '"' + (m2 ? m2 : "") + '}}}';
});
input = input.replace(/\(\(([^ ]+?)( .+?)*\)\)/g, function (m, m1, m2) {
return '{{phrase "' + m1 + '"' + (m2 ? m2 : "") + '}}';
});
return Hparse(input);
};
},
/**
* @method setFilter
* @static
* @param {function} fn
* @description Sets a filter to apply to all returned output
*/
setFilter: function (fn) {
phraseFilter = fn;
}
};
// alias plural form
external.registerHelpers = external.registerHelper;
return external;
})();
return Phrase;
}));