@transifex/cli 3.2.2 → 4.1.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.
- package/README.md +2 -0
- package/oclif.manifest.json +1 -1
- package/package.json +11 -11
- package/src/api/extract.js +26 -5
- package/src/api/parsers/i18next.js +99 -0
- package/src/commands/push.js +7 -0
package/README.md
CHANGED
|
@@ -113,6 +113,7 @@ OPTIONS
|
|
|
113
113
|
--dry-run dry run, do not push to Transifex
|
|
114
114
|
--key-generator=source|hash [default: source] use hashed or source based keys
|
|
115
115
|
--no-wait disable polling for upload results
|
|
116
|
+
--parser=auto|i18next [default: auto] file parser to use
|
|
116
117
|
--purge purge content on Transifex
|
|
117
118
|
--secret=secret native project secret
|
|
118
119
|
--token=token native project public token
|
|
@@ -145,6 +146,7 @@ DESCRIPTION
|
|
|
145
146
|
txjs-cli push --with-tags-only="home,error"
|
|
146
147
|
txjs-cli push --without-tags-only="custom"
|
|
147
148
|
txjs-cli push --token=mytoken --secret=mysecret
|
|
149
|
+
txjs-cli push en.json --parser=i18next
|
|
148
150
|
TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli push
|
|
149
151
|
```
|
|
150
152
|
|
package/oclif.manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"
|
|
1
|
+
{"version":"4.1.0","commands":{"invalidate":{"id":"invalidate","description":"invalidate and refresh CDS cache\nContent for delivery is cached in CDS and refreshed automatically every hour.\nThis command triggers a refresh of cached content on the fly.\n\nBy default, invalidation does not remove existing cached content, but\nstarts the process of updating with latest translations from Transifex.\n\nPassing the --purge option, cached content will be forced to be deleted,\nhowever use that with caution, as it may introduce downtime of\ntranslation delivery to the apps until fresh content is cached in the CDS.\n\nTo invalidate translations some environment variables must be set:\nTRANSIFEX_TOKEN=<Transifex Native Project Token>\nTRANSIFEX_SECRET=<Transifex Native Project Secret>\n(optional) TRANSIFEX_CDS_HOST=<CDS HOST>\n\nor passed as --token=<TOKEN> --secret=<SECRET> parameters\n\nDefault CDS Host is https://cds.svc.transifex.net\n\nExamples:\ntxjs-cli invalidate\ntxjs-cli invalidate --purge\ntxjs-cli invalidate --token=mytoken --secret=mysecret\nTRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli invalidate\n","pluginName":"@transifex/cli","pluginType":"core","aliases":[],"flags":{"purge":{"name":"purge","type":"boolean","description":"force delete CDS cached content","allowNo":false},"token":{"name":"token","type":"option","description":"native project public token","default":""},"secret":{"name":"secret","type":"option","description":"native project secret","default":""},"cds-host":{"name":"cds-host","type":"option","description":"CDS host URL","default":""}},"args":[]},"push":{"id":"push","description":"detect and push source content to Transifex\nParse .js, .ts, .jsx, .tsx and .html files and detect phrases marked for\ntranslation by Transifex Native toolkit for Javascript and\nupload them to Transifex for translation.\n\nTo push content some environment variables must be set:\nTRANSIFEX_TOKEN=<Transifex Native Project Token>\nTRANSIFEX_SECRET=<Transifex Native Project Secret>\n(optional) TRANSIFEX_CDS_HOST=<CDS HOST>\n\nor passed as --token=<TOKEN> --secret=<SECRET> parameters\n\nDefault CDS Host is https://cds.svc.transifex.net\n\nExamples:\ntxjs-cli push -v\ntxjs-cli push src/\ntxjs-cli push /home/repo/src\ntxjs-cli push \"*.js\"\ntxjs-cli push --dry-run\ntxjs-cli push --no-wait\ntxjs-cli push --key-generator=hash\ntxjs-cli push --append-tags=\"master,release:2.5\"\ntxjs-cli push --with-tags-only=\"home,error\"\ntxjs-cli push --without-tags-only=\"custom\"\ntxjs-cli push --token=mytoken --secret=mysecret\ntxjs-cli push en.json --parser=i18next\nTRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli push\n","pluginName":"@transifex/cli","pluginType":"core","aliases":[],"flags":{"dry-run":{"name":"dry-run","type":"boolean","description":"dry run, do not push to Transifex","allowNo":false},"verbose":{"name":"verbose","type":"boolean","char":"v","description":"verbose output","allowNo":false},"purge":{"name":"purge","type":"boolean","description":"purge content on Transifex","allowNo":false},"no-wait":{"name":"no-wait","type":"boolean","description":"disable polling for upload results","allowNo":false},"token":{"name":"token","type":"option","description":"native project public token","default":""},"secret":{"name":"secret","type":"option","description":"native project secret","default":""},"append-tags":{"name":"append-tags","type":"option","description":"append tags to strings","default":""},"with-tags-only":{"name":"with-tags-only","type":"option","description":"push strings with specific tags","default":""},"without-tags-only":{"name":"without-tags-only","type":"option","description":"push strings without specific tags","default":""},"cds-host":{"name":"cds-host","type":"option","description":"CDS host URL","default":""},"parser":{"name":"parser","type":"option","description":"file parser to use","options":["auto","i18next"],"default":"auto"},"key-generator":{"name":"key-generator","type":"option","description":"use hashed or source based keys","options":["source","hash"],"default":"source"}},"args":[{"name":"pattern","description":"file pattern to scan for strings","required":false,"default":"**/*.{js,jsx,ts,tsx,html,vue,pug,ejs}"}]}}}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transifex/cli",
|
|
3
3
|
"description": "i18n CLI tool for Transifex Native",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"author": "Transifex",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"transifex",
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@babel/parser": "^7.17.
|
|
28
|
-
"@babel/traverse": "^7.
|
|
27
|
+
"@babel/parser": "^7.17.10",
|
|
28
|
+
"@babel/traverse": "^7.17.10",
|
|
29
29
|
"@colors/colors": "^1.5.0",
|
|
30
30
|
"@oclif/command": "^1.8.0",
|
|
31
31
|
"@oclif/config": "^1.17.0",
|
|
32
32
|
"@oclif/errors": "^1.3.4",
|
|
33
33
|
"@oclif/plugin-help": "^5.1.12",
|
|
34
|
-
"@transifex/native": "^
|
|
34
|
+
"@transifex/native": "^4.1.0",
|
|
35
35
|
"angular-html-parser": "^1.7.1",
|
|
36
|
-
"axios": "^0.
|
|
36
|
+
"axios": "^0.27.2",
|
|
37
37
|
"cli-ux": "^6.0.9",
|
|
38
|
-
"ejs": "^3.1.
|
|
39
|
-
"glob": "^
|
|
38
|
+
"ejs": "^3.1.7",
|
|
39
|
+
"glob": "^8.0.1",
|
|
40
40
|
"lodash": "^4.17.21",
|
|
41
41
|
"pug": "^3.0.2",
|
|
42
42
|
"shelljs": "^0.8.4",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"chai": "^4.3.3",
|
|
49
49
|
"eslint": "^7.21.0",
|
|
50
50
|
"eslint-config-airbnb-base": "^14.2.1",
|
|
51
|
-
"eslint-plugin-import": "^2.
|
|
52
|
-
"globby": "^11.0
|
|
53
|
-
"mocha": "^
|
|
51
|
+
"eslint-plugin-import": "^2.26.0",
|
|
52
|
+
"globby": "^11.1.0",
|
|
53
|
+
"mocha": "^10.0.0",
|
|
54
54
|
"nock": "^13.0.11",
|
|
55
55
|
"nyc": "^15.1.0"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
|
-
"node": ">=
|
|
58
|
+
"node": ">=14.0.0"
|
|
59
59
|
},
|
|
60
60
|
"files": [
|
|
61
61
|
"/bin",
|
package/src/api/extract.js
CHANGED
|
@@ -9,6 +9,7 @@ const pug = require('pug');
|
|
|
9
9
|
const { parseHTMLTemplateFile } = require('./parsers/angularHTML');
|
|
10
10
|
const { babelExtractPhrases } = require('./parsers/babel');
|
|
11
11
|
const { extractVuePhrases } = require('./parsers/vue');
|
|
12
|
+
const { extractI18NextPhrases } = require('./parsers/i18next');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Parse file and extract phrases using AST
|
|
@@ -20,29 +21,49 @@ const { extractVuePhrases } = require('./parsers/vue');
|
|
|
20
21
|
* @param {String[]} options.filterWithTags
|
|
21
22
|
* @param {String[]} options.filterWithoutTags
|
|
22
23
|
* @param {Boolean} options.useHashedKeys
|
|
24
|
+
* @param {String} options.parser
|
|
23
25
|
* @returns {Object}
|
|
24
26
|
*/
|
|
25
27
|
function extractPhrases(file, relativeFile, options = {}) {
|
|
26
28
|
const HASHES = {};
|
|
27
29
|
let source = fs.readFileSync(file, 'utf8');
|
|
28
30
|
|
|
29
|
-
//
|
|
31
|
+
// i18next JSON
|
|
32
|
+
if (options.parser === 'i18next') {
|
|
33
|
+
extractI18NextPhrases(HASHES, source, relativeFile, options);
|
|
34
|
+
return HASHES;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// PUBG templates
|
|
30
38
|
if (path.extname(file) === '.pug') {
|
|
31
39
|
source = pug.compileClient(source);
|
|
32
|
-
|
|
40
|
+
babelExtractPhrases(HASHES, source, relativeFile, options);
|
|
41
|
+
return HASHES;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// EJS templates
|
|
45
|
+
if (path.extname(file) === '.ejs') {
|
|
33
46
|
const template = new ejs.Template(source);
|
|
34
47
|
template.generateSource();
|
|
35
48
|
source = template.source;
|
|
49
|
+
babelExtractPhrases(HASHES, source, relativeFile, options);
|
|
50
|
+
return HASHES;
|
|
36
51
|
}
|
|
37
52
|
|
|
53
|
+
// HTML templates
|
|
38
54
|
if (path.extname(file) === '.html') {
|
|
39
55
|
parseHTMLTemplateFile(HASHES, file, relativeFile, options);
|
|
40
|
-
|
|
56
|
+
return HASHES;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Vue templates
|
|
60
|
+
if (path.extname(file) === '.vue') {
|
|
41
61
|
extractVuePhrases(HASHES, source, relativeFile, options);
|
|
42
|
-
|
|
43
|
-
babelExtractPhrases(HASHES, source, relativeFile, options);
|
|
62
|
+
return HASHES;
|
|
44
63
|
}
|
|
45
64
|
|
|
65
|
+
// default
|
|
66
|
+
babelExtractPhrases(HASHES, source, relativeFile, options);
|
|
46
67
|
return HASHES;
|
|
47
68
|
}
|
|
48
69
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const { implodePlurals } = require('@transifex/native');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const { mergePayload } = require('../merge');
|
|
4
|
+
const { createPayload, isPayloadValid } = require('./utils');
|
|
5
|
+
|
|
6
|
+
const PLURAL_MAP = {
|
|
7
|
+
_1: 'one',
|
|
8
|
+
_2: 'two',
|
|
9
|
+
_3: 'few',
|
|
10
|
+
_4: 'many',
|
|
11
|
+
_5: 'other',
|
|
12
|
+
_one: 'one',
|
|
13
|
+
_two: 'two',
|
|
14
|
+
_few: 'few',
|
|
15
|
+
_many: 'many',
|
|
16
|
+
_other: 'other',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse i18next JSON v3 & v4 files
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} HASHES A map of keys and content for phrases
|
|
23
|
+
* @param {String} source The content we want to parse
|
|
24
|
+
* @param {String} relativeFile occurence
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {String[]} options.appendTags
|
|
27
|
+
* @param {String[]} options.filterWithTags
|
|
28
|
+
* @param {String[]} options.filterWithoutTags
|
|
29
|
+
* @param {Boolean} options.useHashedKeys
|
|
30
|
+
*/
|
|
31
|
+
function extractI18NextPhrases(HASHES, source, relativeFile, options) {
|
|
32
|
+
let json = {};
|
|
33
|
+
try {
|
|
34
|
+
json = JSON.parse(source);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const plurals = {};
|
|
40
|
+
|
|
41
|
+
_.each(json, (string, key) => {
|
|
42
|
+
// nesting
|
|
43
|
+
if (!_.isString(string) && !_.isArray(string) && _.isObject(string)) {
|
|
44
|
+
_.each(string, (innerString, innerKey) => {
|
|
45
|
+
if (!_.isString(innerString)) return;
|
|
46
|
+
const partial = createPayload(innerString, {}, relativeFile, options);
|
|
47
|
+
if (!isPayloadValid(partial, options)) return;
|
|
48
|
+
|
|
49
|
+
mergePayload(HASHES, {
|
|
50
|
+
[`${key}.${innerKey}`]: {
|
|
51
|
+
string: partial.string,
|
|
52
|
+
meta: partial.meta,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!_.isString(string)) return;
|
|
60
|
+
|
|
61
|
+
let isPlural = false;
|
|
62
|
+
_.each(_.keys(PLURAL_MAP), (suffix) => {
|
|
63
|
+
if (_.endsWith(key, suffix)) {
|
|
64
|
+
isPlural = true;
|
|
65
|
+
const pluralKey = key.slice(0, -suffix.length);
|
|
66
|
+
plurals[pluralKey] = plurals[pluralKey] || {};
|
|
67
|
+
plurals[pluralKey][PLURAL_MAP[suffix]] = string;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (isPlural) return;
|
|
71
|
+
|
|
72
|
+
const partial = createPayload(string, {}, relativeFile, options);
|
|
73
|
+
if (!isPayloadValid(partial, options)) return;
|
|
74
|
+
|
|
75
|
+
mergePayload(HASHES, {
|
|
76
|
+
[key]: {
|
|
77
|
+
string: partial.string,
|
|
78
|
+
meta: partial.meta,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// add plurals
|
|
84
|
+
_.each(plurals, (plural, key) => {
|
|
85
|
+
const partial = createPayload(implodePlurals(plural, 'count'), {}, relativeFile, options);
|
|
86
|
+
if (!isPayloadValid(partial, options)) return;
|
|
87
|
+
|
|
88
|
+
mergePayload(HASHES, {
|
|
89
|
+
[`${key}_txplural`]: {
|
|
90
|
+
string: partial.string,
|
|
91
|
+
meta: partial.meta,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
extractI18NextPhrases,
|
|
99
|
+
};
|
package/src/commands/push.js
CHANGED
|
@@ -73,6 +73,7 @@ class PushCommand extends Command {
|
|
|
73
73
|
filterWithTags,
|
|
74
74
|
filterWithoutTags,
|
|
75
75
|
useHashedKeys,
|
|
76
|
+
parser: flags.parser,
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
_.each(allFiles, (file) => {
|
|
@@ -255,6 +256,7 @@ txjs-cli push --append-tags="master,release:2.5"
|
|
|
255
256
|
txjs-cli push --with-tags-only="home,error"
|
|
256
257
|
txjs-cli push --without-tags-only="custom"
|
|
257
258
|
txjs-cli push --token=mytoken --secret=mysecret
|
|
259
|
+
txjs-cli push en.json --parser=i18next
|
|
258
260
|
TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli push
|
|
259
261
|
`;
|
|
260
262
|
|
|
@@ -307,6 +309,11 @@ PushCommand.flags = {
|
|
|
307
309
|
description: 'CDS host URL',
|
|
308
310
|
default: '',
|
|
309
311
|
}),
|
|
312
|
+
parser: flags.string({
|
|
313
|
+
description: 'file parser to use',
|
|
314
|
+
default: 'auto',
|
|
315
|
+
options: ['auto', 'i18next'],
|
|
316
|
+
}),
|
|
310
317
|
'key-generator': flags.string({
|
|
311
318
|
description: 'use hashed or source based keys',
|
|
312
319
|
default: 'source',
|