@transifex/cli 3.2.1 → 4.1.0-alpha.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 CHANGED
@@ -1,4 +1,22 @@
1
- # Transifex Native CLI
1
+ <p align="center">
2
+ <a href="https://www.transifex.com">
3
+ <img src="https://raw.githubusercontent.com/transifex/transifex-javascript/master/media/transifex.png" height="60">
4
+ </a>
5
+ </p>
6
+ <p align="center">
7
+ <i>Transifex Native is a full end-to-end, cloud-based localization stack for moderns apps.</i>
8
+ </p>
9
+ <p align="center">
10
+ <img src="https://github.com/transifex/transifex-javascript/actions/workflows/npm-publish.yml/badge.svg">
11
+ <a href="https://www.npmjs.com/package/@transifex/cli">
12
+ <img src="https://img.shields.io/npm/v/@transifex/cli.svg">
13
+ </a>
14
+ <a href="https://developers.transifex.com/docs/native">
15
+ <img src="https://img.shields.io/badge/docs-transifex.com-blue">
16
+ </a>
17
+ </p>
18
+
19
+ # Transifex Native SDK: JavaScript i18n CLI tool
2
20
 
3
21
  A command line tool that parses `.js`, `.ts`, `.jsx`, `.tsx` and `.html` source files, extracts phrases marked for localization by [Transifex Native](https://www.transifex.com/native/) and pushes them to [Transifex](https:/www.transifex.com) for translation.
4
22
 
@@ -8,6 +26,8 @@ Related packages:
8
26
 
9
27
  Learn more about Transifex Native in the [Transifex Developer Hub](https://developers.transifex.com/docs/native).
10
28
 
29
+ ![cli](https://raw.githubusercontent.com/transifex/transifex-javascript/master/media/cli.gif)
30
+
11
31
  # Upgrade to v2
12
32
 
13
33
  If you are upgrading from the `1.x.x` version, please read this [migration guide](https://github.com/transifex/transifex-javascript/blob/HEAD/UPGRADE_TO_V2.md), as there are breaking changes in place.
@@ -93,6 +113,7 @@ OPTIONS
93
113
  --dry-run dry run, do not push to Transifex
94
114
  --key-generator=source|hash [default: source] use hashed or source based keys
95
115
  --no-wait disable polling for upload results
116
+ --parser=auto|i18next [default: auto] file parser to use
96
117
  --purge purge content on Transifex
97
118
  --secret=secret native project secret
98
119
  --token=token native project public token
@@ -125,6 +146,7 @@ DESCRIPTION
125
146
  txjs-cli push --with-tags-only="home,error"
126
147
  txjs-cli push --without-tags-only="custom"
127
148
  txjs-cli push --token=mytoken --secret=mysecret
149
+ txjs-cli push en.json --parser=i18next
128
150
  TRANSIFEX_TOKEN=mytoken TRANSIFEX_SECRET=mysecret txjs-cli push
129
151
  ```
130
152
 
@@ -1 +1 @@
1
- {"version":"3.2.1","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\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":""},"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}"}]}}}
1
+ {"version":"4.1.0-alpha.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,13 +1,16 @@
1
1
  {
2
2
  "name": "@transifex/cli",
3
- "description": "Transifex Native CLI",
4
- "version": "3.2.1",
3
+ "description": "i18n CLI tool for Transifex Native",
4
+ "version": "4.1.0-alpha.0",
5
5
  "author": "Transifex",
6
6
  "keywords": [
7
7
  "transifex",
8
8
  "i18n",
9
9
  "l10n",
10
10
  "localization",
11
+ "internationalization",
12
+ "globalization",
13
+ "translation",
11
14
  "cli"
12
15
  ],
13
16
  "homepage": "https://github.com/transifex/transifex-javascript/tree/master/packages/cli",
@@ -21,19 +24,19 @@
21
24
  "access": "public"
22
25
  },
23
26
  "dependencies": {
24
- "@babel/parser": "^7.17.8",
25
- "@babel/traverse": "^7.13.0",
27
+ "@babel/parser": "^7.17.10",
28
+ "@babel/traverse": "^7.17.10",
26
29
  "@colors/colors": "^1.5.0",
27
30
  "@oclif/command": "^1.8.0",
28
31
  "@oclif/config": "^1.17.0",
29
32
  "@oclif/errors": "^1.3.4",
30
33
  "@oclif/plugin-help": "^5.1.12",
31
- "@transifex/native": "^3.2.1",
34
+ "@transifex/native": "^4.1.0-alpha.0",
32
35
  "angular-html-parser": "^1.7.1",
33
- "axios": "^0.26.1",
36
+ "axios": "^0.27.2",
34
37
  "cli-ux": "^6.0.9",
35
- "ejs": "^3.1.6",
36
- "glob": "^7.1.6",
38
+ "ejs": "^3.1.7",
39
+ "glob": "^8.0.1",
37
40
  "lodash": "^4.17.21",
38
41
  "pug": "^3.0.2",
39
42
  "shelljs": "^0.8.4",
@@ -45,14 +48,14 @@
45
48
  "chai": "^4.3.3",
46
49
  "eslint": "^7.21.0",
47
50
  "eslint-config-airbnb-base": "^14.2.1",
48
- "eslint-plugin-import": "^2.22.1",
49
- "globby": "^11.0.1",
50
- "mocha": "^9.1.3",
51
+ "eslint-plugin-import": "^2.26.0",
52
+ "globby": "^11.1.0",
53
+ "mocha": "^10.0.0",
51
54
  "nock": "^13.0.11",
52
55
  "nyc": "^15.1.0"
53
56
  },
54
57
  "engines": {
55
- "node": ">=12.0.0"
58
+ "node": ">=14.0.0"
56
59
  },
57
60
  "files": [
58
61
  "/bin",
@@ -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
- // Handle simple templates that compile to javascript
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
- } else if (path.extname(file) === '.ejs') {
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
- } else if (path.extname(file) === '.vue') {
56
+ return HASHES;
57
+ }
58
+
59
+ // Vue templates
60
+ if (path.extname(file) === '.vue') {
41
61
  extractVuePhrases(HASHES, source, relativeFile, options);
42
- } else if (path.extname(file) !== '.html') {
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
+ };
@@ -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',