i18nexus-cli 3.6.1 → 3.8.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 +22 -6
- package/bin/index.js +11 -3
- package/commands/listen.js +48 -96
- package/commands/pull.js +4 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ If you wish to download your files to a different directory, you can use the `--
|
|
|
92
92
|
| `--ver` or `-v` | `latest` |
|
|
93
93
|
| `--confirmed` | `false` |
|
|
94
94
|
| `--clean` | `false` |
|
|
95
|
+
| `--compact` | `false` |
|
|
95
96
|
|
|
96
97
|
### Notes
|
|
97
98
|
|
|
@@ -110,6 +111,9 @@ Downloads only translations that have been confirmed in i18nexus
|
|
|
110
111
|
`--clean`
|
|
111
112
|
Before download, clears your destination folder specified in --path. As a safety precaution, this only deletes folders with names that match a simple language code regex. You should still ensure you are not storing any files in your destination folder that you do not want deleted.
|
|
112
113
|
|
|
114
|
+
`--compact`
|
|
115
|
+
Output JSON without extra whitespace (default is pretty-printed)
|
|
116
|
+
|
|
113
117
|
## Personal Access Tokens
|
|
114
118
|
|
|
115
119
|
The following commands require a Personal Access Token (PAT) because they write data to your i18nexus project. PATs are created in your i18nexus account dashboard.
|
|
@@ -315,9 +319,20 @@ A personal access token that you have generated in your i18nexus account (Can al
|
|
|
315
319
|
i18nexus listen -k <PROJECT_API_KEY>
|
|
316
320
|
```
|
|
317
321
|
|
|
318
|
-
This command
|
|
322
|
+
This command opens a WebSocket connection from your local environment to the i18nexus servers to receive real-time updates whenever strings are added, updated, or deleted in your project.
|
|
323
|
+
|
|
324
|
+
When changes occur, your local JSON files are automatically refreshed to stay in sync.
|
|
325
|
+
|
|
326
|
+
Many developers prefer to run this automatically alongside their development server. For example, in a Next.js project, you can do the following in your `package.json` using [concurrently](https://www.npmjs.com/package/concurrently):
|
|
319
327
|
|
|
320
|
-
|
|
328
|
+
```json
|
|
329
|
+
"scripts": {
|
|
330
|
+
"dev": "concurrently --raw \"next dev --turbo\" \"i18nexus listen\"",
|
|
331
|
+
"build": "i18nexus pull && next build",
|
|
332
|
+
"start": "i18nexus pull && next start",
|
|
333
|
+
"lint": "next lint"
|
|
334
|
+
}
|
|
335
|
+
```
|
|
321
336
|
|
|
322
337
|
### Options
|
|
323
338
|
|
|
@@ -325,14 +340,15 @@ When a string is added, updated, or deleted in your project, a webhook is sent t
|
|
|
325
340
|
| ------------------- | --------- | ----------------------- |
|
|
326
341
|
| `--api-key` or `-k` | ✔ | |
|
|
327
342
|
| `--path` or `-p` | | See `pull` default path |
|
|
328
|
-
| `--
|
|
343
|
+
| `--compact` | | `false` |
|
|
329
344
|
|
|
330
345
|
### Notes
|
|
331
346
|
|
|
332
347
|
`--api-key`
|
|
333
348
|
Your project API key (Can also be set using environment variable `I18NEXUS_API_KEY`)
|
|
334
349
|
|
|
335
|
-
`--
|
|
336
|
-
The
|
|
350
|
+
`--path`
|
|
351
|
+
The path to the destination folder in which translation files will be downloaded
|
|
337
352
|
|
|
338
|
-
|
|
353
|
+
`--compact`
|
|
354
|
+
Output JSON without extra whitespace (default is pretty-printed)
|
package/bin/index.js
CHANGED
|
@@ -47,12 +47,17 @@ program
|
|
|
47
47
|
'Removes and rebuilds destination folder before download',
|
|
48
48
|
false
|
|
49
49
|
)
|
|
50
|
+
.option(
|
|
51
|
+
'--compact',
|
|
52
|
+
'Output JSON without extra whitespace (default is pretty-printed)'
|
|
53
|
+
)
|
|
50
54
|
.action(options => {
|
|
51
55
|
pull({
|
|
52
56
|
apiKey: options.apiKey,
|
|
53
57
|
version: options.ver,
|
|
54
58
|
path: options.path,
|
|
55
59
|
clean: options.clean,
|
|
60
|
+
compact: options.compact,
|
|
56
61
|
confirmed: options.confirmed
|
|
57
62
|
});
|
|
58
63
|
});
|
|
@@ -268,12 +273,15 @@ program
|
|
|
268
273
|
'-p, --path <path>',
|
|
269
274
|
'The path to the destination folder in which translation files will be downloaded'
|
|
270
275
|
)
|
|
271
|
-
.option(
|
|
276
|
+
.option(
|
|
277
|
+
'--compact',
|
|
278
|
+
'Output JSON without extra whitespace (default is pretty-printed)'
|
|
279
|
+
)
|
|
272
280
|
.action(options => {
|
|
273
281
|
listen({
|
|
274
282
|
apiKey: options.apiKey,
|
|
275
|
-
|
|
276
|
-
|
|
283
|
+
path: options.path,
|
|
284
|
+
compact: options.compact
|
|
277
285
|
});
|
|
278
286
|
});
|
|
279
287
|
|
package/commands/listen.js
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const http = require('http');
|
|
5
|
-
const localtunnel = require('localtunnel');
|
|
2
|
+
const WebSocket = require('ws');
|
|
6
3
|
const colors = require('colors');
|
|
7
|
-
const handleFetch = require('../handleFetch');
|
|
8
|
-
const baseUrl = require('../baseUrl');
|
|
9
4
|
const pull = require('../commands/pull');
|
|
5
|
+
const baseUrl = require('../baseUrl');
|
|
6
|
+
const { URL } = require('url');
|
|
10
7
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const listen = async ({ apiKey, port, path }) => {
|
|
14
|
-
port = Number(port);
|
|
15
|
-
|
|
16
|
-
if (!Number.isInteger(port) || port <= 0) {
|
|
17
|
-
console.log(colors.red('Invalid port'));
|
|
18
|
-
return process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
8
|
+
const listen = async ({ apiKey, path, compact = false }) => {
|
|
21
9
|
await pull(
|
|
22
10
|
{
|
|
23
11
|
apiKey,
|
|
24
12
|
version: 'latest',
|
|
25
|
-
path
|
|
13
|
+
path,
|
|
26
14
|
clean: false,
|
|
27
|
-
confirmed: false
|
|
15
|
+
confirmed: false,
|
|
16
|
+
compact
|
|
28
17
|
},
|
|
29
18
|
{
|
|
30
19
|
logging: false,
|
|
@@ -32,105 +21,68 @@ const listen = async ({ apiKey, port, path }) => {
|
|
|
32
21
|
}
|
|
33
22
|
);
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
const wsUrl = new URL(`${baseUrl.replace(/^http/, 'ws')}/cable`);
|
|
25
|
+
wsUrl.searchParams.set('api_key', apiKey);
|
|
26
|
+
|
|
27
|
+
const ws = new WebSocket(wsUrl.toString(), {
|
|
28
|
+
headers: {
|
|
29
|
+
Origin: 'http://cli.i18nexus.com'
|
|
40
30
|
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
ws.on('open', () => {
|
|
34
|
+
console.log(colors.green('Listening for i18nexus string updates...'));
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
ws.send(
|
|
37
|
+
JSON.stringify({
|
|
38
|
+
command: 'subscribe',
|
|
39
|
+
identifier: JSON.stringify({ channel: 'CliListenChannel' })
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
ws.on('message', async message => {
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(message);
|
|
47
|
+
const payload = data.message;
|
|
48
|
+
|
|
49
|
+
if (payload?.event === 'strings.changed') {
|
|
46
50
|
await pull(
|
|
47
51
|
{
|
|
48
52
|
apiKey,
|
|
49
53
|
version: 'latest',
|
|
50
|
-
path
|
|
54
|
+
path,
|
|
51
55
|
clean: false,
|
|
52
|
-
confirmed: false
|
|
56
|
+
confirmed: false,
|
|
57
|
+
compact
|
|
53
58
|
},
|
|
54
59
|
{
|
|
55
60
|
logging: false,
|
|
56
61
|
successLog: ' ✔ Translations updated'
|
|
57
62
|
}
|
|
58
63
|
);
|
|
59
|
-
|
|
60
|
-
res.writeHead(200).end('ok');
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.error(colors.red('Failed to download translations:'), e);
|
|
63
|
-
res.writeHead(500).end('error');
|
|
64
64
|
}
|
|
65
|
-
})
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error(colors.red('i18nexus Sync Failed'));
|
|
67
|
+
}
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
server.listen(port, async () => {
|
|
71
|
-
res(await localtunnel({ port }));
|
|
72
|
-
});
|
|
70
|
+
ws.on('close', () => {
|
|
71
|
+
process.exit(1);
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
`${baseUrl}/project_resources/dev_webhooks`,
|
|
80
|
-
{
|
|
81
|
-
method: 'POST',
|
|
82
|
-
headers: { 'Content-Type': 'application/json' },
|
|
83
|
-
body: JSON.stringify({ api_key: apiKey, url: publicUrl })
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
let closing = false;
|
|
88
|
-
|
|
89
|
-
/* ────────────── graceful shutdown ────────────── */
|
|
90
|
-
const cleanUp = async (exitCode = 0) => {
|
|
91
|
-
if (closing) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
closing = true;
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const { collection } = await createRes.json();
|
|
99
|
-
const webhookId = collection[0].id;
|
|
74
|
+
ws.on('error', err => {
|
|
75
|
+
console.error(colors.red('i18nexus Connection Error'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
100
78
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
body: JSON.stringify({ api_key: apiKey, id: webhookId })
|
|
105
|
-
});
|
|
106
|
-
} catch (e) {}
|
|
107
|
-
tunnel.close();
|
|
108
|
-
server.close();
|
|
109
|
-
process.exit(exitCode);
|
|
79
|
+
const cleanUp = () => {
|
|
80
|
+
ws.close();
|
|
81
|
+
process.exit(0);
|
|
110
82
|
};
|
|
111
83
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
cleanUp(1);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
console.log(colors.green('Listening for i18nexus updates…'));
|
|
119
|
-
|
|
120
|
-
process.on('SIGINT', () => cleanUp());
|
|
121
|
-
process.on('SIGTERM', () => cleanUp());
|
|
122
|
-
|
|
123
|
-
let connectionLost = false;
|
|
124
|
-
tunnel.on('error', () => {
|
|
125
|
-
if (connectionLost) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
connectionLost = true;
|
|
129
|
-
|
|
130
|
-
console.error(colors.red('i18nexus listener connection lost'));
|
|
131
|
-
|
|
132
|
-
cleanUp(1);
|
|
133
|
-
});
|
|
84
|
+
process.on('SIGINT', cleanUp);
|
|
85
|
+
process.on('SIGTERM', cleanUp);
|
|
134
86
|
};
|
|
135
87
|
|
|
136
88
|
module.exports = listen;
|
package/commands/pull.js
CHANGED
|
@@ -127,13 +127,15 @@ const pull = async (opt, internalOptions = {}) => {
|
|
|
127
127
|
cleanDirectory(path);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
const spacing = opt.compact ? 0 : 2;
|
|
131
|
+
|
|
130
132
|
if (projectLibrary === 'next-intl') {
|
|
131
133
|
for (let lng in translations) {
|
|
132
134
|
fs.mkdirSync(path, { recursive: true });
|
|
133
135
|
|
|
134
136
|
fs.writeFileSync(
|
|
135
137
|
`${path}/${lng}.json`,
|
|
136
|
-
JSON.stringify(translations[lng])
|
|
138
|
+
JSON.stringify(translations[lng], null, spacing)
|
|
137
139
|
);
|
|
138
140
|
}
|
|
139
141
|
} else {
|
|
@@ -144,7 +146,7 @@ const pull = async (opt, internalOptions = {}) => {
|
|
|
144
146
|
for (let namespace in translations[lng]) {
|
|
145
147
|
fs.writeFileSync(
|
|
146
148
|
`${lngFilePath}/${namespace}.json`,
|
|
147
|
-
JSON.stringify(translations[lng][namespace])
|
|
149
|
+
JSON.stringify(translations[lng][namespace], null, spacing)
|
|
148
150
|
);
|
|
149
151
|
}
|
|
150
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18nexus-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Command line interface (CLI) for accessing the i18nexus API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"colors": "^1.4.0",
|
|
21
21
|
"commander": "^7.2.0",
|
|
22
22
|
"https-proxy-agent": "^5.0.0",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"node-fetch": "^2.6.7",
|
|
24
|
+
"ws": "^8.18.3"
|
|
25
25
|
}
|
|
26
26
|
}
|