i18next-cli 1.59.0 → 1.59.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/dist/cjs/cli.js +1 -1
- package/dist/cjs/utils/file-utils.js +58 -14
- package/dist/esm/cli.js +1 -1
- package/dist/esm/utils/file-utils.js +58 -15
- package/package.json +1 -1
- package/types/utils/file-utils.d.ts +12 -0
- package/types/utils/file-utils.d.ts.map +1 -1
package/dist/cjs/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ const program = new commander.Command();
|
|
|
32
32
|
program
|
|
33
33
|
.name('i18next-cli')
|
|
34
34
|
.description('A unified, high-performance i18next CLI.')
|
|
35
|
-
.version('1.59.
|
|
35
|
+
.version('1.59.1'); // This string is replaced with the actual version at build time by rollup
|
|
36
36
|
// new: global config override option
|
|
37
37
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
38
38
|
program
|
|
@@ -7,6 +7,24 @@ var config = require('../config.js');
|
|
|
7
7
|
var json5Parser = require('@croct/json5-parser');
|
|
8
8
|
var yaml = require('yaml');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when an existing translation file in a structured data format
|
|
12
|
+
* (JSON/JSON5/YAML) exists on disk but cannot be parsed. Callers should treat
|
|
13
|
+
* this as fatal rather than as an empty/missing file, since overwriting an
|
|
14
|
+
* unparseable file would silently destroy its contents (e.g. a merge conflict
|
|
15
|
+
* marker accidentally committed into an en.json).
|
|
16
|
+
*/
|
|
17
|
+
class ParseTranslationFileError extends Error {
|
|
18
|
+
filePath;
|
|
19
|
+
cause;
|
|
20
|
+
constructor(filePath, cause) {
|
|
21
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
22
|
+
super(`Could not parse translation file ${filePath}: ${detail}`);
|
|
23
|
+
this.filePath = filePath;
|
|
24
|
+
this.cause = cause;
|
|
25
|
+
this.name = 'ParseTranslationFileError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
10
28
|
/**
|
|
11
29
|
* Resolve an output template (string or function) into an actual path string.
|
|
12
30
|
*
|
|
@@ -58,23 +76,48 @@ async function loadTranslationFile(filePath) {
|
|
|
58
76
|
catch {
|
|
59
77
|
return null; // File doesn't exist
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
const ext = node_path.extname(fullPath).toLowerCase();
|
|
80
|
+
// Structured data formats (JSON/JSON5/YAML): the file exists (it passed the
|
|
81
|
+
// access() check above) but could not be parsed. Treating this as `null` is
|
|
82
|
+
// dangerous: callers coalesce it to an empty object and may overwrite the
|
|
83
|
+
// existing file, silently destroying its contents (e.g. when a merge conflict
|
|
84
|
+
// marker breaks an en.json that a watcher then re-extracts). Fail loudly so
|
|
85
|
+
// the caller can stop instead of overwriting good data with nothing.
|
|
86
|
+
if (ext === '.json5') {
|
|
87
|
+
const content = await promises.readFile(fullPath, 'utf-8');
|
|
88
|
+
try {
|
|
65
89
|
// Parse as a JSON5 object node
|
|
66
90
|
const node = json5Parser.JsonParser.parse(content, json5Parser.JsonObjectNode);
|
|
67
91
|
return node.toJSON();
|
|
68
92
|
}
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (ext === '.yaml' || ext === '.yml') {
|
|
98
|
+
const content = await promises.readFile(fullPath, 'utf-8');
|
|
99
|
+
try {
|
|
71
100
|
return yaml.parse(content);
|
|
72
101
|
}
|
|
73
|
-
|
|
74
|
-
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (ext === '.json') {
|
|
107
|
+
const content = await promises.readFile(fullPath, 'utf-8');
|
|
108
|
+
try {
|
|
75
109
|
return JSON.parse(content);
|
|
76
110
|
}
|
|
77
|
-
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (ext === '.ts' || ext === '.js') {
|
|
116
|
+
// .ts/.js resource files are loaded via jiti, which can fail for reasons
|
|
117
|
+
// unrelated to corruption (e.g. a transitive import or a side-effectful
|
|
118
|
+
// module). This path has been deliberately lenient since #59, so keep
|
|
119
|
+
// degrading to `null` here rather than aborting the whole command.
|
|
120
|
+
try {
|
|
78
121
|
// Load TypeScript path aliases for proper module resolution
|
|
79
122
|
const aliases = await config.getTsConfigAliases();
|
|
80
123
|
const jiti$1 = jiti.createJiti(process.cwd(), {
|
|
@@ -84,12 +127,12 @@ async function loadTranslationFile(filePath) {
|
|
|
84
127
|
const module = await jiti$1.import(fullPath, { default: true });
|
|
85
128
|
return module;
|
|
86
129
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return null;
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.warn(`Could not parse translation file ${filePath}:`, error);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
92
134
|
}
|
|
135
|
+
return null; // Unsupported file type
|
|
93
136
|
}
|
|
94
137
|
// Helper to load raw JSON5 content for preservation
|
|
95
138
|
async function loadRawJson5Content(filePath) {
|
|
@@ -156,6 +199,7 @@ function inferFormatFromPath(filePath, defaultFormat = 'json') {
|
|
|
156
199
|
return defaultFormat || 'json';
|
|
157
200
|
}
|
|
158
201
|
|
|
202
|
+
exports.ParseTranslationFileError = ParseTranslationFileError;
|
|
159
203
|
exports.getOutputPath = getOutputPath;
|
|
160
204
|
exports.inferFormatFromPath = inferFormatFromPath;
|
|
161
205
|
exports.loadRawJson5Content = loadRawJson5Content;
|
package/dist/esm/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ const program = new Command();
|
|
|
30
30
|
program
|
|
31
31
|
.name('i18next-cli')
|
|
32
32
|
.description('A unified, high-performance i18next CLI.')
|
|
33
|
-
.version('1.59.
|
|
33
|
+
.version('1.59.1'); // This string is replaced with the actual version at build time by rollup
|
|
34
34
|
// new: global config override option
|
|
35
35
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
36
36
|
program
|
|
@@ -5,6 +5,24 @@ import { getTsConfigAliases } from '../config.js';
|
|
|
5
5
|
import { JsonParser, JsonObjectNode } from '@croct/json5-parser';
|
|
6
6
|
import yaml from 'yaml';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when an existing translation file in a structured data format
|
|
10
|
+
* (JSON/JSON5/YAML) exists on disk but cannot be parsed. Callers should treat
|
|
11
|
+
* this as fatal rather than as an empty/missing file, since overwriting an
|
|
12
|
+
* unparseable file would silently destroy its contents (e.g. a merge conflict
|
|
13
|
+
* marker accidentally committed into an en.json).
|
|
14
|
+
*/
|
|
15
|
+
class ParseTranslationFileError extends Error {
|
|
16
|
+
filePath;
|
|
17
|
+
cause;
|
|
18
|
+
constructor(filePath, cause) {
|
|
19
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
20
|
+
super(`Could not parse translation file ${filePath}: ${detail}`);
|
|
21
|
+
this.filePath = filePath;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.name = 'ParseTranslationFileError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
/**
|
|
9
27
|
* Resolve an output template (string or function) into an actual path string.
|
|
10
28
|
*
|
|
@@ -56,23 +74,48 @@ async function loadTranslationFile(filePath) {
|
|
|
56
74
|
catch {
|
|
57
75
|
return null; // File doesn't exist
|
|
58
76
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
const ext = extname(fullPath).toLowerCase();
|
|
78
|
+
// Structured data formats (JSON/JSON5/YAML): the file exists (it passed the
|
|
79
|
+
// access() check above) but could not be parsed. Treating this as `null` is
|
|
80
|
+
// dangerous: callers coalesce it to an empty object and may overwrite the
|
|
81
|
+
// existing file, silently destroying its contents (e.g. when a merge conflict
|
|
82
|
+
// marker breaks an en.json that a watcher then re-extracts). Fail loudly so
|
|
83
|
+
// the caller can stop instead of overwriting good data with nothing.
|
|
84
|
+
if (ext === '.json5') {
|
|
85
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
86
|
+
try {
|
|
63
87
|
// Parse as a JSON5 object node
|
|
64
88
|
const node = JsonParser.parse(content, JsonObjectNode);
|
|
65
89
|
return node.toJSON();
|
|
66
90
|
}
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (ext === '.yaml' || ext === '.yml') {
|
|
96
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
97
|
+
try {
|
|
69
98
|
return yaml.parse(content);
|
|
70
99
|
}
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (ext === '.json') {
|
|
105
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
106
|
+
try {
|
|
73
107
|
return JSON.parse(content);
|
|
74
108
|
}
|
|
75
|
-
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw new ParseTranslationFileError(filePath, error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (ext === '.ts' || ext === '.js') {
|
|
114
|
+
// .ts/.js resource files are loaded via jiti, which can fail for reasons
|
|
115
|
+
// unrelated to corruption (e.g. a transitive import or a side-effectful
|
|
116
|
+
// module). This path has been deliberately lenient since #59, so keep
|
|
117
|
+
// degrading to `null` here rather than aborting the whole command.
|
|
118
|
+
try {
|
|
76
119
|
// Load TypeScript path aliases for proper module resolution
|
|
77
120
|
const aliases = await getTsConfigAliases();
|
|
78
121
|
const jiti = createJiti(process.cwd(), {
|
|
@@ -82,12 +125,12 @@ async function loadTranslationFile(filePath) {
|
|
|
82
125
|
const module = await jiti.import(fullPath, { default: true });
|
|
83
126
|
return module;
|
|
84
127
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return null;
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.warn(`Could not parse translation file ${filePath}:`, error);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
90
132
|
}
|
|
133
|
+
return null; // Unsupported file type
|
|
91
134
|
}
|
|
92
135
|
// Helper to load raw JSON5 content for preservation
|
|
93
136
|
async function loadRawJson5Content(filePath) {
|
|
@@ -154,4 +197,4 @@ function inferFormatFromPath(filePath, defaultFormat = 'json') {
|
|
|
154
197
|
return defaultFormat || 'json';
|
|
155
198
|
}
|
|
156
199
|
|
|
157
|
-
export { getOutputPath, inferFormatFromPath, loadRawJson5Content, loadTranslationFile, serializeTranslationFile };
|
|
200
|
+
export { ParseTranslationFileError, getOutputPath, inferFormatFromPath, loadRawJson5Content, loadTranslationFile, serializeTranslationFile };
|
package/package.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { I18nextToolkitConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an existing translation file in a structured data format
|
|
4
|
+
* (JSON/JSON5/YAML) exists on disk but cannot be parsed. Callers should treat
|
|
5
|
+
* this as fatal rather than as an empty/missing file, since overwriting an
|
|
6
|
+
* unparseable file would silently destroy its contents (e.g. a merge conflict
|
|
7
|
+
* marker accidentally committed into an en.json).
|
|
8
|
+
*/
|
|
9
|
+
export declare class ParseTranslationFileError extends Error {
|
|
10
|
+
readonly filePath: string;
|
|
11
|
+
readonly cause?: unknown | undefined;
|
|
12
|
+
constructor(filePath: string, cause?: unknown | undefined);
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* Ensures that the directory for a given file path exists.
|
|
4
16
|
* Creates all necessary parent directories recursively if they don't exist.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAKvD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EACvF,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA8BR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAKvD;;;;;;GAMG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;aACrB,QAAQ,EAAE,MAAM;aAAkB,KAAK,CAAC,EAAE,OAAO;gBAAjD,QAAQ,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,OAAO,YAAA;CAK/E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EACvF,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA8BR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CA8DhG;AAGD,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQnF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,EAChE,WAAW,GAAE,MAAM,GAAG,MAAU,EAChC,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CA6BR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,GACtE,WAAW,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,CAAC,CAO9D"}
|