handlebars-i18n 1.8.3 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,11 @@
2
2
  * handlebars-i18n.js
3
3
  *
4
4
  * @author: Florian Walzel
5
- * @date: 2024-08
6
5
  *
7
6
  * handlebars-i18n adds features for localization/
8
7
  * internationalization to handlebars.js
9
8
  *
10
- * Copyright (c) 2020-24 Florian Walzel, MIT License
9
+ * Copyright (c) 2020-25 Florian Walzel, MIT License
11
10
  *
12
11
  *********************************************************************/
13
12
 
@@ -381,12 +380,22 @@
381
380
  * use like: {{__ "key_name"}}
382
381
  * or with attributes: {{__ "key_with_count" count=7}}
383
382
  *
384
- * @param str
385
- * @param attributes
383
+ * @param key
384
+ * @param options
386
385
  * @returns {*}
387
386
  */
388
- function (str, attributes) {
389
- return new handlebars.SafeString((typeof (i18next) !== 'undefined' ? i18next.t(str, attributes.hash) : str));
387
+ function (key, options) {
388
+ const hash = options.hash || {};
389
+
390
+ // Force object/array return if needed
391
+ const result = (typeof i18next !== "undefined")
392
+ ? i18next.t(key, { ...hash, returnObjects: true })
393
+ : key;
394
+
395
+ if (typeof result === "string") {
396
+ return new handlebars.SafeString(result);
397
+ }
398
+ return result;
390
399
  }
391
400
  );
392
401
  handlebars.registerHelper('_locale',
@@ -502,7 +511,7 @@
502
511
  break;
503
512
  default:
504
513
  throw new Error('@ handlebars-i18n: invalid argument "unit" was given for _dateAdd.' +
505
- 'Unit must be either "second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year".');
514
+ 'Unit must be one of "second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year".');
506
515
  }
507
516
 
508
517
  const dateFormat = new Intl.DateTimeFormat(i18next.language, opts);
@@ -629,4 +638,4 @@
629
638
  }
630
639
  },
631
640
  }
632
- });
641
+ });
@@ -1 +1 @@
1
- !function(e,t){if("object"==typeof exports&&"object"==typeof module){const e=require("handlebars"),r=require("i18next"),n=require("intl"),a=require("relative-time-format");module.exports=t(e,r,n,a,"TEST"===process?.env?.NODE_ENV)}else if("function"==typeof define&&define.amd)define(["Handlebars","i18next","Intl"],t);else{if("object"!=typeof e.Handlebars||"object"!=typeof e.i18next||"object"!=typeof e.Intl)return console.error("@ handlebars-i18n: One or more dependencies are missing. Check for Handlebars, i18next and Intl."),!1;e.HandlebarsI18n=t(e.Handlebars,e.i18next,e.Intl)}}(this,(function(e,t,r,n,a){"use strict";const o={DateTimeFormat:{standard:{},custom:{}},RelativeTimeFormat:{standard:{all:{unit:"hours"}},custom:{}},NumberFormat:{standard:{},custom:{}},PriceFormat:{standard:{all:{style:"currency",currency:"EUR"}},custom:{}}};let i=JSON.parse(JSON.stringify(o));const s={};function u(e,t){let r=[null].concat(t);return new(e.bind.apply(e,r))}function l(e,t,r){if("object"==typeof e&&"object"==typeof e.hash&&Object.keys(e.hash).length>0){let n=e.hash;if(void 0===n.format)return n;if(void 0!==r.custom[n.format]&&void 0!==r.custom[n.format][t])return r.custom[n.format][t]}return void 0!==r.standard[t]?r.standard[t]:void 0!==r.standard.all?r.standard.all:{}}function c(e,t,r,n){return"string"!=typeof e?(console.error("@ handlebars-i18n.configure(): Invalid argument <"+e+'> First argument must be a string with language code such as "en".'),!1):["DateTimeFormat","RelativeTimeFormat","NumberFormat","PriceFormat"].includes(t)?"object"!=typeof r?(console.error("@ handlebars-i18n.configure(): Invalid argument <"+r+"> Third argument must be an object containing the configuration parameters."),!1):(null==n||"string"==typeof n)&&""!==n&&" "!==n||(console.error("@ handlebars-i18n.configure(): Invalid argument <"+n+"> Fourth argument (optional) must be a string naming your custom format configuration."),!1):(console.error("@ handlebars-i18n.configure(): Invalid argument <"+t+'>. Second argument must be a string with the options key. Use either "DateTimeFormat", "RelativeTimeFormat", "NumberFormat", or "PriceFormat".'),!1)}function m(e,t,r,n){return null!=n?(void 0===i[t].custom[n]&&(i[t].custom[n]={}),i[t].custom[n][e]=r):i[t].standard[e]=r,!0}function g(e){return"number"==typeof e||"string"==typeof e&&""!==e}function d(e){let t;if("number"==typeof e)t=new Date(e);else if("string"==typeof e)if("["===e.charAt(0)&&"]"===e.slice(-1)){let r=(e=e.substring(1,e.length-1).replace(/ /g,"")).split(",");t=u.bind(null,Date)(r)}else t="now"===e.toLowerCase()||"today"===e.toLowerCase()?new Date:new Date(e);else t=new Date;return t}function f(e,t){if("function"==typeof r.RelativeTimeFormat)return new r.RelativeTimeFormat(e,t);if(void 0===s[e])try{s[e]=require(`relative-time-format/locale/${e}`)}catch(e){console.error(e)}return n.addLocale(s[e]),new n(e,t)}function b(e,t){return t=t||"hour",Math.trunc(e/{second:1e3,seconds:1e3,minute:6e4,minutes:6e4,hour:36e5,hours:36e5,day:864e5,days:864e5,week:6048e5,weeks:6048e5,month:2629746e3,months:2629746e3,quarter:78894e5,quarters:78894e5,year:315576e5,years:315576e5}[t])}return{configure:function(e,t,r,n){if("string"!=typeof e&&!Array.isArray(e))return console.error("@ handlebars-i18n.configure(): Invalid argument <"+e+'> First argument must be a string with language code such as "en" or an array with language parameters.'),!1;if(Array.isArray(e)){if(e.length<1)return console.log("@ handlebars-i18n.configure(): You passed an empty array, no parameters taken."),!1;e.forEach((e=>{if(!c(e[0],e[1],e[2],e[3]))return!1;m(e[0],e[1],e[2],e[3])}))}else{if(!c(e,t,r,n))return!1;m(e,t,r,n)}return!0},reset:function(){return i=JSON.parse(JSON.stringify(o)),!0},init:function(n,a){return"object"==typeof n&&null!==n?e=n:null!=n&&console.error("@ handlebars-i18n.init(): Invalid Argument [1] given for overrideHndlbrs. Argument must be the Handlebars object. Using generic Handlebars object instead."),"object"==typeof a&&null!==a?t=a:null!=a&&console.error("@ handlebars-i18n.init(): Invalid Argument [2] given for overrideI18n. Argument must be the i18next object. Using generic i18next object on module level instead."),e.registerHelper("__",(function(r,n){return new e.SafeString(void 0!==t?t.t(r,n.hash):r)})),e.registerHelper("_locale",(function(){return t.language})),e.registerHelper("localeIs",(function(e){return t.language===e})),e.registerHelper("_date",(function(e,n){const a=d(e),o=l(n,t.language,i.DateTimeFormat);return new r.DateTimeFormat(t.language,o).format(a)})),e.registerHelper("_dateAdd",(function(e,n,a){if("number"!=typeof e&&"string"!=typeof e)throw new Error('@ handlebars-i18n: invalid first argument "dateInput" was given for _dateAdd.');if("number"!=typeof n)throw new Error('@ handlebars-i18n: invalid second argument "offset" was given for _dateAdd.');const o=d(e),s=l(a,t.language,i.DateTimeFormat);switch(s.unit=s.unit||"hour",s.unit){case"second":case"seconds":o.setSeconds(o.getSeconds()+n);break;case"minute":case"minutes":o.setMinutes(o.getMinutes()+n);break;case"hour":case"hours":o.setHours(o.getHours()+n);break;case"day":case"days":o.setDate(o.getDate()+n);break;case"week":case"weeks":o.setDate(o.getDate()+7*n);break;case"month":case"months":o.setMonth(o.getMonth()+n);break;case"quarter":case"quarters":o.setMonth(o.getMonth()+3*n);break;case"year":case"years":o.setFullYear(o.getFullYear()+n);break;default:throw new Error('@ handlebars-i18n: invalid argument "unit" was given for _dateAdd.Unit must be either "second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year".')}return new r.DateTimeFormat(t.language,s).format(o)})),e.registerHelper("_dateRel",(function(e,r){const n=parseInt(e),a=l(r,t.language,i.RelativeTimeFormat);return f(t.language,a).format(n,a.unit)})),e.registerHelper("_dateDiff",(function(e,r,n){let a,o=l(n,t.language,i.RelativeTimeFormat);if(!g(e))return console.error("@ handlebars-i18n: invalid first argument dateInputA was given for _dateDiff."),null;r=r||"now",a=d(e)-d(r);const s=b(a,o.unit);return f(t.language,o).format(s,o.unit)})),e.registerHelper("_num",(function(e,n){let a=l(n,t.language,i.NumberFormat);return new r.NumberFormat(t.language,a).format(e)})),e.registerHelper("_price",(function(e,n){let a=l(n,t.language,i.PriceFormat);"string"!=typeof a.style&&"string"==typeof a.currency&&(a.style="currency");return new r.NumberFormat(t.language,a).format(e)})),e},...a&&{private:{applyToConstructor:u,configLookup:l,validateArgs:c,setArgs:m,isNumOrString:g,createDateObj:d,getRelDateFormatPolyfill:f,getDateDiff:b}}}}));
1
+ !function(e,t){if("object"==typeof exports&&"object"==typeof module){const e=require("handlebars"),r=require("i18next"),n=require("intl"),a=require("relative-time-format");module.exports=t(e,r,n,a,"TEST"===process?.env?.NODE_ENV)}else if("function"==typeof define&&define.amd)define(["Handlebars","i18next","Intl"],t);else{if("object"!=typeof e.Handlebars||"object"!=typeof e.i18next||"object"!=typeof e.Intl)return console.error("@ handlebars-i18n: One or more dependencies are missing. Check for Handlebars, i18next and Intl."),!1;e.HandlebarsI18n=t(e.Handlebars,e.i18next,e.Intl)}}(this,(function(e,t,r,n,a){"use strict";const o={DateTimeFormat:{standard:{},custom:{}},RelativeTimeFormat:{standard:{all:{unit:"hours"}},custom:{}},NumberFormat:{standard:{},custom:{}},PriceFormat:{standard:{all:{style:"currency",currency:"EUR"}},custom:{}}};let i=JSON.parse(JSON.stringify(o));const s={};function u(e,t){let r=[null].concat(t);return new(e.bind.apply(e,r))}function l(e,t,r){if("object"==typeof e&&"object"==typeof e.hash&&Object.keys(e.hash).length>0){let n=e.hash;if(void 0===n.format)return n;if(void 0!==r.custom[n.format]&&void 0!==r.custom[n.format][t])return r.custom[n.format][t]}return void 0!==r.standard[t]?r.standard[t]:void 0!==r.standard.all?r.standard.all:{}}function c(e,t,r,n){return"string"!=typeof e?(console.error("@ handlebars-i18n.configure(): Invalid argument <"+e+'> First argument must be a string with language code such as "en".'),!1):["DateTimeFormat","RelativeTimeFormat","NumberFormat","PriceFormat"].includes(t)?"object"!=typeof r?(console.error("@ handlebars-i18n.configure(): Invalid argument <"+r+"> Third argument must be an object containing the configuration parameters."),!1):(null==n||"string"==typeof n)&&""!==n&&" "!==n||(console.error("@ handlebars-i18n.configure(): Invalid argument <"+n+"> Fourth argument (optional) must be a string naming your custom format configuration."),!1):(console.error("@ handlebars-i18n.configure(): Invalid argument <"+t+'>. Second argument must be a string with the options key. Use either "DateTimeFormat", "RelativeTimeFormat", "NumberFormat", or "PriceFormat".'),!1)}function m(e,t,r,n){return null!=n?(void 0===i[t].custom[n]&&(i[t].custom[n]={}),i[t].custom[n][e]=r):i[t].standard[e]=r,!0}function g(e){return"number"==typeof e||"string"==typeof e&&""!==e}function f(e){let t;if("number"==typeof e)t=new Date(e);else if("string"==typeof e)if("["===e.charAt(0)&&"]"===e.slice(-1)){let r=(e=e.substring(1,e.length-1).replace(/ /g,"")).split(",");t=u.bind(null,Date)(r)}else t="now"===e.toLowerCase()||"today"===e.toLowerCase()?new Date:new Date(e);else t=new Date;return t}function d(e,t){if("function"==typeof r.RelativeTimeFormat)return new r.RelativeTimeFormat(e,t);if(void 0===s[e])try{s[e]=require(`relative-time-format/locale/${e}`)}catch(e){console.error(e)}return n.addLocale(s[e]),new n(e,t)}function b(e,t){return t=t||"hour",Math.trunc(e/{second:1e3,seconds:1e3,minute:6e4,minutes:6e4,hour:36e5,hours:36e5,day:864e5,days:864e5,week:6048e5,weeks:6048e5,month:2629746e3,months:2629746e3,quarter:78894e5,quarters:78894e5,year:315576e5,years:315576e5}[t])}return{configure:function(e,t,r,n){if("string"!=typeof e&&!Array.isArray(e))return console.error("@ handlebars-i18n.configure(): Invalid argument <"+e+'> First argument must be a string with language code such as "en" or an array with language parameters.'),!1;if(Array.isArray(e)){if(e.length<1)return console.log("@ handlebars-i18n.configure(): You passed an empty array, no parameters taken."),!1;e.forEach((e=>{if(!c(e[0],e[1],e[2],e[3]))return!1;m(e[0],e[1],e[2],e[3])}))}else{if(!c(e,t,r,n))return!1;m(e,t,r,n)}return!0},reset:function(){return i=JSON.parse(JSON.stringify(o)),!0},init:function(n,a){return"object"==typeof n&&null!==n?e=n:null!=n&&console.error("@ handlebars-i18n.init(): Invalid Argument [1] given for overrideHndlbrs. Argument must be the Handlebars object. Using generic Handlebars object instead."),"object"==typeof a&&null!==a?t=a:null!=a&&console.error("@ handlebars-i18n.init(): Invalid Argument [2] given for overrideI18n. Argument must be the i18next object. Using generic i18next object on module level instead."),e.registerHelper("__",(function(r,n){const a=n.hash||{},o=void 0!==t?t.t(r,{...a,returnObjects:!0}):r;return"string"==typeof o?new e.SafeString(o):o})),e.registerHelper("_locale",(function(){return t.language})),e.registerHelper("localeIs",(function(e){return t.language===e})),e.registerHelper("_date",(function(e,n){const a=f(e),o=l(n,t.language,i.DateTimeFormat);return new r.DateTimeFormat(t.language,o).format(a)})),e.registerHelper("_dateAdd",(function(e,n,a){if("number"!=typeof e&&"string"!=typeof e)throw new Error('@ handlebars-i18n: invalid first argument "dateInput" was given for _dateAdd.');if("number"!=typeof n)throw new Error('@ handlebars-i18n: invalid second argument "offset" was given for _dateAdd.');const o=f(e),s=l(a,t.language,i.DateTimeFormat);switch(s.unit=s.unit||"hour",s.unit){case"second":case"seconds":o.setSeconds(o.getSeconds()+n);break;case"minute":case"minutes":o.setMinutes(o.getMinutes()+n);break;case"hour":case"hours":o.setHours(o.getHours()+n);break;case"day":case"days":o.setDate(o.getDate()+n);break;case"week":case"weeks":o.setDate(o.getDate()+7*n);break;case"month":case"months":o.setMonth(o.getMonth()+n);break;case"quarter":case"quarters":o.setMonth(o.getMonth()+3*n);break;case"year":case"years":o.setFullYear(o.getFullYear()+n);break;default:throw new Error('@ handlebars-i18n: invalid argument "unit" was given for _dateAdd.Unit must be one of "second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year".')}return new r.DateTimeFormat(t.language,s).format(o)})),e.registerHelper("_dateRel",(function(e,r){const n=parseInt(e),a=l(r,t.language,i.RelativeTimeFormat);return d(t.language,a).format(n,a.unit)})),e.registerHelper("_dateDiff",(function(e,r,n){let a,o=l(n,t.language,i.RelativeTimeFormat);if(!g(e))return console.error("@ handlebars-i18n: invalid first argument dateInputA was given for _dateDiff."),null;r=r||"now",a=f(e)-f(r);const s=b(a,o.unit);return d(t.language,o).format(s,o.unit)})),e.registerHelper("_num",(function(e,n){let a=l(n,t.language,i.NumberFormat);return new r.NumberFormat(t.language,a).format(e)})),e.registerHelper("_price",(function(e,n){let a=l(n,t.language,i.PriceFormat);"string"!=typeof a.style&&"string"==typeof a.currency&&(a.style="currency");return new r.NumberFormat(t.language,a).format(e)})),e},...a&&{private:{applyToConstructor:u,configLookup:l,validateArgs:c,setArgs:m,isNumOrString:g,createDateObj:f,getRelDateFormatPolyfill:d,getDateDiff:b}}}}));
@@ -53,7 +53,12 @@
53
53
  'key2': '{{what}} is good.',
54
54
  'key3WithCount': '{{count}} item',
55
55
  'key3WithCount_plural': '{{count}} items',
56
- 'key4': 'Selected Language is:'
56
+ 'key4': 'Selected Language is:',
57
+ 'fruits': ['Apple', 'Banana', 'Cherry'],
58
+ 'steps': [
59
+ { 'title': 'Step 1', 'text': 'Open the app' },
60
+ { 'title': 'Step 2', text: 'Click start' }
61
+ ]
57
62
  }
58
63
  },
59
64
  'de' : {
@@ -63,12 +68,18 @@
63
68
  'key2': '{{what}} ist gut.',
64
69
  'key3WithCount': '{{count}} Gegenstand',
65
70
  'key3WithCount_plural': '{{count}} Gegenstände',
66
- 'key4': 'Die ausgewählte Sprache ist:'
71
+ 'key4': 'Die ausgewählte Sprache ist:',
72
+ 'fruits': ['Apfel', 'Banane', 'Kirsche'],
73
+ 'steps': [
74
+ { 'title': 'Schritt 1', 'text': 'App öffnen' },
75
+ { 'title': 'Schritt 2', 'text': 'Klicke auf Start' }
76
+ ]
67
77
  }
68
78
  }
69
79
  },
70
80
  lng : 'en',
71
- compatibilityJSON: 'v2'
81
+ compatibilityJSON: 'v2', // important: defines the JSON standard for variable replacement
82
+ returnObjects: true // set to true if you want to loop over an array or objects of properties
72
83
  });
73
84
 
74
85
  // -- Handlebars' example data object
@@ -130,6 +141,16 @@
130
141
  <code>{{{{raw}}}} {{__ "key1" lng="de"}} {{{{/raw}}}}</code>
131
142
  <p>&#x2192; {{__ "key1" lng="de"}}</p>
132
143
 
144
+ <h4>Loop over Array of Translations:</h4>
145
+ <code>
146
+ {{{{raw}}}} {{#each (__ "fruits")}} {{this}} {{/each}} {{{{/raw}}}}
147
+ </code>
148
+ <p><ul>
149
+ {{#each (__ "fruits")}}
150
+ <li>{{this}}</li>
151
+ {{/each}}
152
+ </ul></p>
153
+
133
154
  <h3>Output selected language</h3>
134
155
 
135
156
  <button onclick="changeLang()">{{__ "key0"}} {{#if (localeIs "en")}}German {{else}}Englisch {{/if}}</button>
@@ -267,4 +288,4 @@
267
288
 
268
289
  </script>
269
290
  </body>
270
- </html>
291
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handlebars-i18n",
3
- "version": "1.8.3",
3
+ "version": "1.9.0",
4
4
  "description": "handlebars-i18n adds internationalization to handlebars.js using i18next and Intl.",
5
5
  "main": "dist/handlebars-i18n.js",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -1,11 +1,8 @@
1
1
  # handlebars-i18n
2
2
 
3
- `handlebars-i18n` adds the internationalization features of [i18next](https://www.i18next.com/)
3
+ *What it is about:* `handlebars-i18n` adds the translation features of [i18next](https://www.i18next.com/)
4
4
  to [handlebars.js](https://handlebarsjs.com/). It also provides **date**, **number**, and **currency formatting**
5
- via [Intl](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Intl). Use as node module or in
6
- the web browser. Supports Typescript.
7
-
8
- Handlebars-i18n is listed amongst i18next’s [framework helpers](https://www.i18next.com/overview/supported-frameworks).
5
+ via [Intl](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Intl). Use as node module or in the web browser. handlebars-i18n is listed amongst i18next’s official [framework helpers](https://www.i18next.com/overview/supported-frameworks).
9
6
 
10
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
11
8
  ![Node.js version](https://img.shields.io/badge/node-%3E%3D14-brightgreen)
@@ -17,16 +14,22 @@ Handlebars-i18n is listed amongst i18next’s [framework helpers](https://www.i1
17
14
  ![npm](https://img.shields.io/npm/dm/handlebars-i18n)
18
15
  ![GitHub stars](https://img.shields.io/github/stars/fwalzel/handlebars-i18n?style=social)
19
16
 
20
- ## License
21
17
 
22
- Copyright (c) 2020–25 Florian Walzel,
18
+ ## Key Features & Advantages
19
+
20
+ - handlebars-i18n comes lightweight, well tested, and with detailed [examples](#-detailed-examples)
21
+ - allows granular custom [presets](#generic-language-format-settings) per language
22
+ - supports Typescript
23
+ - has an optional [CLI](#additional-cli-helper-for-handlebars-i18n-available) for automatic Translations via DeepL
24
+
23
25
 
24
- MIT License
26
+ ## Please Support
25
27
 
26
- If you use handlebars-i18n in a professional context, you could
28
+ `handlebars-i18n` is free but not free of value. If you make serious use of handlebars-i18n, I’d be delighted if you
27
29
 
28
30
  [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/fwalzel)
29
31
 
32
+
30
33
  ## Install
31
34
 
32
35
  ```sh
@@ -35,17 +38,17 @@ npm i handlebars-i18n
35
38
 
36
39
  ## Import
37
40
 
38
- Import as commonJS within node environment:
41
+ Import with ES6 import syntax:
39
42
 
40
- ```javascript
41
- const HandlebarsI18n = require("handlebars-i18n");
43
+ ```typescript
44
+ import HandlebarsI18n from "handlebars-i18n";
42
45
  HandlebarsI18n.init();
43
46
  ```
44
47
 
45
- With ES6 import syntax:
48
+ As commonJS within node environment:
46
49
 
47
- ```typescript
48
- import * as HandlebarsI18n from "handlebars-i18n";
50
+ ```javascript
51
+ const HandlebarsI18n = require("handlebars-i18n");
49
52
  HandlebarsI18n.init();
50
53
  ```
51
54
 
@@ -61,12 +64,12 @@ Usage in web browser (old school):
61
64
  </script>
62
65
  ```
63
66
 
64
- Via jsDelivr CDN:
67
+ Via CDN:
65
68
  ```javascript
66
- <script src="https://cdn.jsdelivr.net/npm/handlebars-i18n@1.8.3/dist/handlebars-i18n.min.js"></script>
69
+ <script src="https://cdn.jsdelivr.net/npm/handlebars-i18n@1.9.0/dist/handlebars-i18n.min.js"></script>
67
70
  ```
68
71
 
69
- ## Quick example
72
+ ## Quick Example
70
73
 
71
74
  Initialize i18next with your language strings and default settings:
72
75
 
@@ -88,7 +91,8 @@ i18next.init({
88
91
  }
89
92
  }
90
93
  },
91
- lng: "en"
94
+ lng: "en",
95
+ compatibilityJSON: 'v2'
92
96
  });
93
97
  ```
94
98
 
@@ -100,7 +104,6 @@ let data = {
100
104
  myPrice: 1200.99,
101
105
  myDate: "2020-03-11T03:24:00"
102
106
  }
103
-
104
107
  ```
105
108
 
106
109
  Initialize handlebars-i18n:
@@ -120,66 +123,66 @@ HandlebarsI18n.configure([
120
123
 
121
124
  Finally use in template:
122
125
 
123
- ```
126
+ ```hbs
124
127
  <p> {{__ "phrase1"}} </p>
125
128
  ```
126
129
 
127
- * returns for "en" &#x2192; **What is good?**
130
+ * output: en **What is good?** | de → **Was ist gut?**
128
131
 
129
- ```
132
+ ```hbs
130
133
  <p> {{__ "phrase2" thing=myItem}} </p>
131
134
  ```
132
135
 
133
- * returns for "en" &#x2192; **handlebars-i18n is good.**
136
+ * output: en **handlebars-i18n is good.** | de → **handlebars-i18n ist gut.**
134
137
 
135
- ```
138
+ ```hbs
136
139
  <p> {{_date myDate}} </p>
137
140
  ```
138
141
 
139
- * returns for "en" &#x2192; **March 11, 2020, 4:24 AM**
142
+ * output: en **March 11, 2020 at 3:24 AM** | de → **11.3.2020, 03:24**
140
143
 
141
- ```
144
+ ```hbs
142
145
  <p> {{_price myPrice}} </p>
143
146
  ```
144
147
 
145
- * returns for "en" &#x2192; **$1,200.99**
148
+ * output: en **\$1,200.99** | de → **1.200,99 $**
146
149
 
147
- ## Detailed examples
150
+ ## Detailed Examples
148
151
 
149
152
  :point_right: See the *examples folder* in the repo for more use cases and details.
150
153
 
151
- - Open `examples/browser-example/index.html` in your Web browser to see an implementation with a simple UI.
152
- - Run `npm run example:js` in the console to get a very basic node example logged.
153
- - Run `npm run example:ts` to compile and log a typescript example.
154
+ - Open `examples/browser-example/index.html` in your web browser to see an implementation with a simple UI.
155
+ - Prompt `npm run example:js` from the root of this repo to log a very basic node example to console.
156
+ - Prompt `npm run example:ts` to compile and log a typescript example.
154
157
 
155
- ## Additional CLI Helper for Handlebars-i18n available :metal:
158
+ ## Additional CLI Helper for Handlebars-i18n available
156
159
 
157
- Handlebars-i18n has its own command line
160
+ :metal: Handlebars-i18n has its own command line
158
161
  interface [handlebars-i18n-cli](https://www.npmjs.com/package/handlebars-i18n-cli).
159
162
 
160
163
  ```sh
161
164
  npm i handlebars-i18n-cli --save-dev
162
165
  ```
163
166
 
164
- Automatically extract translation strings from handlebars templates and generate i18next conform json files from it.
165
- Handlebars-i18n-cli also helps to keep[](https://) your translations up to date when changes are made in the templates
166
- over time.
167
+ * programmatically extract/ update translation strings from handlebars templates and generate i18next conform
168
+ JSON files from it
169
+ * automatic translation of i18next JSON via [DeepL’s](https://www.deepl.com/en/pro-api/) free API
167
170
 
168
- ## API
171
+ ## Public Functions
169
172
 
170
- ### __
173
+ ### `__`
171
174
 
172
- Returns the phrase associated with the given key for the selected language. __ will take all options
175
+ Returns the phrase associated with the given key for the selected language. `__` will take all options
173
176
  i18next’s [t-function](https://www.i18next.com/overview/api#t) would take.
174
177
  The primary key can be passed hard encoded in the template when written in quotes:
175
178
 
176
- ```
179
+ ```hbs
177
180
  {{__ "keyToTranslationPhrase"}}
178
181
  ```
179
182
 
180
183
  … or it can be referenced via a handlebars variable:
181
184
 
182
- ```
185
+ ```hbs
183
186
  {{__ keyFromHandlebarsData}}
184
187
  ```
185
188
 
@@ -187,7 +190,7 @@ The primary key can be passed hard encoded in the template when written in quote
187
190
 
188
191
  Template usage:
189
192
 
190
- ```
193
+ ```hbs
191
194
  {{__ "whatIsWhat" a="Everything" b="fine"}}
192
195
  ```
193
196
 
@@ -203,7 +206,7 @@ The i18next resource:
203
206
 
204
207
  **Plurals**
205
208
 
206
- ```
209
+ ```hbs
207
210
  {{__ "keyWithCount" count=8}}
208
211
  ```
209
212
 
@@ -213,74 +216,99 @@ The i18next resource:
213
216
  "keyWithCount" : "{{count}} item",
214
217
  "keyWithCount_plural" : "{{count}} items"
215
218
  }
216
- },
219
+ }, ...
217
220
  ```
218
221
 
219
- **Override globally selected language**
222
+ **Override the globally selected language**
220
223
 
221
- ```
224
+ ```hbs
222
225
  {{__ "key1" lng="de"}}
223
226
  ```
224
227
 
225
- Will output the contents for "**de**" even though other language is selected.
228
+ Will output the contents for `de` even though a different language is globally set.
229
+
230
+ **Looping over an array (or object) of translations**
231
+
232
+ ```hbs
233
+ <ul>
234
+ {{#each (__ "fruits")}}
235
+ <li>{{this}}</li>
236
+ {{/each}}
237
+ </ul>
238
+ ```
239
+ In this case the key `fruits` would contain an array of translation strings, like:
240
+
241
+ ```javascript
242
+ {
243
+ en: {
244
+ translation: {
245
+ fruits: ["Apple", "Banana", "Cherry"]
246
+ }
247
+ },
248
+ returnObjects: true
249
+ }
250
+ ```
251
+
252
+ It is recommended to set `returnObjects` actively to `true` in the `i18next.init` object if you want to loop over
253
+ an array or objects of properties.
226
254
 
227
255
  ---
228
256
 
229
- ### _locale
257
+ ### `_locale`
230
258
 
231
- Returns the shortcode of i18next’s currently selected language such as "**en**", "**de**", "**fi**", "**ja**" … etc.
259
+ Returns the shortcode of i18next’s currently selected language such as `en`, `de`, `ja` … etc.
232
260
 
233
- ```
261
+ ```hbs
234
262
  {{_locale}}
235
263
  ```
236
264
 
237
265
  ---
238
266
 
239
- ### localeIs
267
+ ### `localeIs`
240
268
 
241
269
  Checks a string against i18next’s currently selected language. Returns **true** or **false**.
242
270
 
243
- ```
271
+ ```hbs
244
272
  {{#if (localeIs "en")}} ... {{/if}}
245
273
  ```
246
274
 
247
275
  ---
248
276
 
249
- ### _date
277
+ ### `_date`
250
278
 
251
279
  Outputs a formatted date according to the language specific conventions.
252
280
 
253
- ```
281
+ ```hbs
254
282
  {{_date}}
255
283
  ```
256
284
 
257
285
  If called without argument the current date is returned. Any other input date can be passed as a conventional date
258
- string, a number (timestamp in milliseconds), or a date array. _date accepts all arguments
286
+ string, a number (timestamp in milliseconds), or a date array. `_date` accepts all arguments
259
287
  Javascript’s [new Date()](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Date)
260
288
  constructor would accept.
261
289
 
262
290
  **Date argument given as date string:**
263
291
 
264
- ```
292
+ ```hbs
265
293
  {{_date "2020-03-11T03:24:00"}}
266
294
  ```
267
295
 
268
296
  or
269
297
 
270
- ```
298
+ ```hbs
271
299
  {{_date "December 17, 1995 03:24:00"}}
272
300
  ```
273
301
 
274
302
  **Date argument given as number (milliseconds since begin of unix epoch):**
275
303
 
276
- ```
304
+ ```hbs
277
305
  {{_date 1583922952743}}
278
306
  ```
279
307
 
280
308
  **Date argument given as javascript date array** [year, monthIndex [, day [, hour [, minutes [,
281
309
  seconds [, milliseconds]]]]]]:
282
310
 
283
- ```
311
+ ```hbs
284
312
  {{_date "[2012, 11, 20, 3, 0, 0]"}}
285
313
  ```
286
314
 
@@ -291,60 +319,59 @@ See [Intl DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScrip
291
319
  for your option arguments. Alternatively check this repo’s TS types
292
320
  in [handlebars-i18n.d.ts](./dist/handlebars-i18n.d.ts).
293
321
 
294
- ```
322
+ ```hbs
295
323
  {{_date 1583922952743 year="2-digit" day="2-digit" timeZone="America/Los_Angeles"}}
296
324
  ```
297
325
 
298
326
  ---
299
327
 
300
- ### _dateAdd :tada: new in 1.8
328
+ ### `_dateAdd` :tada: new in 1.8
301
329
 
302
330
  Adds a time offset in a given unit to a date, returns the modified date.
303
331
 
304
- ```
332
+ ```hbs
305
333
  {{_dateAdd "1996-12-17" 24 unit="hour"}}
306
334
  ```
307
335
 
308
- Will output for "en" &#x2192; **12/18/1996**
336
+ * output: en **12/18/1996**
309
337
 
310
- The first argument is a date (see function **_date** for valid date inputs). The second argument is a time amount given
338
+ The first argument is a date (see function `_date` for valid date inputs). The second argument is a time amount given
311
339
  as number. The option **unit** specifies the time amount. Possible units
312
340
  are `"second"` | `"minute"` | `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` |`"year"` (default is `"hour"`).
313
- Further options as for function **_date** can be applied.
341
+ Further options as for function `_date` can be applied.
314
342
 
315
343
  ---
316
344
 
317
- ### _dateDiff
345
+ ### `_dateDiff`
318
346
 
319
347
  Outputs the relative time difference between two given dates.
320
348
 
349
+ ```hbs
350
+ {{_dateDiff "2000-12-17" "2001-12-17" unit="year"}}
321
351
  ```
322
- {{_dateDiff "1996-12-17T00:00:00" "1995-12-17T00:00:00" unit="year"}}
323
- ```
324
-
325
- Will output for "en" &#x2192; **in 1 year**
352
+ * output: en → **in 1 year**
326
353
 
327
354
  The second date argument is subtracted from the first. If the difference is a positive value, a future event statement
328
355
  is made. A negative value refers to a past date. (If no second argument is given, the default date is the present moment).
329
- Allowed date input formats are similar to **_date**, options equal **_dateRel**. Default unit is `"hour"`.
356
+ Allowed date input formats are similar to `_date`, options equal `_dateRel`. Default unit is `"hour"`.
330
357
 
331
358
  ---
332
359
 
333
- ### _dateRel
360
+ ### `_dateRel`
334
361
 
335
362
  Outputs a string with a relative date statement, formatted according to the language specific conventions.
336
363
 
337
- ```
364
+ ```hbs
338
365
  {{_dateRel 7 unit="hour"}}
339
366
  ```
340
367
 
341
- Will output for "en" &#x2192; **in 7 hours**
368
+ * output: en **in 7 hours**
342
369
 
343
- ```
370
+ ```hbs
344
371
  {{_dateRel -7 unit="hour"}}
345
372
  ```
346
373
 
347
- Will output for "en" &#x2192; **7 hours ago**
374
+ * output: en **7 hours ago**
348
375
 
349
376
  A positive number argument leads to a future event statement, a negative refers to a past date. Possible units
350
377
  are `"second"` | `"minute"` | `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` |`"year"` (default is `"hour"`).
@@ -354,12 +381,11 @@ Alternatively check this repo’s TS types in [handlebars-i18n.d.ts](./dist/hand
354
381
 
355
382
  ---
356
383
 
357
- ### _num
384
+ ### `_num`
358
385
 
359
- Outputs a formatted number according to the language specific conventions of number representation, e.g.
360
- **4,100,000.8314** for "**en**", but **4.100.000,8314** for "**de**".
386
+ Outputs a formatted number according to the language specific conventions of number representation.
361
387
 
362
- ```
388
+ ```hbs
363
389
  {{_num 4100000.8314 }}
364
390
  ```
365
391
 
@@ -370,20 +396,19 @@ See [Intl NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/
370
396
  for your option arguments. Alternatively check this repo’s TS types
371
397
  in [handlebars-i18n.d.ts](./dist/handlebars-i18n.d.ts).
372
398
 
373
- ```
399
+ ```hbs
374
400
  {{_num 3.14159 maximumFractionDigits=2}}
375
401
  ```
376
402
 
377
- Will output **3.14** for "**en**", but **3,14** for "**de**".
403
+ * output: en → **3.14** | de **3,14**
378
404
 
379
405
  ---
380
406
 
381
- ### _price
407
+ ### `_price`
382
408
 
383
- Outputs a formatted currency string according to the language specific conventions of price representation, e.g.
384
- **€9,999.99** for "**en**", but **9.999,99 €** for "**de**".
409
+ Outputs a formatted currency string according to the language specific conventions of price representation.
385
410
 
386
- ```
411
+ ```hbs
387
412
  {{_price 9999.99}}
388
413
  ```
389
414
 
@@ -394,13 +419,13 @@ See [Intl NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/
394
419
  for your option arguments. Alternatively check this repo’s TS types
395
420
  in [handlebars-i18n.d.ts](./dist/handlebars-i18n.d.ts).
396
421
 
397
- ```
422
+ ```hbs
398
423
  {{_price 1000 currency="JPY" minimumFractionDigits=2}}
399
424
  ```
400
425
 
401
426
  ---
402
427
 
403
- ## How to use HandlebarsI18n.configure method
428
+ ## How to use `HandlebarsI18n.configure` method
404
429
 
405
430
  ### Generic language format settings
406
431
 
@@ -411,7 +436,7 @@ all languages or only for specific languages.
411
436
  HandlebarsI18n.configure("all", "DateTimeFormat", {timeZone: "America/Los_Angeles"});
412
437
  ```
413
438
 
414
- First argument is the language shortcode or "**all**" for all languages. Second is the format option you want to
439
+ First argument is the language shortcode or `"all"` for all languages. Second is the format option you want to
415
440
  address (`DateTimeFormat`, `RelativeTimeFormat`, `NumberFormat`, or `PriceFormat`). Third argument is the options object
416
441
  with the specific settings.
417
442
 
@@ -433,9 +458,9 @@ HandlebarsI18n.configure("all", "PriceFormat", {currency: "HKD", currencyDisplay
433
458
 
434
459
  You can also define specific subsets to be used in the template, i.e. if you want the date in different formats such as:
435
460
 
436
- - **2020** (year-only)
437
- - **11.3.2020** (standard-date)
438
- - **7:24:02** (time-only)
461
+ - **2020** (`year-only`)
462
+ - **11.3.2020** (`standard-date`)
463
+ - **7:24:02** (`time-only`)
439
464
 
440
465
  To do this, define a 4th parameter with a custom name:
441
466
 
@@ -447,9 +472,9 @@ HandlebarsI18n.configure([
447
472
  ]);
448
473
  ```
449
474
 
450
- Call a subset in template with the parameter format="custom-name", like:
475
+ Call a subset in template with the parameter `format="custom-name"`, like:
451
476
 
452
- ```
477
+ ```hbs
453
478
  {{_date myDate format="year-only"}}
454
479
  ```
455
480
 
@@ -507,9 +532,9 @@ HandlebarsModified.registerHelper("foo", function () {
507
532
  HandlebarsI18n.init(HandlebarsModified);
508
533
  ```
509
534
 
510
- HandlebarsI18n will have your previously defined method **foo()** by now.
535
+ HandlebarsI18n will have your previously defined method `foo` by now.
511
536
 
512
- The same can be done for a custom instance of i18next. Pass it as the second argument to the init function.
537
+ The same can be done for a custom instance of i18next. Pass it as the second argument to the `init` function.
513
538
 
514
539
  ```javascript
515
540
  const i18nextCustom = require("i18next");
@@ -523,12 +548,20 @@ HandlebarsI18n.init(null, i18nextCustom);
523
548
  npm test
524
549
  ```
525
550
 
551
+ ## License
552
+
553
+ MIT License, Copyright (c) 2020–25 Florian Walzel
554
+
555
+ ## Contributing
556
+
557
+ Contributions are welcome! Please open an issue or submit a pull request on GitHub.
558
+
526
559
  ## Merci à vous
527
560
 
528
561
  For your contribution, I would like to
529
562
  thank [@MickL](https://github.com/MickL), [@dargmuesli](https://github.com/dargmuesli), and [@DiefBell](DiefBell).
530
563
 
531
- ## Note
564
+ ## Note on alternatives
532
565
 
533
566
  There is a *different* package named [handlebars-i18next](https://www.npmjs.com/package/handlebars-i18next)
534
567
  by [@jgonggrijp](https://github.com/jgonggrijp) which might also suit your needs. Cheers!
@@ -19,13 +19,23 @@ describe('handlebars-i18n Tests', function () {
19
19
  'en': {
20
20
  translation: {
21
21
  'key1': 'What is good?',
22
- 'key2': '{{what}} is {{adverb}}.'
22
+ 'key2': '{{what}} is {{adverb}}.',
23
+ 'fruits': ["Apple", "Banana", "Cherry"],
24
+ 'steps': [
25
+ { 'title': 'Step 1', 'text': 'Open the app' },
26
+ { 'title': 'Step 2', text: 'Click start' }
27
+ ]
23
28
  }
24
29
  },
25
30
  'de': {
26
31
  translation: {
27
32
  'key1': 'Was ist gut?',
28
- 'key2': '{{what}} ist {{adverb}}.'
33
+ 'key2': '{{what}} ist {{adverb}}.',
34
+ 'fruits': ["Apfel", "Banane", "Kirsche"],
35
+ 'steps': [
36
+ { 'title': 'Schritt 1', 'text': 'App öffnen' },
37
+ { 'title': 'Schritt 2', 'text': 'Klicke auf Start' }
38
+ ]
29
39
  }
30
40
  }
31
41
  },
@@ -177,6 +187,37 @@ describe('handlebars-i18n Tests', function () {
177
187
  assert.equal("handlebarsI18next ist gut.", res.string);
178
188
  });
179
189
 
190
+ it("__ should loop over array of strings with #each", () => {
191
+ i18next.changeLanguage('en');
192
+ const template = Handlebars.compile(`
193
+ <ul>
194
+ {{#each (__ "fruits")}}
195
+ <li>{{this}}</li>
196
+ {{/each}}
197
+ </ul>
198
+ `);
199
+
200
+ const output = template({});
201
+ console.log(output);
202
+ expect(output).to.contain("Apple");
203
+ expect(output).to.contain("Banana");
204
+ expect(output).to.contain("Cherry");
205
+ });
206
+
207
+ it("__ should loop over array of objects with #each", () => {
208
+ const template = Handlebars.compile(`
209
+ <ol>
210
+ {{#each (__ "steps")}}
211
+ <li><strong>{{title}}</strong>: {{text}}</li>
212
+ {{/each}}
213
+ </ol>
214
+ `);
215
+
216
+ const output = template({});
217
+ expect(output).to.contain("<strong>Step 1</strong>: Open the app");
218
+ expect(output).to.contain("<strong>Step 2</strong>: Click start");
219
+ });
220
+
180
221
 
181
222
  /****************************************
182
223
  Tests against function _date
@@ -1165,4 +1206,4 @@ describe('handlebars-i18n Private helper Function Tests (in production not expor
1165
1206
  expect(result).to.deep.equal({});
1166
1207
  });
1167
1208
 
1168
- });
1209
+ });