google-spreadsheet-translation-sync 1.4.0 → 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
@@ -101,6 +101,7 @@ Possible Values:
101
101
  * `locale_json` (translations are stored in simple key/value json files)
102
102
  * `json_structure` (translations are stored in a json tree, where the keys are split by the . character)
103
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
104
105
  * `properties` (utilizes [propertie-reader](https://github.com/steveukx/properties) for java property files)
105
106
  * `yml` (utilizes [js-yaml](https://github.com/nodeca/js-yaml) for symfony yaml translation files)
106
107
  Default value: `locale_json`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-spreadsheet-translation-sync",
3
- "version": "1.4.0",
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": {
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
+ }
@@ -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,7 +53,7 @@ function ensureFolder (folder) {
53
53
  }
54
54
 
55
55
 
56
- // const testFor = 'all' // 'connect', 'upload', 'import', 'properties', 'locale_json', 'gettext', 'yml', 'json_structure'
56
+ // const testFor = 'all' // 'connect', 'upload', 'import', 'properties', 'locale_json', 'gettext', 'yml', 'json_structure', 'wordpress'
57
57
  // const testFor = 'gettext';
58
58
  const testFor = 'all';
59
59
 
@@ -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'],