handlebars-i18n-cli 1.0.5 → 2.0.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.
- package/.github/workflows/coveralls.yml +34 -17
- package/.github/workflows/node.js.yml +2 -2
- package/README.md +191 -55
- package/README_programmatic.md +195 -0
- package/bin/i18n-collect +36 -2
- package/bin/i18n-deepl +71 -0
- package/package.json +27 -16
- package/src/i18n-collect.js +162 -289
- package/src/i18n-deepl.js +304 -0
- package/src/index.js +4 -0
- package/test/handlebars-i18n-cli.test.mjs +472 -0
- package/tsconfig.json +20 -0
- package/types/i18n-collect.d.ts +5 -0
- package/types/i18n-collect.d.ts.map +1 -0
- package/types/i18n-deepl.d.ts +44 -0
- package/types/i18n-deepl.d.ts.map +1 -0
- package/types/index.d.ts +7 -0
- package/types/index.d.ts.map +1 -0
- package/want.md +18 -0
- package/src/deepl-free-api-key.json +0 -3
- package/src/deepl-pro-api-key.json +0 -3
- package/test/handlebars-i18n-cli.test.js +0 -174
- package/test/test-generated/test-12.json +0 -8
package/src/i18n-collect.js
CHANGED
|
@@ -1,151 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
/****************************************
|
|
2
2
|
* i18n-collect.js
|
|
3
|
-
*
|
|
4
3
|
* @author: Florian Walzel
|
|
5
|
-
* @date: 2022-10
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* $ i18n-collect <source> <target> <options...>
|
|
9
|
-
*
|
|
10
|
-
* valid options:
|
|
11
|
-
* --alphabetical || -a
|
|
12
|
-
* --dryRun || -dr
|
|
13
|
-
* --empty || -e
|
|
14
|
-
* --lng=de,fr,es,etc…
|
|
15
|
-
* --log || -l
|
|
16
|
-
* --separateLngFiles || -sf
|
|
17
|
-
* --translFunc=yourCustomFunctionName
|
|
18
|
-
* --update || -u*
|
|
19
|
-
*
|
|
20
|
-
* Copyright (c) 2020 Florian Walzel, MIT LICENSE
|
|
21
|
-
*
|
|
22
|
-
* Permission is hereby granted, free of charge, to any person
|
|
23
|
-
* obtaininga copy of this software and associated documentation
|
|
24
|
-
* files (the "Software"), to deal in the Software without restriction,
|
|
25
|
-
* including without limitation the rights to use, copy, modify, merge,
|
|
26
|
-
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
27
|
-
* and to permit persons to whom the Software is furnished to do so,
|
|
28
|
-
* subject to the following conditions:
|
|
29
|
-
*
|
|
30
|
-
* The above copyright notice and this permission notice shall be
|
|
31
|
-
* included in all copies or substantial portions of the Software.
|
|
32
|
-
*
|
|
33
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
34
|
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
35
|
-
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
36
|
-
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
37
|
-
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
38
|
-
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
39
|
-
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
40
|
-
* THE SOFTWARE.
|
|
41
4
|
*
|
|
42
|
-
|
|
43
|
-
|
|
5
|
+
****************************************/
|
|
44
6
|
|
|
45
|
-
'use strict';
|
|
46
7
|
|
|
47
8
|
/****************************************
|
|
48
|
-
*
|
|
9
|
+
* IMPORT
|
|
49
10
|
****************************************/
|
|
50
11
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const readFileAsync = promisify(fs.readFile)
|
|
54
|
-
const writeFileAsync = promisify(fs.writeFile)
|
|
55
|
-
|
|
12
|
+
import fst from 'async-file-tried';
|
|
13
|
+
import {glob} from 'glob';
|
|
56
14
|
|
|
57
15
|
/****************************************
|
|
58
|
-
* FUNCTIONS
|
|
16
|
+
* PRIVATE FUNCTIONS
|
|
59
17
|
****************************************/
|
|
60
18
|
|
|
61
19
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @param file
|
|
65
|
-
* @returns {Promise<*>}
|
|
66
|
-
*/
|
|
67
|
-
async function readFile (file) {
|
|
68
|
-
try {
|
|
69
|
-
const data = await readFileAsync(file, 'utf8')
|
|
70
|
-
return data
|
|
71
|
-
}
|
|
72
|
-
catch (e) {
|
|
73
|
-
console.log('\x1b[31m%s\x1b[0m', `Error. Could not read ${file}`)
|
|
74
|
-
console.error(e)
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Asynchronously write file in utf8 encoding
|
|
81
|
-
*
|
|
82
|
-
* @param file
|
|
83
|
-
* @param data
|
|
84
|
-
* @returns {Promise<boolean>}
|
|
85
|
-
*/
|
|
86
|
-
async function writeFile(file, data) {
|
|
87
|
-
try {
|
|
88
|
-
await writeFileAsync(file, data, 'utf8')
|
|
89
|
-
return true
|
|
90
|
-
}
|
|
91
|
-
catch (e) {
|
|
92
|
-
console.log('\x1b[31m%s\x1b[0m', `Error. Could not write ${file}`)
|
|
93
|
-
console.error(e)
|
|
94
|
-
return false
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Simple object check.
|
|
20
|
+
* Simple object check
|
|
100
21
|
*
|
|
101
22
|
* @param item
|
|
102
23
|
* @returns {boolean}
|
|
103
24
|
*/
|
|
104
25
|
function isObject(item) {
|
|
105
|
-
return (item && typeof item === 'object' && !
|
|
26
|
+
return (item && typeof item === 'object' && !Array.isArray(item))
|
|
106
27
|
}
|
|
107
28
|
|
|
108
29
|
/**
|
|
109
30
|
* Conditionally removes a substring (file extension)
|
|
110
|
-
* from end of given string
|
|
31
|
+
* from end of given string
|
|
111
32
|
*
|
|
112
33
|
* @param str
|
|
113
34
|
* @param ending
|
|
114
35
|
* @returns {*|string[]}
|
|
115
36
|
*/
|
|
116
|
-
function sanitizeFileExt(str, ending='.json') {
|
|
37
|
+
function sanitizeFileExt(str, ending = '.json') {
|
|
117
38
|
return str.toLowerCase().endsWith(ending) ? str.slice(0, ending.length * (-1)) : str
|
|
118
39
|
}
|
|
119
40
|
|
|
120
|
-
/**
|
|
121
|
-
* Log the help information to console
|
|
122
|
-
* @returns {boolean}
|
|
123
|
-
*/
|
|
124
|
-
function logHelp() {
|
|
125
|
-
console.log('\x1b[2m%s', 'Usage:')
|
|
126
|
-
console.log('i18n-collect <source> <target> <options...>')
|
|
127
|
-
console.log('')
|
|
128
|
-
console.log('<source> path to handlebars.js template file(s), glob pattern allowed')
|
|
129
|
-
console.log('<target> json file(s) to write result to')
|
|
130
|
-
console.log('')
|
|
131
|
-
console.log('<options>')
|
|
132
|
-
console.log('--alphabetical or -a will order the keys to the translation strings alphabetically in the json')
|
|
133
|
-
console.log(' default: keys in order of appearance as within the template(s)')
|
|
134
|
-
console.log('--dryRun or -dr will log the result(s) but not write out json file(s)')
|
|
135
|
-
console.log('--empty or -e will create empty value strings for the translations in the json')
|
|
136
|
-
console.log(' default: value strings contain current language and key name')
|
|
137
|
-
console.log('--lng=en,fr,es,… the languages you want to be generated')
|
|
138
|
-
console.log(' default: en')
|
|
139
|
-
console.log('--log or -l log final results to console')
|
|
140
|
-
console.log('--separateLngFiles write each language in a separate json file')
|
|
141
|
-
console.log(' or -sf default: all languages are written as arrays in one json file')
|
|
142
|
-
console.log('--translFunc=customName a custom name of the translation function used in the templates')
|
|
143
|
-
console.log(' default: __ like handlebars-i18n notation: {{__ keyToTranslate}}')
|
|
144
|
-
console.log('--update or -u updates existing json files(s) after changes made in template file(s)')
|
|
145
|
-
console.log('\x1b[0m')
|
|
146
|
-
return true
|
|
147
|
-
}
|
|
148
|
-
|
|
149
41
|
/**
|
|
150
42
|
* A collection of functions to extract and handle the
|
|
151
43
|
* strings between mustaches {{ ... }}
|
|
@@ -157,8 +49,8 @@ function logHelp() {
|
|
|
157
49
|
* getAllResults: mustacheBetweens.getAllResults}}
|
|
158
50
|
*/
|
|
159
51
|
const mustacheBetweens = {
|
|
160
|
-
results
|
|
161
|
-
str
|
|
52
|
+
results: [],
|
|
53
|
+
str: '',
|
|
162
54
|
/**
|
|
163
55
|
* Returns a substring between an opening and
|
|
164
56
|
* a closing string sequence in a given string
|
|
@@ -167,51 +59,49 @@ const mustacheBetweens = {
|
|
|
167
59
|
* @param sub2
|
|
168
60
|
* @returns {string|boolean}
|
|
169
61
|
*/
|
|
170
|
-
getFromBetween
|
|
171
|
-
if (this.str.indexOf(sub1) < 0 || this.str.indexOf(sub2) < 0)
|
|
172
|
-
return false
|
|
62
|
+
getFromBetween: function (sub1, sub2) {
|
|
173
63
|
let SP = this.str.indexOf(sub1) + sub1.length,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
64
|
+
string1 = this.str.slice(0, SP),
|
|
65
|
+
string2 = this.str.slice(SP),
|
|
66
|
+
TP = string1.length + string2.indexOf(sub2);
|
|
67
|
+
|
|
177
68
|
return this.str.substring(SP, TP)
|
|
178
69
|
},
|
|
179
70
|
|
|
180
71
|
/**
|
|
181
72
|
* Removes a found sequence between an opening and
|
|
182
|
-
* a closing string from a
|
|
73
|
+
* a closing string from a given string
|
|
183
74
|
*
|
|
184
75
|
* @param sub1
|
|
185
76
|
* @param sub2
|
|
186
77
|
* @returns {boolean}
|
|
187
78
|
*/
|
|
188
|
-
removeFromBetween
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.str = this.str.replace(removal,'')
|
|
79
|
+
removeFromBetween: function (sub1, sub2) {
|
|
80
|
+
let removal = sub1 + this.getFromBetween(sub1, sub2) + sub2;
|
|
81
|
+
this.str = this.str.replace(removal, '');
|
|
82
|
+
return true
|
|
193
83
|
},
|
|
194
84
|
|
|
195
85
|
/**
|
|
196
86
|
* gets all substrings of a string that are between an opening character sequence (sub1)
|
|
197
|
-
* and a closing sequence (sub2). The Result is stored in parent results array
|
|
87
|
+
* and a closing sequence (sub2). The Result is stored in parent results array
|
|
198
88
|
*
|
|
199
89
|
* @param sub1
|
|
200
90
|
* @param sub2
|
|
201
91
|
*/
|
|
202
|
-
getAllResults
|
|
92
|
+
getAllResults: function (sub1, sub2) {
|
|
203
93
|
// first check to see if we do have both substrings
|
|
204
94
|
if (this.str.indexOf(sub1) < 0 || this.str.indexOf(sub2) < 0)
|
|
205
|
-
return false
|
|
95
|
+
return false;
|
|
206
96
|
// find first result
|
|
207
|
-
let result = this.getFromBetween(sub1, sub2)
|
|
97
|
+
let result = this.getFromBetween(sub1, sub2);
|
|
208
98
|
// replace multiple spaces by a single one, then trim and push it to the results array
|
|
209
|
-
this.results.push(result.replace(/ +(?= )/g,'').trim())
|
|
99
|
+
this.results.push(result.replace(/ +(?= )/g, '').trim());
|
|
210
100
|
// remove the most recently found one from the string
|
|
211
|
-
this.removeFromBetween(sub1, sub2)
|
|
101
|
+
this.removeFromBetween(sub1, sub2);
|
|
212
102
|
// recursion in case there are more substrings
|
|
213
103
|
if (this.str.indexOf(sub1) > -1 && this.str.indexOf(sub2) > -1)
|
|
214
|
-
this.getAllResults(sub1, sub2)
|
|
104
|
+
this.getAllResults(sub1, sub2);
|
|
215
105
|
},
|
|
216
106
|
|
|
217
107
|
/**
|
|
@@ -221,34 +111,34 @@ const mustacheBetweens = {
|
|
|
221
111
|
* @param sub2
|
|
222
112
|
* @returns {*}
|
|
223
113
|
*/
|
|
224
|
-
getSorted
|
|
225
|
-
this.str = string
|
|
226
|
-
this.getAllResults(sub1, sub2)
|
|
114
|
+
getSorted: function (string, translFuncName, sub1 = '{{', sub2 = '}}') {
|
|
115
|
+
this.str = string;
|
|
116
|
+
this.getAllResults(sub1, sub2);
|
|
227
117
|
this.results =
|
|
228
118
|
this.results.filter(
|
|
229
|
-
(el) => {
|
|
230
|
-
return typeof el === 'string' && el.startsWith(`${translFuncName} `)
|
|
231
|
-
})
|
|
232
|
-
.map(
|
|
233
119
|
(el) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
.replace(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
transformed.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
120
|
+
return typeof el === 'string' && el.startsWith(`${translFuncName} `)
|
|
121
|
+
})
|
|
122
|
+
.map(
|
|
123
|
+
(el) => {
|
|
124
|
+
// remove leading translation function and explode string by space
|
|
125
|
+
let splited = el.replace(`${translFuncName} `, '').split(' ');
|
|
126
|
+
// remove quotation marks around key name in element 0 of array
|
|
127
|
+
splited[0] = splited[0]
|
|
128
|
+
.replace(/"/g, '')
|
|
129
|
+
.replace(/'/g, '');
|
|
130
|
+
// split remaining string in first element of array by dot (.) to get separate keys of a dot-notated object
|
|
131
|
+
let keys = splited[0].split('.');
|
|
132
|
+
// transformed is a container object for key
|
|
133
|
+
let transformed = {};
|
|
134
|
+
transformed.keys = keys;
|
|
135
|
+
transformed.replacementVars = [];
|
|
136
|
+
// split following elements by '=' and preserve first element of split
|
|
137
|
+
for (let i = 1; i < splited.length; i++)
|
|
138
|
+
transformed.replacementVars[i - 1] = splited[i].split('=')[0];
|
|
139
|
+
|
|
140
|
+
return transformed
|
|
141
|
+
});
|
|
252
142
|
return this.results
|
|
253
143
|
}
|
|
254
144
|
};
|
|
@@ -266,22 +156,22 @@ const mustacheBetweens = {
|
|
|
266
156
|
* @returns {*}
|
|
267
157
|
*/
|
|
268
158
|
const arrRmvDuplicateValues = (arr) => {
|
|
269
|
-
let seen = {
|
|
159
|
+
let seen = {};
|
|
270
160
|
return arr.filter((item) => {
|
|
271
161
|
return seen.hasOwnProperty(item.keys) ? false : seen[item.keys] = true
|
|
272
162
|
})
|
|
273
|
-
}
|
|
163
|
+
};
|
|
274
164
|
|
|
275
165
|
|
|
276
166
|
/**
|
|
277
|
-
* Builds a nested object with key-value-pairs from a three-dimensional array
|
|
167
|
+
* Builds a nested object with key-value-pairs from a three-dimensional array
|
|
278
168
|
*
|
|
279
169
|
* @param arr
|
|
280
170
|
* @param lang
|
|
281
171
|
* @param empty
|
|
282
172
|
* @returns {{}}
|
|
283
173
|
*/
|
|
284
|
-
function objectify
|
|
174
|
+
function objectify(arr, lang = 'en', empty = false) {
|
|
285
175
|
|
|
286
176
|
/**
|
|
287
177
|
*
|
|
@@ -290,16 +180,14 @@ function objectify (arr, lang='en', empty = false) {
|
|
|
290
180
|
* @param arr
|
|
291
181
|
* @param pos
|
|
292
182
|
*/
|
|
293
|
-
function __iterateArr
|
|
294
|
-
if (!
|
|
183
|
+
function __iterateArr(obj, val, arr, pos) {
|
|
184
|
+
if (!obj.hasOwnProperty(arr[pos])) {
|
|
295
185
|
if (pos + 1 < arr.length) {
|
|
296
|
-
obj[arr[pos]] = {
|
|
297
|
-
__iterateArr(obj[arr[pos]], val, arr, pos + 1)
|
|
298
|
-
}
|
|
299
|
-
else
|
|
186
|
+
obj[arr[pos]] = {};
|
|
187
|
+
__iterateArr(obj[arr[pos]], val, arr, pos + 1);
|
|
188
|
+
} else
|
|
300
189
|
obj[arr[pos]] = val;
|
|
301
|
-
}
|
|
302
|
-
else if (pos+1 < arr.length)
|
|
190
|
+
} else if (pos + 1 < arr.length)
|
|
303
191
|
__iterateArr(obj[arr[pos]], val, arr, pos + 1);
|
|
304
192
|
}
|
|
305
193
|
|
|
@@ -311,22 +199,22 @@ function objectify (arr, lang='en', empty = false) {
|
|
|
311
199
|
* @param textBefore
|
|
312
200
|
* @returns {string}
|
|
313
201
|
*/
|
|
314
|
-
function __listTranslVariables(arr, textBefore= '')
|
|
202
|
+
function __listTranslVariables(arr, textBefore = '') {
|
|
315
203
|
let str = '';
|
|
316
204
|
if (arr.length === 0)
|
|
317
205
|
return str;
|
|
318
206
|
for (let elem of arr)
|
|
319
207
|
str += `{{${elem}}} `;
|
|
320
|
-
return textBefore + str.slice(0, -1)
|
|
208
|
+
return textBefore + str.slice(0, -1)
|
|
321
209
|
}
|
|
322
210
|
|
|
323
|
-
let obj = {
|
|
211
|
+
let obj = {}
|
|
324
212
|
arr.forEach((el) => {
|
|
325
213
|
let prop;
|
|
326
214
|
if (empty)
|
|
327
215
|
prop = __listTranslVariables(el.replacementVars);
|
|
328
216
|
else
|
|
329
|
-
prop = `${lang} of ${el.keys.join('.') + __listTranslVariables(el.replacementVars, ' with variables ')}
|
|
217
|
+
prop = `${lang} of ${el.keys.join('.') + __listTranslVariables(el.replacementVars, ' with variables ')}`;
|
|
330
218
|
__iterateArr(obj, prop, el.keys, 0)
|
|
331
219
|
})
|
|
332
220
|
|
|
@@ -335,27 +223,29 @@ function objectify (arr, lang='en', empty = false) {
|
|
|
335
223
|
|
|
336
224
|
/**
|
|
337
225
|
* Deep merge two objects whereby all properties of
|
|
338
|
-
* sources are kept and the target properties are added
|
|
226
|
+
* sources are kept and the target properties are added
|
|
339
227
|
*
|
|
340
228
|
* @param target
|
|
341
229
|
* @param ...sources
|
|
342
230
|
*/
|
|
343
231
|
function mergeDeep(target, ...sources) {
|
|
344
|
-
if (!
|
|
232
|
+
if (!sources.length)
|
|
233
|
+
return target;
|
|
234
|
+
|
|
345
235
|
const source = sources.shift();
|
|
346
236
|
|
|
347
237
|
if (isObject(target) && isObject(source)) {
|
|
348
238
|
for (const key in source) {
|
|
349
239
|
if (isObject(source[key])) {
|
|
350
|
-
if (!
|
|
351
|
-
Object.assign(target, {[key]: {
|
|
240
|
+
if (!target[key])
|
|
241
|
+
Object.assign(target, {[key]: {}});
|
|
352
242
|
mergeDeep(target[key], source[key]);
|
|
353
243
|
} else
|
|
354
244
|
Object.assign(target, {[key]: source[key]});
|
|
355
245
|
}
|
|
356
246
|
}
|
|
357
247
|
|
|
358
|
-
return mergeDeep(target, ...sources)
|
|
248
|
+
return mergeDeep(target, ...sources)
|
|
359
249
|
}
|
|
360
250
|
|
|
361
251
|
/**
|
|
@@ -370,23 +260,23 @@ function mergeDeep(target, ...sources) {
|
|
|
370
260
|
* @returns arr
|
|
371
261
|
*/
|
|
372
262
|
function deepSort(arr) {
|
|
373
|
-
// determine the longest array in keys properties
|
|
374
263
|
let depth = 0;
|
|
264
|
+
// determine the longest array in keys properties
|
|
375
265
|
for (let inst of arr)
|
|
376
266
|
if (inst.keys.length > depth)
|
|
377
267
|
depth = inst.keys.length;
|
|
378
268
|
|
|
379
269
|
// iterate from longest to shortest and sort
|
|
380
|
-
for (let i = depth-1; i>=0; i--) {
|
|
270
|
+
for (let i = depth - 1; i >= 0; i--) {
|
|
381
271
|
arr = arr.sort((a, b) => {
|
|
382
|
-
if (
|
|
272
|
+
if (a.keys[i] !== undefined && b.keys[i] !== undefined)
|
|
383
273
|
return a.keys[i] > b.keys[i] ? -1 : 1;
|
|
384
274
|
else
|
|
385
|
-
return a.keys[i] !== undefined ? -1 : 1
|
|
275
|
+
return a.keys[i] !== undefined ? -1 : 1;
|
|
386
276
|
});
|
|
387
277
|
}
|
|
388
278
|
// we get a descending array, so we invert it
|
|
389
|
-
return arr.reverse()
|
|
279
|
+
return arr.reverse()
|
|
390
280
|
}
|
|
391
281
|
|
|
392
282
|
|
|
@@ -394,130 +284,106 @@ function deepSort(arr) {
|
|
|
394
284
|
* EXPORT PUBLIC INTERFACE
|
|
395
285
|
****************************************/
|
|
396
286
|
|
|
397
|
-
|
|
287
|
+
async function i18nCollect(source, target, options) {
|
|
398
288
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
for (let i = 2; i < argv.length; i++)
|
|
402
|
-
args.push(argv[i])
|
|
289
|
+
if (typeof source !== 'string')
|
|
290
|
+
throw new Error(`First argument SOURCE must be of type string. Please specify a valid SOURCE.`);
|
|
403
291
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
throw new Error(`No arguments given. Please specify SOURCE and TARGET.
|
|
407
|
-
Call argument --help for further Information.`)
|
|
292
|
+
if (typeof target !== 'string')
|
|
293
|
+
throw new Error(`Second argument TARGET must be of type string. Please specify a valid TARGET.`);
|
|
408
294
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
return logHelp()
|
|
414
|
-
// error the missing second argument
|
|
415
|
-
else
|
|
416
|
-
throw new Error(`Missing second argument for TARGET.
|
|
417
|
-
Call argument --help for further Information.`)
|
|
418
|
-
}
|
|
295
|
+
options = options || {};
|
|
296
|
+
|
|
297
|
+
if (typeof options !== 'object' && !Array.isArray(options) && Object.prototype.toString.call(options))
|
|
298
|
+
throw new Error(`Third argument OPTIONS must be of type object. Please specify a valid OPTION.`);
|
|
419
299
|
|
|
420
300
|
// register vars
|
|
421
|
-
let hndlbrKeys = [
|
|
422
|
-
sources,
|
|
423
|
-
targetFileName,
|
|
424
|
-
targetFileNameSeparated,
|
|
425
|
-
translationFuncName,
|
|
426
|
-
pos = -1,
|
|
427
|
-
languages,
|
|
301
|
+
let hndlbrKeys = [],
|
|
428
302
|
translObj,
|
|
429
303
|
outputObj;
|
|
430
304
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
(el) => ! (el === 'help' || el.startsWith('--') || el.startsWith('-'))
|
|
434
|
-
)
|
|
305
|
+
// get glob from source
|
|
306
|
+
const templateFiles = await glob(source, {ignore: 'node_modules/**'})
|
|
435
307
|
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
translationFuncName = (pos >= 0) ? args[pos].split('=')[1] : '__'
|
|
444
|
-
|
|
445
|
-
// read in file(s) and join contents keeping only unique key names
|
|
446
|
-
for (let file of sources) {
|
|
447
|
-
console.log(`Now processing ${file}`)
|
|
448
|
-
let content = await readFile(file)
|
|
308
|
+
// extract translations keys from file(s) in array hndlbrKeys
|
|
309
|
+
for (let file of templateFiles) {
|
|
310
|
+
console.log(`Now processing ${file}`);
|
|
311
|
+
let [content, err] = await fst.readFile(file, 'utf8');
|
|
312
|
+
if (err)
|
|
313
|
+
throw (err);
|
|
314
|
+
//console.log(content)
|
|
449
315
|
hndlbrKeys = hndlbrKeys.concat(
|
|
450
|
-
mustacheBetweens.getSorted(content,
|
|
451
|
-
)
|
|
316
|
+
mustacheBetweens.getSorted(content, options.translFunc || '__')
|
|
317
|
+
);
|
|
452
318
|
}
|
|
453
319
|
|
|
454
320
|
// break if no strings for translation where found
|
|
455
321
|
if (hndlbrKeys.length === 0)
|
|
456
|
-
return console.log('No strings for translation found, no files written.')
|
|
322
|
+
return console.log('No strings for translation found, no files written.');
|
|
457
323
|
|
|
458
324
|
// remove all duplicate value entries in position 'keys' of array hndlbrKeys
|
|
459
|
-
hndlbrKeys = arrRmvDuplicateValues(hndlbrKeys)
|
|
325
|
+
hndlbrKeys = arrRmvDuplicateValues(hndlbrKeys);
|
|
460
326
|
|
|
461
|
-
// evaluate argument '
|
|
462
|
-
if (
|
|
327
|
+
// evaluate argument 'alphabetical' for sorting
|
|
328
|
+
if (options.alphabetical)
|
|
463
329
|
hndlbrKeys = deepSort(hndlbrKeys)
|
|
464
330
|
|
|
465
|
-
// form an array of languages from argument '
|
|
466
|
-
languages =
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return el.split('=')[1].split(',')
|
|
470
|
-
})[0]
|
|
471
|
-
|
|
472
|
-
// if no language parameter is passed set 'en' as default language
|
|
473
|
-
if (typeof languages === 'undefined') languages = ['en']
|
|
331
|
+
// form an array of languages from argument 'lng
|
|
332
|
+
const languages = (Array.isArray(options.lng) && options.lng.length > 0)
|
|
333
|
+
? options.lng
|
|
334
|
+
: ['en'];
|
|
474
335
|
|
|
475
336
|
|
|
476
337
|
// WRITE TO ONE FILE PER LANGUAGE
|
|
477
338
|
// ------------------------------------------------
|
|
478
339
|
|
|
479
|
-
// evaluate argument '
|
|
480
|
-
if (
|
|
340
|
+
// evaluate argument 'separateLngFiles' to output each language in a separate file
|
|
341
|
+
if (options.separateLngFiles) {
|
|
481
342
|
|
|
482
343
|
// if user entered argument for target ending with .json, remove it
|
|
483
|
-
targetFileName = sanitizeFileExt(
|
|
344
|
+
let targetFileName = sanitizeFileExt(target)
|
|
484
345
|
|
|
485
346
|
for (let lng of languages) {
|
|
347
|
+
|
|
486
348
|
// join file name per language such as myfile.de.json, myfile.en.json, ...
|
|
487
|
-
targetFileNameSeparated =
|
|
488
|
-
|
|
349
|
+
let targetFileNameSeparated = (targetFileName.startsWith('/')
|
|
350
|
+
? targetFileName.substring(1)
|
|
351
|
+
: targetFileName)
|
|
352
|
+
+ '.' + lng + '.json';
|
|
489
353
|
|
|
490
354
|
// create output object per language and add keys in nested object form
|
|
491
|
-
outputObj = {
|
|
492
|
-
outputObj[lng] = objectify(hndlbrKeys, lng,
|
|
355
|
+
outputObj = {}
|
|
356
|
+
outputObj[lng] = objectify(hndlbrKeys, lng, options.empty)
|
|
493
357
|
|
|
494
|
-
// if
|
|
358
|
+
// if option 'update' was given, existing files per language are read in, parsed,
|
|
495
359
|
// and the new translation Object is merged onto the existing translation
|
|
496
|
-
if (
|
|
497
|
-
let
|
|
498
|
-
|
|
499
|
-
|
|
360
|
+
if (options.update) {
|
|
361
|
+
let [res, err] = await fst.readJson(targetFileNameSeparated);
|
|
362
|
+
if (err)
|
|
363
|
+
throw (err);
|
|
364
|
+
outputObj = mergeDeep(outputObj[lng], res)
|
|
500
365
|
}
|
|
501
366
|
|
|
502
367
|
// convert output object to json with linebreaks and indenting of 2 spaces
|
|
503
368
|
const fileOutputJson = JSON.stringify(outputObj, null, 2)
|
|
504
369
|
|
|
505
370
|
// log output per language
|
|
506
|
-
if (
|
|
507
|
-
|| args.includes('--dryRun') || args.includes('-dr'))
|
|
371
|
+
if (options.log || options.dryRun)
|
|
508
372
|
console.log(fileOutputJson)
|
|
509
373
|
|
|
374
|
+
let [write, e] = [undefined, undefined];
|
|
375
|
+
|
|
510
376
|
// write files only if no --dryRun option was set
|
|
511
|
-
if (!
|
|
377
|
+
if (!options.dryRun)
|
|
512
378
|
// write out the json to target file per language
|
|
513
|
-
|
|
514
|
-
|
|
379
|
+
[write, e] = await fst.writeFile(targetFileNameSeparated, fileOutputJson);
|
|
380
|
+
if (e)
|
|
381
|
+
throw (e);
|
|
382
|
+
console.log('\x1b[34m%s\x1b[0m', `Wrote language keys for '${lng}' to ${targetFileNameSeparated}`)
|
|
515
383
|
}
|
|
516
384
|
|
|
517
|
-
|
|
518
|
-
console.log('\x1b[36m%s\x1b[0m', 'This was a dry run. No files witten.')
|
|
519
|
-
:
|
|
520
|
-
console.log('\x1b[32m%s\x1b[0m', `You’re good. All Done.`)
|
|
385
|
+
if (options.dryRun)
|
|
386
|
+
console.log('\x1b[36m%s\x1b[0m', 'This was a dry run. No files witten.');
|
|
521
387
|
}
|
|
522
388
|
|
|
523
389
|
// WRITE SINGLE FILE CONTAINING ALL LANGUAGES
|
|
@@ -525,37 +391,44 @@ exports.cli = async (argv) => {
|
|
|
525
391
|
else {
|
|
526
392
|
// create object to hold the translations and create a key for every language
|
|
527
393
|
// add all handlebars translation keys to each language key as nested objects
|
|
528
|
-
translObj = {translations: {
|
|
394
|
+
translObj = {translations: {}}
|
|
395
|
+
|
|
529
396
|
languages.forEach((lng) => {
|
|
530
|
-
translObj.translations[lng] = objectify(hndlbrKeys, lng,
|
|
397
|
+
translObj.translations[lng] = objectify(hndlbrKeys, lng, options.empty)
|
|
531
398
|
})
|
|
532
399
|
|
|
533
|
-
// if argument '
|
|
400
|
+
// if argument 'update' was given, an existing file is read in, parsed,
|
|
534
401
|
// and the new translation Object is merged onto the existing translations
|
|
535
|
-
if (
|
|
536
|
-
let
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
else
|
|
541
|
-
outputObj = translObj
|
|
402
|
+
if (options.update) {
|
|
403
|
+
let [res, err] = await fst.readJson(target);
|
|
404
|
+
if (err)
|
|
405
|
+
throw (err);
|
|
406
|
+
outputObj = mergeDeep(translObj, res)
|
|
407
|
+
} else
|
|
408
|
+
outputObj = translObj;
|
|
542
409
|
|
|
543
410
|
// convert output object to json with linebreaks and indenting of 2 spaces
|
|
544
|
-
const fileOutputJson = JSON.stringify(outputObj,null, 2)
|
|
411
|
+
const fileOutputJson = JSON.stringify(outputObj, null, 2)
|
|
545
412
|
|
|
546
|
-
// log the final object to console if option '
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
console.log(fileOutputJson)
|
|
413
|
+
// log the final object to console if option 'log' or 'dryRun' was set
|
|
414
|
+
if (options.log || options.dryRun)
|
|
415
|
+
console.log(fileOutputJson);
|
|
550
416
|
|
|
551
|
-
// exit if option '
|
|
552
|
-
if (
|
|
417
|
+
// exit if option 'dryRun' was set
|
|
418
|
+
if (options.dryRun) {
|
|
553
419
|
console.log('\x1b[36m%s\x1b[0m', 'This was a dry run. No file witten.')
|
|
554
420
|
process.exit(0)
|
|
555
421
|
}
|
|
556
422
|
|
|
557
423
|
// write out the json to target file
|
|
558
|
-
|
|
559
|
-
|
|
424
|
+
let [res, err] = await fst.writeFile(target, fileOutputJson);
|
|
425
|
+
if (err)
|
|
426
|
+
throw (err);
|
|
560
427
|
}
|
|
561
|
-
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Export the function
|
|
432
|
+
export {
|
|
433
|
+
i18nCollect
|
|
434
|
+
}
|