i18nexus-cli 3.6.1 → 3.7.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
@@ -315,9 +315,20 @@ A personal access token that you have generated in your i18nexus account (Can al
315
315
  i18nexus listen -k <PROJECT_API_KEY>
316
316
  ```
317
317
 
318
- This command starts a tunnel to your local development server using [localtunnel](https://www.npmjs.com/package/localtunnel), allowing i18nexus to send real-time webhooks for translation updates.
318
+ 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.
319
319
 
320
- When a string is added, updated, or deleted in your project, a webhook is sent to your local server, automatically updating your local JSON files.
320
+ When changes occur, your local JSON files are automatically refreshed to stay in sync.
321
+
322
+ 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):
323
+
324
+ ```json
325
+ "scripts": {
326
+ "dev": "concurrently --raw \"next dev --turbo\" \"i18nexus listen\"",
327
+ "build": "i18nexus pull && next build",
328
+ "start": "i18nexus pull && next start",
329
+ "lint": "next lint"
330
+ }
331
+ ```
321
332
 
322
333
  ### Options
323
334
 
@@ -325,14 +336,11 @@ When a string is added, updated, or deleted in your project, a webhook is sent t
325
336
  | ------------------- | --------- | ----------------------- |
326
337
  | `--api-key` or `-k` | &#10004; | |
327
338
  | `--path` or `-p` | | See `pull` default path |
328
- | `--port` | | '3002' |
329
339
 
330
340
  ### Notes
331
341
 
332
342
  `--api-key`
333
343
  Your project API key (Can also be set using environment variable `I18NEXUS_API_KEY`)
334
344
 
335
- `--port`
336
- The local port your server will listen on to receive incoming webhook requests from i18nexus. Defaults to `3002`.
337
-
338
- The tunnel will remain active as long as the CLI is running. Press `Ctrl+C` to stop listening.
345
+ `--path`
346
+ The path to the destination folder in which translation files will be downloaded
package/bin/index.js CHANGED
@@ -268,11 +268,9 @@ program
268
268
  '-p, --path <path>',
269
269
  'The path to the destination folder in which translation files will be downloaded'
270
270
  )
271
- .option('--port <port>', 'Port your local dev server runs on', '3002')
272
271
  .action(options => {
273
272
  listen({
274
273
  apiKey: options.apiKey,
275
- port: options.port,
276
274
  path: options.path
277
275
  });
278
276
  });
@@ -1,28 +1,16 @@
1
1
  #!/usr/bin/env node
2
- /* commands/listen.js */
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 LISTEN_PATH = '/webhook'; // fixed internal path
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 }) => {
21
9
  await pull(
22
10
  {
23
11
  apiKey,
24
12
  version: 'latest',
25
- path: path,
13
+ path,
26
14
  clean: false,
27
15
  confirmed: false
28
16
  },
@@ -32,22 +20,37 @@ const listen = async ({ apiKey, port, path }) => {
32
20
  }
33
21
  );
34
22
 
35
- /* ────────────── 1. start local HTTP listener ────────────── */
36
- const server = http.createServer(async (req, res) => {
37
- if (req.method !== 'POST' || req.url !== LISTEN_PATH) {
38
- res.writeHead(404).end();
39
- return;
23
+ const wsUrl = new URL(`${baseUrl.replace(/^http/, 'ws')}/cable`);
24
+ wsUrl.searchParams.set('api_key', apiKey);
25
+
26
+ const ws = new WebSocket(wsUrl.toString(), {
27
+ headers: {
28
+ Origin: 'http://cli.i18nexus.com'
40
29
  }
30
+ });
31
+
32
+ ws.on('open', () => {
33
+ console.log(colors.green('Listening for i18nexus string updates...'));
41
34
 
42
- let body = '';
43
- req.on('data', chunk => (body += chunk));
44
- req.on('end', async () => {
45
- try {
35
+ ws.send(
36
+ JSON.stringify({
37
+ command: 'subscribe',
38
+ identifier: JSON.stringify({ channel: 'CliListenChannel' })
39
+ })
40
+ );
41
+ });
42
+
43
+ ws.on('message', async message => {
44
+ try {
45
+ const data = JSON.parse(message);
46
+ const payload = data.message;
47
+
48
+ if (payload?.event === 'strings.changed') {
46
49
  await pull(
47
50
  {
48
51
  apiKey,
49
52
  version: 'latest',
50
- path: path,
53
+ path,
51
54
  clean: false,
52
55
  confirmed: false
53
56
  },
@@ -56,81 +59,28 @@ const listen = async ({ apiKey, port, path }) => {
56
59
  successLog: ' ✔ Translations updated'
57
60
  }
58
61
  );
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
62
  }
65
- });
63
+ } catch (e) {
64
+ console.error(colors.red('i18nexus Sync Failed'));
65
+ }
66
66
  });
67
67
 
68
- /* ────────────── 2. start tunnel ────────────── */
69
- const tunnel = await new Promise(res => {
70
- server.listen(port, async () => {
71
- res(await localtunnel({ port }));
72
- });
68
+ ws.on('close', () => {
69
+ process.exit(1);
73
70
  });
74
71
 
75
- const publicUrl = `${tunnel.url}${LISTEN_PATH}`;
76
-
77
- /* ────────────── 3. register dev webhook with i18nexus ────────────── */
78
- const createRes = await handleFetch(
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;
72
+ ws.on('error', err => {
73
+ console.error(colors.red('i18nexus Connection Error'));
74
+ process.exit(1);
75
+ });
100
76
 
101
- await handleFetch(`${baseUrl}/project_resources/dev_webhooks`, {
102
- method: 'DELETE',
103
- headers: { 'Content-Type': 'application/json' },
104
- body: JSON.stringify({ api_key: apiKey, id: webhookId })
105
- });
106
- } catch (e) {}
107
- tunnel.close();
108
- server.close();
109
- process.exit(exitCode);
77
+ const cleanUp = () => {
78
+ ws.close();
79
+ process.exit(0);
110
80
  };
111
81
 
112
- if (createRes.status !== 200) {
113
- console.log(colors.red('Failed to register webhook with i18nexus'));
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
- });
82
+ process.on('SIGINT', cleanUp);
83
+ process.on('SIGTERM', cleanUp);
134
84
  };
135
85
 
136
86
  module.exports = listen;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18nexus-cli",
3
- "version": "3.6.1",
3
+ "version": "3.7.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
- "localtunnel": "^2.0.2",
24
- "node-fetch": "^2.6.7"
23
+ "node-fetch": "^2.6.7",
24
+ "ws": "^8.18.3"
25
25
  }
26
26
  }