google-spreadsheet-translation-sync 1.3.19 → 1.4.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/README.md CHANGED
@@ -65,6 +65,12 @@ Default value: 'default'
65
65
 
66
66
  If no locale is provided or could be parsed, the defaultLocaleName is used instead.
67
67
 
68
+ #### options.defaultFallback
69
+ Type: `Boolean`
70
+ Default value: `false`
71
+
72
+ Fills empty collumns automatically with the value from the defaultLocale - usefull if always complete translations have to be provided per file.
73
+
68
74
  #### options.fileBaseName
69
75
  Type: `String`
70
76
  Default value: (empty string)
@@ -95,6 +101,7 @@ Possible Values:
95
101
  * `locale_json` (translations are stored in simple key/value json files)
96
102
  * `json_structure` (translations are stored in a json tree, where the keys are split by the . character)
97
103
  * `gettext` (utilizes [node gettext-parser](https://github.com/smhg/gettext-parser) for the work with po and mo files)
104
+ * `wordpress` (utilizes [node gettext-parser](https://github.com/smhg/gettext-parser) for the work with po and mo files) and adds additionally the new wordpress 6.5+ `.l10n.php` structure for faster parsing
98
105
  * `properties` (utilizes [propertie-reader](https://github.com/steveukx/properties) for java property files)
99
106
  * `yml` (utilizes [js-yaml](https://github.com/nodeca/js-yaml) for symfony yaml translation files)
100
107
  Default value: `locale_json`
package/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * The options type
5
+ * @typedef {{translationFormat: string, mode: string, spreadsheetId: string, gid : string, credentials: {}, keyId: string, fileBaseName: string, namespaces: boolean, defaultLocaleName: string, defaultFallback: boolean}} OptionsObject
6
+ */
7
+
3
8
  /**
4
9
  * Adds commas to a number
5
10
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-spreadsheet-translation-sync",
3
- "version": "1.3.19",
3
+ "version": "1.4.1",
4
4
  "description": "A plugin to read and write i18n translationsfrom and to google spreadsheets ",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,7 +5,7 @@
5
5
 
6
6
  /**
7
7
  * @param {[]} translationFiles - an array of files
8
- * @param {{translationFormat: string, mode: string, spreadsheetId: string, gid : string, credentials: {}, keyId: string, fileBaseName: string, namespaces: boolean, defaultLocaleName: string}} options
8
+ * @param {OptionsObject} options
9
9
  * @param {function} callback
10
10
  */
11
11
  module.exports = function (translationFiles, options, callback) {
package/src/handler.js CHANGED
@@ -23,5 +23,7 @@ module.exports.getHandler = function (translationFormat) {
23
23
  case 'yaml':
24
24
  case TRANSLATION_FORMATS.YAML:
25
25
  return require('./handlers/yaml');
26
+ case TRANSLATION_FORMATS.WORDPRESS:
27
+ return require('./handlers/wordpress');
26
28
  }
27
29
  }
@@ -0,0 +1,173 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs');
4
+ const withoutError = require('../helpers').withoutError
5
+ const gettextParser = require("gettext-parser");
6
+ const constraints = require('../util/constraints');
7
+
8
+
9
+ module.exports.loadTranslationFile = function (filePath, callback) {
10
+
11
+ fs.access(filePath, function (err) {
12
+
13
+ if (err) {
14
+ callback({}); // we return the empty json
15
+ } else {
16
+
17
+ fs.readFile(filePath, function (err, data) {
18
+
19
+ if (err) {
20
+ callback({});
21
+ } else {
22
+ callback(gettextParser.po.parse(data));
23
+ }
24
+ })
25
+ }
26
+ });
27
+ }
28
+
29
+ /**
30
+ * A wrapper to return a flat key: value object structure, used for uploading
31
+ * @param filePath
32
+ * @param callback
33
+ */
34
+ module.exports.getTranslationKeys = function (filePath, callback) {
35
+
36
+ const translations = {};
37
+
38
+ this.loadTranslationFile(filePath, function (parsedObj) {
39
+ if (parsedObj.translations) {
40
+ Object.keys(parsedObj.translations['']).forEach(function (key) {
41
+ translations[key] = parsedObj.translations[''][key].msgstr;
42
+ });
43
+ }
44
+
45
+ callback(translations);
46
+ })
47
+ }
48
+
49
+
50
+ module.exports.updateTranslations = function (translationData, translationRootFolder, options, callback) {
51
+ const path = require('path');
52
+ const async = require('async');
53
+ const mod = this;
54
+ const fileUtils = require('../util/file-utils');
55
+
56
+ if (! fs.existsSync(translationRootFolder)) {
57
+ throw new Error('The folder ' + translationRootFolder + ' does not exist');
58
+ }
59
+
60
+ async.each(Object.keys(translationData), function(locale, done) {
61
+
62
+ // is it a comment or a real translation?
63
+ if (locale.substr(0, constraints.commentCollumnName.length) !== constraints.commentCollumnName) {
64
+
65
+ async.each(Object.keys(translationData[locale]), function (namespace, done2) {
66
+
67
+ const localeFileName = fileUtils.buildTranslationFileName(constraints.TRANSLATION_FORMATS.GETTEXT, namespace, locale, options);
68
+ const file = path.resolve(translationRootFolder + '/' + localeFileName);
69
+ const moFile = path.resolve(translationRootFolder + '/' + localeFileName.replace('.po', '.mo'));
70
+ const phpFile = path.resolve(translationRootFolder + '/' + localeFileName.replace('.po', '.l10n.php'));
71
+
72
+ mod.loadTranslationFile(file, function (parsedObj) {
73
+
74
+ // do we have a file?
75
+ if (!parsedObj.translations) {
76
+ parsedObj = {
77
+ "charset": "UTF-8",
78
+
79
+ "headers": {
80
+ "content-type": "text/plain; charset=UTF-8",
81
+ "plural-forms": "nplurals=2; plural=(n!=1);",
82
+ "X-Generator": "node-google-spreadsheet-translation-sync",
83
+ "Project-Id-Version": options.fileBaseName,
84
+ "Language": locale
85
+ },
86
+
87
+ "translations": {
88
+ "": {}
89
+ }
90
+ }
91
+ }
92
+
93
+ const potentiallyUpdatedTranslations = translationData[locale][namespace];
94
+
95
+ if (potentiallyUpdatedTranslations) {
96
+ // update our object
97
+ Object.keys(potentiallyUpdatedTranslations).forEach(function (key, index) {
98
+ if (!parsedObj.translations[''][key]) {
99
+ parsedObj.translations[''][key] = {
100
+ "msgid": key,
101
+ "msgstr": [],
102
+ "comments": {}
103
+ }
104
+ }
105
+
106
+ parsedObj.translations[''][key].msgstr[0] = potentiallyUpdatedTranslations[key];
107
+
108
+ // do we have a comment?
109
+ if (translationData[constraints.commentCollumnName] && translationData[constraints.commentCollumnName][key]) {
110
+ if (!parsedObj.translations[''][key].comments) {
111
+ parsedObj.translations[''][key].comments = {};
112
+ }
113
+
114
+ parsedObj.translations[''][key].comments.translator = translationData[constraints.commentCollumnName][key];
115
+ }
116
+ });
117
+
118
+ // now we write
119
+
120
+ const output = gettextParser.po.compile(parsedObj, {sort: true});
121
+ fs.writeFileSync(file, output);
122
+
123
+ const mo = gettextParser.mo.compile(parsedObj);
124
+ fs.writeFileSync(moFile, mo);
125
+
126
+ // write the additional .i10n.php file
127
+
128
+ if (fs.existsSync(phpFile)) {
129
+ fs.unlinkSync(phpFile);
130
+ }
131
+
132
+ let phpContent = '<?php\n';
133
+ phpContent += 'return [\n';
134
+
135
+ Object.keys(parsedObj.headers).forEach((header) => {
136
+ phpContent += ` "${header.toLocaleLowerCase()}" => ${JSON.stringify(parsedObj.headers[header])},\n`;
137
+ })
138
+
139
+ phpContent += ` "messages" => [\n`;
140
+
141
+ Object.keys(parsedObj.translations['']).forEach((key) => {
142
+ if (key) {
143
+ phpContent += ` "${key}" => ${JSON.stringify(parsedObj.translations[''][key].msgstr[0])},\n`;
144
+ }
145
+ });
146
+
147
+ phpContent += ' ]\n';
148
+ phpContent += '];\n';
149
+
150
+ fs.writeFileSync(phpFile, phpContent);
151
+
152
+ // console.info('Updated translations of %o', localeFileName);
153
+ } else {
154
+ // console.info('Ignored unchanged %o', localeFileName);
155
+ }
156
+
157
+ done2();
158
+ });
159
+
160
+ }, function () {
161
+ done();
162
+ })
163
+ } else {
164
+ done();
165
+ }
166
+ }, function (err) {
167
+ if (withoutError(err, callback)) {
168
+ callback(null);
169
+ }
170
+ });
171
+
172
+
173
+ }
@@ -1,5 +1,10 @@
1
1
  'use strict'
2
-
2
+ /**
3
+ *
4
+ * @param translationRootFolder
5
+ * @param {OptionsObject} options
6
+ * @param callback
7
+ */
3
8
  module.exports = function (translationRootFolder, options, callback) {
4
9
 
5
10
  const connector = require('./connector');
@@ -74,6 +79,26 @@ module.exports = function (translationRootFolder, options, callback) {
74
79
  }
75
80
  }
76
81
 
82
+ if (options.defaultFallback) {
83
+
84
+ const defaultLocale = options.defaultLocaleName || 'default';
85
+
86
+ Object.keys(translationData).forEach(locale => {
87
+ // use the default locale as reference
88
+ Object.keys(translationData[defaultLocale]).forEach(namespace => {
89
+ if (!translationData[locale][namespace]) {
90
+ translationData[locale][namespace] = {};
91
+ }
92
+
93
+ Object.keys(translationData[defaultLocale][namespace]).forEach(key => {
94
+ if (translationData[locale][namespace][key] === undefined) {
95
+ translationData[locale][namespace][key] = translationData[defaultLocale][namespace][key];
96
+ }
97
+ });
98
+ });
99
+ });
100
+ }
101
+
77
102
  // now we get the handler
78
103
  const h = require('./handler');
79
104
  const TRANSLATION_FORMATS = require('./util/constraints').TRANSLATION_FORMATS;
@@ -2,6 +2,7 @@ const formats = {
2
2
  LOCALE_JSON: 'locale_json', // just a plain array - lorem.ipsum keys will be {'lorem.ipsum': 'whatever'}
3
3
  JSON_STRUCTURE: 'json_structure', // lorem.ipsum keys will be converted into a json structure: {lorem: {ipsum: 'whatever'}}
4
4
  GETTEXT: 'gettext',
5
+ WORDPRESS: 'wordpress',
5
6
  PROPERTIES: 'properties',
6
7
  YAML: 'yml'
7
8
  };
package/test/test.js CHANGED
@@ -53,8 +53,8 @@ function ensureFolder (folder) {
53
53
  }
54
54
 
55
55
 
56
- // const testFor = 'all' // 'connect', 'upload', 'import', 'properties', 'locale_json', 'gettext', 'yml', 'json_structure'
57
- // const testFor = 'json_structure';
56
+ // const testFor = 'all' // 'connect', 'upload', 'import', 'properties', 'locale_json', 'gettext', 'yml', 'json_structure', 'wordpress'
57
+ // const testFor = 'gettext';
58
58
  const testFor = 'all';
59
59
 
60
60
  const tests = [
@@ -370,6 +370,74 @@ const tests = [
370
370
  }
371
371
  },
372
372
 
373
+ {
374
+ name: 'should import updated wordpress keys in the test project',
375
+ run: ['import', 'wordpress'],
376
+ fnc: function (done) {
377
+ this.timeout(timeout);
378
+
379
+ const baseName = 'karmapa-chenno';
380
+
381
+ const options = {
382
+ translationFormat: 'wordpress',
383
+ fileBaseName: baseName,
384
+ spreadsheetId: testSheetId_gettext,
385
+ credentials: accessData
386
+ };
387
+
388
+ const translationRoot = ensureFolder(path.resolve('./test/translations/' + options.translationFormat + '/'));
389
+ const testFile = path.resolve(translationRoot + '/' + baseName + '-en.po');
390
+ const testFilePhp = path.resolve(translationRoot + '/' + baseName + '-en.l10n.php');
391
+ const testFilePl = path.resolve(translationRoot + '/' + baseName + '-pl.po');
392
+
393
+ app.importFromSpreadsheet(translationRoot, options, function (err) {
394
+ expect(err).to.be.null
395
+
396
+ if (!err) {
397
+ const fs = require('fs');
398
+
399
+ expect(fs.existsSync(testFile)).to.equal(true);
400
+
401
+ const po = require('gettext-parser').po.parse(fs.readFileSync(testFile));
402
+ const poPl = require('gettext-parser').po.parse(fs.readFileSync(testFilePl));
403
+ const translations = po.translations[''];
404
+ const translationsPl = poPl.translations[''];
405
+ expect(translations.add_address.msgstr[0]).to.equal("Add new address");
406
+ expect(translationsPl['additional.news'].msgstr[0]).to.equal('czecz 2242');
407
+ expect(translationsPl.add_address).to.be.undefined;
408
+
409
+ const phpContent = fs.readFileSync(testFilePhp).toString();
410
+
411
+ expect(phpContent).to.equal('<?php\n' +
412
+ 'return [\n' +
413
+ ' "content-type" => "text/plain; charset=utf-8",\n' +
414
+ ' "plural-forms" => "nplurals=2; plural=(n!=1);",\n' +
415
+ ' "x-generator" => "node-google-spreadsheet-translation-sync",\n' +
416
+ ' "project-id-version" => "karmapa-chenno",\n' +
417
+ ' "language" => "en",\n' +
418
+ ' "messages" => [\n' +
419
+ ' "add_address" => "Add new address",\n' +
420
+ ' "add_as_favourite" => "Add to Quick Contacts",\n' +
421
+ ' "add_center" => "Add Center",\n' +
422
+ ' "add_contact" => "Add contact",\n' +
423
+ ' "add_phone_number" => "Add new phone",\n' +
424
+ ' "additional.news" => "Additional News",\n' +
425
+ ' "administration" => "Administration",\n' +
426
+ ' "administration_info" => "This page allows you to access functions connected to Members, Centers, Roles and Groups.",\n' +
427
+ ' "all" => "All",\n' +
428
+ ' "all_centers" => "All Centers",\n' +
429
+ ' "all_contacts" => "All contacts",\n' +
430
+ ' "some.key" => "a Key 370",\n' +
431
+ ' ]\n' +
432
+ '];\n');
433
+
434
+ }
435
+
436
+ done();
437
+ });
438
+ }
439
+ },
440
+
373
441
  {
374
442
  name: 'should import updated gettext keys in the test project',
375
443
  run: ['import', 'gettext'],
@@ -403,6 +471,49 @@ const tests = [
403
471
  const translationsPl = poPl.translations[''];
404
472
  expect(translations.add_address.msgstr[0]).to.equal("Add new address");
405
473
  expect(translationsPl['additional.news'].msgstr[0]).to.equal('czecz 2242');
474
+ expect(translationsPl.add_address).to.be.undefined;
475
+ }
476
+
477
+ done();
478
+ });
479
+ }
480
+ },
481
+
482
+ {
483
+ name: 'should fill empty columns with the default',
484
+ run: ['import', 'gettext'],
485
+ fnc: function (done) {
486
+ this.timeout(timeout);
487
+
488
+ const baseName = 'om-mani-peme';
489
+
490
+ const options = {
491
+ translationFormat: 'gettext',
492
+ fileBaseName: baseName,
493
+ spreadsheetId: testSheetId_gettext,
494
+ credentials: accessData,
495
+ defaultLocaleName: 'en',
496
+ defaultFallback: true
497
+ };
498
+
499
+ const translationRoot = ensureFolder(path.resolve('./test/translations/' + options.translationFormat + '/'));
500
+ const testFile = path.resolve(translationRoot + '/' + baseName + '-en.po');
501
+ const testFileDe = path.resolve(translationRoot + '/' + baseName + '-de.po');
502
+
503
+ app.importFromSpreadsheet(translationRoot, options, function (err) {
504
+ expect(err).to.be.null
505
+
506
+ if (!err) {
507
+ const fs = require('fs');
508
+
509
+ expect(fs.existsSync(testFile)).to.equal(true);
510
+
511
+ const po = require('gettext-parser').po.parse(fs.readFileSync(testFile));
512
+ const poDe = require('gettext-parser').po.parse(fs.readFileSync(testFileDe));
513
+ const translations = po.translations[''];
514
+ const translationsDe = poDe.translations[''];
515
+ expect(translations['additional.news'].msgstr[0]).to.equal("Additional News");
516
+ expect(translationsDe['additional.news']?.msgstr[0]).to.equal('Additional News');
406
517
  }
407
518
 
408
519
  done();