handlebars-i18n-cli 1.0.4 → 2.0.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.
@@ -0,0 +1,304 @@
1
+ /*********************************************************************
2
+ * i18n-deepl.js
3
+ * @author: Florian Walzel
4
+ *
5
+ * Get an API Key:
6
+ * @link https://www.deepl.com/de/pro-checkout/account?productId=1200&yearly=false&trial=false
7
+ *
8
+ * The API Docs:
9
+ * @link https://www.deepl.com/docs-api/translate-text/multiple-sentences/
10
+ */
11
+
12
+
13
+ /****************************************
14
+ * IMPORT
15
+ ****************************************/
16
+
17
+ import deepl from 'deepl-node';
18
+ import axios from 'axios';
19
+ import fst from 'async-file-tried';
20
+ import path from 'path';
21
+
22
+
23
+ /****************************************
24
+ * PRIVATE FUNCTIONS
25
+ ****************************************/
26
+
27
+ /**
28
+ * Flatten an object by recursively writing its values to an array
29
+ *
30
+ * @param obj
31
+ * @returns {*[]}
32
+ */
33
+ function __flattenObj(obj) {
34
+ const result = [];
35
+
36
+ function recurse(curr) {
37
+ for (let key in curr) {
38
+ if (typeof curr[key] === 'object' && curr[key] !== null) {
39
+ recurse(curr[key]);
40
+ } else {
41
+ result.push(curr[key]);
42
+ }
43
+ }
44
+ }
45
+
46
+ recurse(obj);
47
+ return result
48
+ }
49
+
50
+ /**
51
+ * The inverse operation of __flattenObject(): maps the values of a flat array back to a given object
52
+ *
53
+ * @param obj
54
+ * @param values
55
+ * @param childParam
56
+ * @returns {*}
57
+ */
58
+ function __mapArrayToObj(obj, values, childParam) {
59
+ let index = 0;
60
+
61
+ function recurse(curr) {
62
+ for (let key in curr) {
63
+ if (typeof curr[key] === 'object' && curr[key] !== null) {
64
+ recurse(curr[key]);
65
+ } else {
66
+ curr[key] = values[index++][childParam];
67
+ }
68
+ }
69
+ }
70
+
71
+ recurse(obj);
72
+ return obj
73
+ }
74
+
75
+ /** Traverse an object by a given path of sub-nodes and retrieve its value
76
+ *
77
+ * @param obj
78
+ * @param path | given like "my.path.to", will retrieve {my: {path: {to: "Value" }}}
79
+ * @returns {*}
80
+ */
81
+ function __getValueFromPath(obj, path) {
82
+ // Split the path into an array of keys
83
+ const keys = path.split('.');
84
+
85
+ // Use reduce to traverse the object
86
+ return keys.reduce((acc, key) => {
87
+ if (acc && acc.hasOwnProperty(key)) {
88
+ return acc[key];
89
+ }
90
+ return undefined; // Return undefined if any key is not found
91
+ }, obj);
92
+ }
93
+
94
+ /** Traverse an object by a given path of sub-nodes and set a value
95
+ * at the given position
96
+ *
97
+ * @param obj
98
+ * @param path
99
+ * @param val
100
+ * @param langCode
101
+ * @private
102
+ */
103
+ function __setNestedValue(obj, path, val, langCode) {
104
+ const keys = path.split('.');
105
+
106
+ function iterate(ob, keys, insert, lngCode, i = 0) {
107
+ let key = keys[i];
108
+ if (i < keys.length - 1) {
109
+ i = i+1;
110
+ iterate(ob[key], keys, insert, langCode, i);
111
+ }
112
+ else {
113
+ ob[key][langCode] = (typeof ob[key][langCode] === 'object')
114
+ ? {...ob[key][langCode], ...insert}
115
+ : ob[key][langCode] = insert;
116
+ }
117
+ }
118
+
119
+ iterate(obj, keys, val, langCode);
120
+ return true
121
+ }
122
+
123
+
124
+ /****************************************
125
+ * PUBLIC INTERFACE
126
+ ****************************************/
127
+
128
+ /**
129
+ * Write th DeepL auth key to .env file
130
+ *
131
+ * @param key
132
+ * @param path
133
+ * @returns {Promise<boolean>}
134
+ */
135
+ async function setAuthKey(key, path='./') {
136
+ if (typeof key !== 'string' || key === '')
137
+ throw new Error('Provided argument is not a valid DeepL auth key.');
138
+ const file = '.env';
139
+ let [res, err] = await fst.writeFile([path, file], `export DEEPL_AUTH=${key}`);
140
+ if (err) {
141
+ throw new Error(`Failed to write file ${file}`);
142
+ }
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Function to fetch supported languages from the DeepL API
148
+ *
149
+ * @param authKey
150
+ * @returns {Promise<*>}
151
+ */
152
+ async function getSupportedLanguages(authKey) {
153
+ if (typeof authKey !== 'string' || authKey === '')
154
+ throw new Error('Invalid argument authKey provided.');
155
+ try {
156
+ const response = await axios.get('https://api-free.deepl.com/v2/languages', {
157
+ params: {
158
+ auth_key: authKey,
159
+ type: 'target'
160
+ }
161
+ });
162
+ return response.data;
163
+ } catch (error) {
164
+ console.error('Error fetching supported languages:', error.response ? error.response.data : error.message);
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Translate a string or an array of strings using the DeepL API
171
+ *
172
+ * @param authKey
173
+ * @param texts
174
+ * @param sourceLang
175
+ * @param targetLang
176
+ * @param options
177
+ * @returns {Promise<TextResult|TextResult[]>}
178
+ */
179
+ async function translateTexts(authKey, texts, sourceLang, targetLang, options) {
180
+ if (typeof authKey !== 'string' || authKey === '')
181
+ throw new Error('Invalid argument authKey provided.');
182
+ const translator = new deepl.Translator(authKey);
183
+ let [res, err] =
184
+ await fst.asyncHandler(() => translator.translateText(texts, sourceLang, targetLang, options));
185
+ if (err)
186
+ throw (err);
187
+ return res;
188
+ }
189
+
190
+
191
+ /** read a json file, translate it with the Deepl API, write the result as json file
192
+ *
193
+ * @param authKey
194
+ * @param JsonSrc
195
+ * @param JsonTarget
196
+ * @param targetLangCode
197
+ * @param sourceNested
198
+ * @param sourceLangCode
199
+ * @param log
200
+ * @param dryRun
201
+ * @param deeplOpts
202
+ * @returns {Promise<boolean>}
203
+ */
204
+ async function translateToJSON(
205
+ authKey,
206
+ JsonSrc,
207
+ JsonTarget,
208
+ sourceLangCode,
209
+ targetLangCode,
210
+ deeplOpts,
211
+ sourceNested,
212
+ log,
213
+ dryRun) {
214
+
215
+ // read the json source
216
+ let [srcObj, err] = await fst.readJson(JsonSrc);
217
+ if (err) {
218
+ console.error(`Unable to read file: ${file}`);
219
+ throw err;
220
+ }
221
+
222
+ // make a copy of srcObj to avoid circular references
223
+ const modifiedObj = JSON.parse(JSON.stringify(srcObj));
224
+
225
+ // define variable to hold the parsed JSON
226
+ let srcObjParsed;
227
+
228
+ // extract the nested key if exists
229
+ if (typeof sourceNested === 'string' && sourceNested !== '') {
230
+ const subEntry = __getValueFromPath(srcObj, sourceNested);
231
+ if (!subEntry)
232
+ throw new Error(`The nested key "${sourceNested}" does not exist in JSON file "${JsonSrc}"`);
233
+ srcObjParsed = subEntry;
234
+ }
235
+ // if not, the source object is the parsed object
236
+ else
237
+ srcObjParsed = srcObj;
238
+
239
+ // in key destination access the child key with the source language code,
240
+ // otherwise assume we are already in the key with the source lang code
241
+ const srcObjPart = (srcObjParsed[sourceLangCode])
242
+ ? srcObjParsed[sourceLangCode]
243
+ : srcObjParsed;
244
+
245
+ // flatten the resulting object to an array
246
+ const translValues = __flattenObj(srcObjPart);
247
+
248
+ // run translation array against DeepL API
249
+ const translRes = await translateTexts(authKey, translValues, sourceLangCode, targetLangCode, deeplOpts);
250
+
251
+ // re-build object structure from array
252
+ const translObj = __mapArrayToObj(srcObjPart, translRes, 'text');
253
+
254
+ // declare the object we are going to write out, holding the result
255
+ let resultObj;
256
+
257
+ // check if source and target are identical either as string, or resolve in the same file
258
+ if (JsonSrc === JsonTarget
259
+ || (await fst.exists(JsonTarget) &&
260
+ fst.realpath(path.resolve(JsonSrc)) === fst.realpath(path.resolve(JsonTarget)))) {
261
+
262
+ // if the content comes from a nested source
263
+ if (typeof sourceNested === 'string' && sourceNested !== '') {
264
+ // ... traverse in the object and insert or merge the translation
265
+ __setNestedValue(modifiedObj, sourceNested, translObj, targetLangCode);
266
+ } else {
267
+ // ... if not, see if the target node exists
268
+ (modifiedObj[targetLangCode])
269
+ ? Object.assign(modifiedObj[targetLangCode], translObj) // ... and merge data with existing prop
270
+ : modifiedObj[targetLangCode] = translObj; // ... else set a new prop
271
+ }
272
+ resultObj = modifiedObj;
273
+ } else {
274
+ // error if the target file name exists
275
+ if (await fst.exists(JsonTarget))
276
+ throw new Error(`The target file "${JsonTarget}" already exists.
277
+ Please prompt a different file name or remove the existing file.`);
278
+ // ... if ok, set the resulting object
279
+ resultObj = translObj;
280
+ }
281
+
282
+ // log if requested
283
+ if (log || dryRun)
284
+ console.log(resultObj);
285
+
286
+ // write out result if it is not a dry run
287
+ if (!dryRun) {
288
+ const [res, err] = await fst.writeJson(JsonTarget, resultObj);
289
+ if (err) {
290
+ console.error(`Unable to write file: ${JsonTarget}`);
291
+ throw err;
292
+ }
293
+ }
294
+
295
+ return true
296
+ }
297
+
298
+ // Export the functions
299
+ export {
300
+ setAuthKey,
301
+ getSupportedLanguages,
302
+ translateTexts,
303
+ translateToJSON
304
+ };
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import {i18nCollect} from './i18n-collect.js';
2
+ import {setAuthKey, getSupportedLanguages, translateTexts, translateToJSON} from './i18n-deepl.js';
3
+
4
+ export {i18nCollect, setAuthKey, getSupportedLanguages, translateTexts, translateToJSON};