handlebars-i18n 1.9.0 → 1.10.1

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.
@@ -1,10 +1,7 @@
1
- name: CI
1
+ name: CI Build (manual)
2
2
 
3
3
  on:
4
- pull_request:
5
- push:
6
- release:
7
- types: [published]
4
+ workflow_dispatch:
8
5
 
9
6
  env:
10
7
  IMAGE_ID: ${{ github.repository }}
@@ -1,4 +1,4 @@
1
- name: Test and Coverage
1
+ name: CI Test and Coverage
2
2
 
3
3
  on:
4
4
  push:
@@ -13,10 +13,10 @@ jobs:
13
13
 
14
14
  - uses: actions/checkout@v3
15
15
 
16
- - name: Use Node.js 16.x
16
+ - name: Use Node.js 22.x
17
17
  uses: actions/setup-node@v3
18
18
  with:
19
- node-version: 16.x
19
+ node-version: 22.x
20
20
 
21
21
  - name: npm install, make test-coverage
22
22
  run: |
@@ -1,7 +1,7 @@
1
1
  # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
2
  # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
3
 
4
- name: Node.js CI
4
+ name: CI Build 16, 18, 20
5
5
 
6
6
  on:
7
7
  push:
@@ -16,15 +16,15 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- node-version: [14.x, 16.x, 18.x]
19
+ node-version: [ 16.x, 18.x, 20.x ]
20
20
  # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v3
24
- - name: Use Node.js ${{ matrix.node-version }}
25
- uses: actions/setup-node@v3
26
- with:
27
- node-version: ${{ matrix.node-version }}
28
- cache: 'npm'
29
- - run: npm ci
30
- - run: npm run build --if-present
23
+ - uses: actions/checkout@v3
24
+ - name: Use Node.js ${{ matrix.node-version }}
25
+ uses: actions/setup-node@v3
26
+ with:
27
+ node-version: ${{ matrix.node-version }}
28
+ cache: 'npm'
29
+ - run: npm ci
30
+ - run: npm run build --if-present
@@ -389,7 +389,7 @@
389
389
 
390
390
  // Force object/array return if needed
391
391
  const result = (typeof i18next !== "undefined")
392
- ? i18next.t(key, { ...hash, returnObjects: true })
392
+ ? i18next.t(key, {...hash, returnObjects: true})
393
393
  : key;
394
394
 
395
395
  if (typeof result === "string") {
@@ -398,6 +398,18 @@
398
398
  return result;
399
399
  }
400
400
  );
401
+ handlebars.registerHelper('keyExists',
402
+ /**
403
+ * checks if a translation key exists
404
+ * use like: {{#if (keyExists "myKey")}} {{__ "myKey"}} {{/if}}
405
+ *
406
+ * @param {string} key - The translation key to check
407
+ * @returns {boolean}
408
+ */
409
+ function (key) {
410
+ return i18next.exists(key);
411
+ }
412
+ );
401
413
  handlebars.registerHelper('_locale',
402
414
  /**
403
415
  * echos the current language
@@ -543,6 +555,7 @@
543
555
  );
544
556
  handlebars.registerHelper('_dateDiff',
545
557
  /**
558
+ * Outputs the relative time difference between two given dates in the requested unit.
546
559
  *
547
560
  * @param dateInputA
548
561
  * @param dateInputB
@@ -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 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}}}}));
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("keyExists",(function(e){return t.exists(e)})),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}}}}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handlebars-i18n",
3
- "version": "1.9.0",
3
+ "version": "1.10.1",
4
4
  "description": "handlebars-i18n adds internationalization to handlebars.js using i18next and Intl.",
5
5
  "main": "dist/handlebars-i18n.js",
6
6
  "scripts": {
@@ -42,26 +42,27 @@
42
42
  "intl": "^1.2.5"
43
43
  },
44
44
  "devDependencies": {
45
- "@commitlint/cli": "17.6.1",
46
- "@commitlint/config-conventional": "16.2.1",
45
+ "@commitlint/cli": "20.2.0",
46
+ "@commitlint/config-conventional": "20.2.0",
47
47
  "@types/intl": "^1.2.2",
48
- "@types/node": "^24.5.2",
48
+ "@types/node": "^24.10.1",
49
49
  "c8": "^10.1.3",
50
- "chai": "4.3.6",
51
- "coveralls-next": "^4.2.1",
52
- "gulp": "5.0.0",
53
- "gulp-rename": "2.0.0",
50
+ "chai": "6.2.1",
51
+ "coveralls-next": "^6.0.1",
52
+ "glob": "^13.0.0",
53
+ "gulp": "5.0.1",
54
+ "gulp-rename": "2.1.0",
54
55
  "gulp-uglify": "3.0.2",
55
56
  "gulp-uglify-es": "3.0.0",
56
57
  "html-entities": "^2.6.0",
57
- "mocha": "10.7.3",
58
+ "mocha": "11.7.5",
58
59
  "mocha-lcov-reporter": "^1.3.0",
59
- "nyc": "15.1.0",
60
- "typescript": "^4.9.5"
60
+ "nyc": "17.1.0",
61
+ "typescript": "^5.9.3"
61
62
  },
62
63
  "dependencies": {
63
64
  "handlebars": "^4.7.8",
64
- "i18next": "^21.10.0",
65
+ "i18next": "^25.7.1",
65
66
  "intl": "^1.2.5",
66
67
  "relative-time-format": "^1.1.11"
67
68
  },
package/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
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 the web browser. handlebars-i18n is listed amongst i18next’s official [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).
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
8
  ![Node.js version](https://img.shields.io/badge/node-%3E%3D14-brightgreen)
@@ -17,7 +17,7 @@ via [Intl](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global
17
17
 
18
18
  ## Key Features & Advantages
19
19
 
20
- - handlebars-i18n comes lightweight, well tested, and with detailed [examples](#-detailed-examples)
20
+ - handlebars-i18n comes lightweight, well tested, and with detailed [examples](#detailed-examples)
21
21
  - allows granular custom [presets](#generic-language-format-settings) per language
22
22
  - supports Typescript
23
23
  - has an optional [CLI](#additional-cli-helper-for-handlebars-i18n-available) for automatic Translations via DeepL
@@ -25,7 +25,7 @@ via [Intl](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global
25
25
 
26
26
  ## Please Support
27
27
 
28
- `handlebars-i18n` is free but not free of value. If you make serious use of handlebars-i18n, I’d be delighted if you
28
+ `handlebars-i18n` is free but not free of value. If you make serious use of handlebars-i18n, I’d be delighted if you
29
29
 
30
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)
31
31
 
@@ -38,7 +38,7 @@ npm i handlebars-i18n
38
38
 
39
39
  ## Import
40
40
 
41
- Import with ES6 import syntax:
41
+ Import with ES6 syntax:
42
42
 
43
43
  ```typescript
44
44
  import HandlebarsI18n from "handlebars-i18n";
@@ -66,7 +66,7 @@ Usage in web browser (old school):
66
66
 
67
67
  Via CDN:
68
68
  ```javascript
69
- <script src="https://cdn.jsdelivr.net/npm/handlebars-i18n@1.9.0/dist/handlebars-i18n.min.js"></script>
69
+ <script src="https://cdn.jsdelivr.net/npm/handlebars-i18n@1.10.1/dist/handlebars-i18n.min.js"></script>
70
70
  ```
71
71
 
72
72
  ## Quick Example
@@ -157,7 +157,7 @@ Finally use in template:
157
157
 
158
158
  ## Additional CLI Helper for Handlebars-i18n available
159
159
 
160
- :metal: Handlebars-i18n has its own command line
160
+ :metal: `handlebars-i18n` has its own command line
161
161
  interface [handlebars-i18n-cli](https://www.npmjs.com/package/handlebars-i18n-cli).
162
162
 
163
163
  ```sh
@@ -168,13 +168,13 @@ npm i handlebars-i18n-cli --save-dev
168
168
  JSON files from it
169
169
  * automatic translation of i18next JSON via [DeepL’s](https://www.deepl.com/en/pro-api/) free API
170
170
 
171
- ## Public Functions
171
+ ## Template Functions
172
172
 
173
173
  ### `__`
174
174
 
175
175
  Returns the phrase associated with the given key for the selected language. `__` will take all options
176
176
  i18next’s [t-function](https://www.i18next.com/overview/api#t) would take.
177
- The primary key can be passed hard encoded in the template when written in quotes:
177
+ The key can be passed hard encoded in the template when written in quotes:
178
178
 
179
179
  ```hbs
180
180
  {{__ "keyToTranslationPhrase"}}
@@ -204,6 +204,8 @@ The i18next resource:
204
204
  }
205
205
  ```
206
206
 
207
+ * output: en → **Everything is fine.**
208
+
207
209
  **Plurals**
208
210
 
209
211
  ```hbs
@@ -249,11 +251,21 @@ In this case the key `fruits` would contain an array of translation strings, lik
249
251
  }
250
252
  ```
251
253
 
252
- It is recommended to set `returnObjects` actively to `true` in the `i18next.init` object if you want to loop over
254
+ It is recommended to set `returnObjects` actively to `true` in the `i18next.init` object if you want to loop over
253
255
  an array or objects of properties.
254
256
 
255
257
  ---
256
258
 
259
+ ### `keyExists`
260
+
261
+ Checks if a i18next translation key exists. Returns `true` or `false`.
262
+
263
+ ```hbs
264
+ {{#if (keyExists "myKey")}} {{__ "myKey"}} {{/if}}
265
+ ```
266
+
267
+ ---
268
+
257
269
  ### `_locale`
258
270
 
259
271
  Returns the shortcode of i18next’s currently selected language such as `en`, `de`, `ja` … etc.
@@ -266,7 +278,7 @@ Returns the shortcode of i18next’s currently selected language such as `en`, `
266
278
 
267
279
  ### `localeIs`
268
280
 
269
- Checks a string against i18next’s currently selected language. Returns **true** or **false**.
281
+ Checks a string against i18next’s currently selected language. Returns `true` or `false`.
270
282
 
271
283
  ```hbs
272
284
  {{#if (localeIs "en")}} ... {{/if}}
@@ -312,7 +324,7 @@ seconds [, milliseconds]]]]]]:
312
324
  {{_date "[2012, 11, 20, 3, 0, 0]"}}
313
325
  ```
314
326
 
315
- **Additional arguments for formatting**
327
+ **Additional arguments for formatting:**
316
328
 
317
329
  You can add multiple arguments for individual formatting.
318
330
  See [Intl DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat)
@@ -325,7 +337,7 @@ in [handlebars-i18n.d.ts](./dist/handlebars-i18n.d.ts).
325
337
 
326
338
  ---
327
339
 
328
- ### `_dateAdd` :tada: new in 1.8
340
+ ### `_dateAdd`
329
341
 
330
342
  Adds a time offset in a given unit to a date, returns the modified date.
331
343
 
@@ -335,16 +347,16 @@ Adds a time offset in a given unit to a date, returns the modified date.
335
347
 
336
348
  * output: en → **12/18/1996**
337
349
 
338
- The first argument is a date (see function `_date` for valid date inputs). The second argument is a time amount given
350
+ The first argument is a date (see function `_date` for valid date inputs). The second argument is a time amount given
339
351
  as number. The option **unit** specifies the time amount. Possible units
340
- are `"second"` | `"minute"` | `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` |`"year"` (default is `"hour"`).
352
+ are `second` | `minute` | `hour` | `day` | `week` | `month` | `quarter` | `year` (default is `hour`).
341
353
  Further options as for function `_date` can be applied.
342
354
 
343
355
  ---
344
356
 
345
357
  ### `_dateDiff`
346
358
 
347
- Outputs the relative time difference between two given dates.
359
+ Outputs the time difference between two given dates in the requested unit.
348
360
 
349
361
  ```hbs
350
362
  {{_dateDiff "2000-12-17" "2001-12-17" unit="year"}}
@@ -352,12 +364,12 @@ Outputs the relative time difference between two given dates.
352
364
  * output: en → **in 1 year**
353
365
 
354
366
  The second date argument is subtracted from the first. If the difference is a positive value, a future event statement
355
- is made. A negative value refers to a past date. (If no second argument is given, the default date is the present moment).
356
- Allowed date input formats are similar to `_date`, options equal `_dateRel`. Default unit is `"hour"`.
367
+ is made. A negative value refers to a past date. (If no second argument is given, the default date is the present moment).
368
+ Allowed date input formats are similar to `_date`, options equal `_dateRel`. Default unit is `hour`.
357
369
 
358
370
  ---
359
371
 
360
- ### `_dateRel`
372
+ ### `_dateRel`
361
373
 
362
374
  Outputs a string with a relative date statement, formatted according to the language specific conventions.
363
375
 
@@ -374,7 +386,7 @@ Outputs a string with a relative date statement, formatted according to the lang
374
386
  * output: en → **7 hours ago**
375
387
 
376
388
  A positive number argument leads to a future event statement, a negative refers to a past date. Possible units
377
- are `"second"` | `"minute"` | `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` |`"year"` (default is `"hour"`).
389
+ are `second` | `minute` | `hour` | `day` | `week` | `month` | `quarter` | `year` (default is `hour`).
378
390
  For a complete set of options (such as `numberingSystem` or `localeMatcher`)
379
391
  see [Intl.RelativeTimeFormat Constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat).
380
392
  Alternatively check this repo’s TS types in [handlebars-i18n.d.ts](./dist/handlebars-i18n.d.ts).
@@ -386,10 +398,10 @@ Alternatively check this repo’s TS types in [handlebars-i18n.d.ts](./dist/hand
386
398
  Outputs a formatted number according to the language specific conventions of number representation.
387
399
 
388
400
  ```hbs
389
- {{_num 4100000.8314 }}
401
+ {{_num 3.14159}}
390
402
  ```
391
403
 
392
- **Additional arguments for formatting**
404
+ **Additional arguments for formatting:**
393
405
 
394
406
  You can add multiple arguments for individual formatting.
395
407
  See [Intl NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
@@ -412,7 +424,7 @@ Outputs a formatted currency string according to the language specific conventio
412
424
  {{_price 9999.99}}
413
425
  ```
414
426
 
415
- **Additional arguments for formatting**
427
+ **Additional arguments for formatting:**
416
428
 
417
429
  You can add multiple arguments for individual currency formatting.
418
430
  See [Intl NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
@@ -440,7 +452,7 @@ First argument is the language shortcode or `"all"` for all languages. Second is
440
452
  address (`DateTimeFormat`, `RelativeTimeFormat`, `NumberFormat`, or `PriceFormat`). Third argument is the options object
441
453
  with the specific settings.
442
454
 
443
- Examples for generic settings:
455
+ **Examples for generic settings:**
444
456
 
445
457
  ```javascript
446
458
  HandlebarsI18n.configure("all", "RelativeTimeFormat", {style: "long", unit: "second"});
@@ -527,7 +539,7 @@ it instead of the generic Handlebars object like so:
527
539
  ```javascript
528
540
  const HandlebarsModified = require("handlebars");
529
541
  HandlebarsModified.registerHelper("foo", function () {
530
- return "what you want"
542
+ return "bar"
531
543
  });
532
544
  HandlebarsI18n.init(HandlebarsModified);
533
545
  ```
@@ -219,6 +219,30 @@ describe('handlebars-i18n Tests', function () {
219
219
  });
220
220
 
221
221
 
222
+ /****************************************
223
+ Tests against function keyExists
224
+ ****************************************/
225
+
226
+ it('should return true for an existing translation key', function() {
227
+ const exists = hI18n.helpers.keyExists('key1');
228
+ assert.isTrue(exists);
229
+ });
230
+
231
+ it('should return false for a non-existing translation key', function() {
232
+ const exists = hI18n.helpers.keyExists('nonExistentKey');
233
+ assert.isFalse(exists);
234
+ });
235
+
236
+ it('should return false when called with no parameters', function() {
237
+ const exists = hI18n.helpers.keyExists();
238
+ assert.isFalse(exists);
239
+ });
240
+
241
+ it('should return false when called with null or undefined', function() {
242
+ assert.isFalse(hI18n.helpers.keyExists(null));
243
+ assert.isFalse(hI18n.helpers.keyExists(undefined));
244
+ });
245
+
222
246
  /****************************************
223
247
  Tests against function _date
224
248
  ****************************************/