Source handlebars.phrase/handlebars.phrase.js

  1. (function (moduleFactory) {
  2. if(typeof exports === "object") {
  3. module.exports = moduleFactory(require("lodash"));
  4. } else if (typeof define === "function" && define.amd) {
  5. define(["lodash"], moduleFactory);
  6. }
  7. }(function (_) {
  8. /**
  9. * @module handlebars%phrase
  10. * @description Provides helper to allow strings to be externalised
  11. *
  12. * var Handlebars = require("handlebars");
  13. * var Phrase = require("handlebars.phrase");
  14. * Phrase.registerHelpers(Handlebars);
  15. * Phrase.setLanguages(langs);
  16. * Phrase.locale(lang);
  17. * @returns {object} Phrase instance
  18. */
  19. var Handlebars;
  20. var languageRegister;
  21. var phraseRegister = {};
  22. var phraseStrings = {}; // raw lang strings that can be returned as is
  23. var phraseTemplates = {}; // compiled lang template cache
  24. var locale = "default";
  25. var pendDelimiter = ".";
  26. var defaultLocaleProvidesFallback = false;
  27. var phraseFilter;
  28. var Phrase = (function () {
  29. /**
  30. * @template phrase
  31. * @block helper
  32. * @description Outputs a phrase corresponding to key provided
  33. *
  34. * {{phrase key}}
  35. *
  36. * Output a phrase, interpolating a variable from the context
  37. *
  38. * {{phrase key var=var}}
  39. *
  40. * Output a phrase for a particular locale
  41. *
  42. * {{phrase key lang}}
  43. * {{phrase key locale=lang}}
  44. *
  45. * Output a phrase for an appended key
  46. *
  47. * {{phrase key _append=append}}
  48. * {{phrase key _append=append _appendix=appendix}}
  49. *
  50. * Output a phrase for an prepended key
  51. *
  52. * {{phrase key _prepend=prepend}}
  53. * {{phrase key _prepend=prepend _prependix=prependix}}
  54. *
  55. * Output a phrase for debugging purposes
  56. *
  57. * {{phrase key _debug=true}}
  58. *
  59. * Use simpliflied syntax
  60. * ((key))
  61. * ((key var=var))
  62. *
  63. */
  64. var PhraseHelper = function () {
  65. var args = Array.prototype.slice.call(arguments);
  66. var isGet = false;
  67. if (args[0] === "get") {
  68. isGet = true;
  69. args.shift();
  70. }
  71. // if we don't pass key, how can we tell that arg isn't a locale?
  72. var key = args[0];
  73. var originalKey = key;
  74. var params = {};
  75. var options = args[args.length-1];
  76. // allow helper context to be overridden
  77. try {
  78. // add in this before opions.hash.params
  79. // however, that means all vars rampage through all scopes
  80. params = _.extend({}, (options.hash.params || {}), options.hash);
  81. delete options.hash.params;
  82. } catch (e) {
  83. console.log(e, options, arguments);
  84. }
  85. // Determine which locale is in effect
  86. var tmpLocale;
  87. if (args.length === 3) {
  88. tmpLocale = args[1];
  89. } else {
  90. tmpLocale = params.locale;
  91. }
  92. if (!tmpLocale && !isGet) {
  93. tmpLocale = this.locale;
  94. }
  95. if (!tmpLocale || tmpLocale === "default") {
  96. tmpLocale = locale;
  97. }
  98. // ensure phraseRegister entries exists for locale and tmpLocale
  99. if (!phraseRegister[locale]) {
  100. phraseRegister[locale] = {};
  101. phraseStrings[locale] = {};
  102. phraseTemplates[locale] = {};
  103. }
  104. if (!phraseRegister[tmpLocale]) {
  105. phraseRegister[tmpLocale] = {};
  106. phraseStrings[tmpLocale] = {};
  107. phraseTemplates[tmpLocale] = {};
  108. }
  109. if (params._append) {
  110. key += pendDelimiter + params._append;
  111. if (params._appendix) {
  112. key += pendDelimiter + params._appendix;
  113. }
  114. }
  115. if (params._prepend) {
  116. key = params._prepend + pendDelimiter + key;
  117. if (params._prependix) {
  118. key = params._prependix + pendDelimiter + key;
  119. }
  120. }
  121. if (!phraseRegister[tmpLocale][key]) {
  122. var templateString = languageRegister[tmpLocale][key];
  123. if (templateString === undefined && tmpLocale !== locale) {
  124. languageRegister[tmpLocale][key] = languageRegister[locale][key];
  125. templateString = languageRegister[tmpLocale][key];
  126. }
  127. if (templateString !== undefined) {
  128. if (templateString.indexOf("{{") === -1 && templateString.indexOf("((") === -1) {
  129. phraseStrings[tmpLocale][key] = templateString;
  130. // possible to cache if compilable but
  131. // just reference to another static phrase?
  132. } else {
  133. phraseTemplates[tmpLocale][key] = Handlebars.compile(templateString);
  134. }
  135. }
  136. phraseRegister[tmpLocale][key] = true;
  137. }
  138. var langValue;
  139. // || (defaultLocaleProvidesFallback && phraseStrings[locale][key])
  140. if (phraseStrings[tmpLocale][key]) {
  141. langValue = phraseStrings[tmpLocale][key];
  142. } else if (phraseTemplates[tmpLocale][key]) {
  143. // could possibly cache the output using key created from input
  144. // but would that be any quicker?
  145. // ensure that tmpLocale gets passed on to nested phrases
  146. params.locale = tmpLocale;
  147. var data = _.extend({}, this, params);
  148. var template = phraseTemplates[tmpLocale][key];
  149. langValue = template.call(this, data);
  150. } else {
  151. if (!isGet && !params["_allow-empty"]) {
  152. // return the key itself
  153. langValue = key;
  154. }
  155. }
  156. if (langValue && params._debug) {
  157. // output a value for debugging
  158. langValue = '<phrase data-phrase-key="' + key + '">' + langValue + "</phrase>";
  159. }
  160. if (langValue && phraseFilter) {
  161. // apply any filter that has been set
  162. langValue = phraseFilter(langValue);
  163. }
  164. return langValue ? new Handlebars.SafeString(langValue) : "";
  165. };
  166. var external = {
  167. /**
  168. * @method locale
  169. * @static
  170. * @param {string} [loc] Locale to change to
  171. * @description Get or set default locale used by Phrase
  172. *
  173. * If called without loc parameter, returns locale
  174. *
  175. * If called with loc parameter, sets locale
  176. *
  177. * @returns {string} Phrase’s locale
  178. */
  179. locale: function (loc) {
  180. if (loc) {
  181. locale = loc;
  182. }
  183. return locale;
  184. },
  185. /**
  186. * @method get
  187. * @static
  188. * @param {object} phrase Lookup key
  189. * @param {object} args Any arguments to pass to phrase
  190. * @param {object} context Specific context to use
  191. * @param {string} [locale] Locale to use
  192. * @description Get HandlebarsSafeString object for a key
  193. *
  194. * @returns {HandlebarsSafeString} phrase
  195. */
  196. get: function (phrase, args, context, locale) {
  197. if (!args) {
  198. args = {};
  199. }
  200. context = context || this;
  201. var params = ["get", phrase, {hash: args}];
  202. if (locale) {
  203. params.splice(2, 0, locale);
  204. }
  205. return PhraseHelper.apply(context, params);
  206. },
  207. /**
  208. * @method getString
  209. * @static
  210. * @param {object} phrase Lookup key
  211. * @param {object} args Any arguments to pass to phrase
  212. * @param {object} context Specific context to use
  213. * @param {string} [locale] Locale to use
  214. * @description Get phrase value for a key
  215. *
  216. * @returns {string} phrase
  217. */
  218. getString: function (phrase, args, context, locale) {
  219. var value = external.get(phrase, args, context, locale);
  220. return value ? value.toString() : undefined;
  221. },
  222. /**
  223. * @method setLanguages
  224. * @static
  225. * @param {object} [languages] Object of language phrases
  226. * @param {object} [options]
  227. * @param {boolean} [options.localefallback=false] Whether default language should provide missing values for other languages
  228. * @param {string} [options.penddelimiter=.] Delimiter to use when appending or prepending keys
  229. * @description Set languages object without which Phrase cannot work
  230. *
  231. * Clears any previously set languages
  232. *
  233. */
  234. setLanguages: function (languages, options) {
  235. options = options || {};
  236. phraseRegister = {};
  237. phraseStrings = {};
  238. phraseTemplates = {};
  239. if (languages) {
  240. languageRegister = languages;
  241. }
  242. if (options.penddelimiter) {
  243. pendDelimiter = options.penddelimiter;
  244. }
  245. if (options.localefallback !== undefined) {
  246. defaultLocaleProvidesFallback = !!options.localefallback;
  247. }
  248. },
  249. /**
  250. * @method addLanguages
  251. * @static
  252. * @param {object} [languages] Object of language phrases
  253. * @description Add keys to existing languages object
  254. *
  255. * Clears any previously cached strings or templates for languages passed
  256. */
  257. addLanguages: function (languages) {
  258. languageRegister = languageRegister || {};
  259. if (languages) {
  260. for (var lang in languages) {
  261. phraseRegister[lang] = {};
  262. phraseStrings[lang] = {};
  263. phraseTemplates[lang] = {};
  264. languageRegister[lang] = _.extend({}, languageRegister[lang], languages[lang]);
  265. }
  266. }
  267. },
  268. /**
  269. * @method setLanguage
  270. * @static
  271. * @param {string} lang Language to be set
  272. * @param {object} phrases Phrases object for the language
  273. * @description Set language individually
  274. *
  275. * Clears any previously cached strings or templates for language
  276. *
  277. */
  278. setLanguage: function (lang, phrases) {
  279. languageRegister = languageRegister || {};
  280. phraseRegister[lang] = {};
  281. phraseStrings[lang] = {};
  282. phraseTemplates[lang] = {};
  283. languageRegister[lang] = phrases;
  284. },
  285. /**
  286. * @method addLanguage
  287. * @static
  288. * @param {string} lang Language to add to
  289. * @param {object} phrases Phrases object for the language
  290. * @description Add phrases to individual language
  291. *
  292. * Clears any previously cached strings or templates for language
  293. *
  294. */
  295. addLanguage: function (lang, phrases) {
  296. languageRegister = languageRegister || {};
  297. phraseRegister[lang] = {};
  298. phraseStrings[lang] = {};
  299. phraseTemplates[lang] = {};
  300. languageRegister[lang] = _.extend({}, languageRegister[lang], phrases);
  301. },
  302. /**
  303. * @method registerHelper
  304. * @static
  305. * @param {object} hbars Handlebars instance
  306. * @description Register Phrase helper with Handlebars
  307. *
  308. * - {@link template:phrase}
  309. */
  310. registerHelper: function (hbars) {
  311. Handlebars = hbars;
  312. Handlebars.registerHelper("phrase", PhraseHelper);
  313. var Hparse = Handlebars.parse;
  314. Handlebars.parse = function (input) {
  315. input = input.replace(/\(\(\(([^ ]+?)( .+?)*\)\)\)/g, function (m, m1, m2) {
  316. return '{{{phrase "' + m1 + '"' + (m2 ? m2 : "") + '}}}';
  317. });
  318. input = input.replace(/\(\(([^ ]+?)( .+?)*\)\)/g, function (m, m1, m2) {
  319. return '{{phrase "' + m1 + '"' + (m2 ? m2 : "") + '}}';
  320. });
  321. return Hparse(input);
  322. };
  323. },
  324. /**
  325. * @method setFilter
  326. * @static
  327. * @param {function} fn
  328. * @description Sets a filter to apply to all returned output
  329. */
  330. setFilter: function (fn) {
  331. phraseFilter = fn;
  332. }
  333. };
  334. // alias plural form
  335. external.registerHelpers = external.registerHelper;
  336. return external;
  337. })();
  338. return Phrase;
  339. }));