@esgettext/tools 1.0.0 → 1.2.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.
Files changed (113) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +197 -22
  3. package/dist/command.js +2 -0
  4. package/dist/commands/convert.js +286 -0
  5. package/dist/commands/install.js +257 -0
  6. package/dist/commands/msgfmt-all.js +219 -0
  7. package/dist/commands/msgmerge-all.js +230 -0
  8. package/dist/commands/xgettext/file-resolver.js +20 -0
  9. package/dist/commands/xgettext/files-collector.js +39 -0
  10. package/dist/commands/xgettext.js +534 -0
  11. package/dist/configuration.js +277 -0
  12. package/dist/esgettext-package-json.js +2 -0
  13. package/dist/index.js +135 -0
  14. package/dist/package.js +10 -12
  15. package/dist/parser/javascript.js +9 -28
  16. package/dist/parser/parser.js +176 -148
  17. package/dist/parser/po.js +144 -128
  18. package/dist/parser/typescript.js +10 -28
  19. package/dist/pot/catalog.js +134 -83
  20. package/dist/pot/entry.js +173 -115
  21. package/dist/pot/keyword.js +66 -92
  22. package/package.json +38 -25
  23. package/bin/esgettext-install.js +0 -5
  24. package/bin/esgettext-mo2json.js +0 -5
  25. package/bin/esgettext-msgfmt-all.js +0 -5
  26. package/bin/esgettext-msgmerge-all.js +0 -5
  27. package/bin/esgettext-xgettext.js +0 -5
  28. package/dist/cli/getopt.d.ts +0 -34
  29. package/dist/cli/getopt.js +0 -150
  30. package/dist/cli/getopt.js.map +0 -1
  31. package/dist/cli/getopt.spec.d.ts +0 -1
  32. package/dist/cli/getopt.spec.js +0 -164
  33. package/dist/cli/getopt.spec.js.map +0 -1
  34. package/dist/cli/install.d.ts +0 -1
  35. package/dist/cli/install.js +0 -91
  36. package/dist/cli/install.js.map +0 -1
  37. package/dist/cli/mo2json-cli.d.ts +0 -1
  38. package/dist/cli/mo2json-cli.js +0 -48
  39. package/dist/cli/mo2json-cli.js.map +0 -1
  40. package/dist/cli/msgfmt-all.d.ts +0 -1
  41. package/dist/cli/msgfmt-all.js +0 -99
  42. package/dist/cli/msgfmt-all.js.map +0 -1
  43. package/dist/cli/msgmerge-all.d.ts +0 -1
  44. package/dist/cli/msgmerge-all.js +0 -85
  45. package/dist/cli/msgmerge-all.js.map +0 -1
  46. package/dist/cli/xgettext-cli.d.ts +0 -1
  47. package/dist/cli/xgettext-cli.js +0 -303
  48. package/dist/cli/xgettext-cli.js.map +0 -1
  49. package/dist/install/install.d.ts +0 -10
  50. package/dist/install/install.js +0 -157
  51. package/dist/install/install.js.map +0 -1
  52. package/dist/msgfmt-all/msgfmt-all.d.ts +0 -8
  53. package/dist/msgfmt-all/msgfmt-all.js +0 -142
  54. package/dist/msgfmt-all/msgfmt-all.js.map +0 -1
  55. package/dist/msgmerge-all/msgmerge-all.d.ts +0 -9
  56. package/dist/msgmerge-all/msgmerge-all.js +0 -157
  57. package/dist/msgmerge-all/msgmerge-all.js.map +0 -1
  58. package/dist/package.d.ts +0 -4
  59. package/dist/package.js.map +0 -1
  60. package/dist/parser/__snapshots__/javascript.spec.js.snap +0 -56
  61. package/dist/parser/__snapshots__/po.spec.js.snap +0 -176
  62. package/dist/parser/javascript.d.ts +0 -4
  63. package/dist/parser/javascript.js.map +0 -1
  64. package/dist/parser/javascript.spec.d.ts +0 -1
  65. package/dist/parser/javascript.spec.js +0 -436
  66. package/dist/parser/javascript.spec.js.map +0 -1
  67. package/dist/parser/parser.d.ts +0 -45
  68. package/dist/parser/parser.js.map +0 -1
  69. package/dist/parser/po.d.ts +0 -22
  70. package/dist/parser/po.js.map +0 -1
  71. package/dist/parser/po.spec.d.ts +0 -1
  72. package/dist/parser/po.spec.js +0 -296
  73. package/dist/parser/po.spec.js.map +0 -1
  74. package/dist/parser/typescript.d.ts +0 -4
  75. package/dist/parser/typescript.js.map +0 -1
  76. package/dist/parser/typescript.spec.d.ts +0 -1
  77. package/dist/parser/typescript.spec.js +0 -106
  78. package/dist/parser/typescript.spec.js.map +0 -1
  79. package/dist/pot/__snapshots__/catalog.spec.js.snap +0 -591
  80. package/dist/pot/catalog.d.ts +0 -26
  81. package/dist/pot/catalog.js.map +0 -1
  82. package/dist/pot/catalog.spec.d.ts +0 -1
  83. package/dist/pot/catalog.spec.js +0 -240
  84. package/dist/pot/catalog.spec.js.map +0 -1
  85. package/dist/pot/entry.d.ts +0 -35
  86. package/dist/pot/entry.js.map +0 -1
  87. package/dist/pot/entry.spec.d.ts +0 -1
  88. package/dist/pot/entry.spec.js +0 -317
  89. package/dist/pot/entry.spec.js.map +0 -1
  90. package/dist/pot/keyword.d.ts +0 -17
  91. package/dist/pot/keyword.js.map +0 -1
  92. package/dist/pot/keyword.spec.d.ts +0 -1
  93. package/dist/pot/keyword.spec.js +0 -54
  94. package/dist/pot/keyword.spec.js.map +0 -1
  95. package/dist/xgettext/__snapshots__/xgettext.spec.js.snap +0 -462
  96. package/dist/xgettext/file-resolver.d.ts +0 -5
  97. package/dist/xgettext/file-resolver.js +0 -23
  98. package/dist/xgettext/file-resolver.js.map +0 -1
  99. package/dist/xgettext/file-resolver.spec.d.ts +0 -1
  100. package/dist/xgettext/file-resolver.spec.js +0 -22
  101. package/dist/xgettext/file-resolver.spec.js.map +0 -1
  102. package/dist/xgettext/files-collector.d.ts +0 -5
  103. package/dist/xgettext/files-collector.js +0 -41
  104. package/dist/xgettext/files-collector.js.map +0 -1
  105. package/dist/xgettext/files-collector.spec.d.ts +0 -1
  106. package/dist/xgettext/files-collector.spec.js +0 -78
  107. package/dist/xgettext/files-collector.spec.js.map +0 -1
  108. package/dist/xgettext/xgettext.d.ts +0 -16
  109. package/dist/xgettext/xgettext.js +0 -335
  110. package/dist/xgettext/xgettext.js.map +0 -1
  111. package/dist/xgettext/xgettext.spec.d.ts +0 -1
  112. package/dist/xgettext/xgettext.spec.js +0 -743
  113. package/dist/xgettext/xgettext.spec.js.map +0 -1
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- This software is Copyright (C) 2020 by Guido Flohr.
1
+ This software is Copyright (C) 2020-2024 by Guido Flohr.
2
2
 
3
3
  This is free software, licensed under:
4
4
 
package/README.md CHANGED
@@ -6,15 +6,37 @@ Supporting tools for https://github.com/gflohr/esgettext.
6
6
 
7
7
  - [Status](#status)
8
8
  - [Prerequisites](#prerequisites)
9
- - [Un*x/Linux](#unxlinux)
9
+ - [Un\*x/Linux](#unxlinux)
10
10
  - [Mac OS X](#mac-os-x)
11
11
  - [MacPorts](#macports)
12
12
  - [HomeBrew](#homebrew)
13
13
  - [MS-DOS (Microsoft Windows)](#ms-dos-microsoft-windows)
14
14
  - [Installation](#installation)
15
15
  - [The Tools](#the-tools)
16
- - [`esgettext-gettextize`](#esgettext-gettextize)
17
- - [`esgettext-xgettext`](#esgettext-xgettext)
16
+ - [`esgettext xgettext`](#esgettext-xgettext)
17
+ - [Other Commands](#other-commands)
18
+ - [Configuration](#configuration)
19
+ - [Configuration Files](#configuration-files)
20
+ - [Validation](#validation)
21
+ - [Configuration Schema](#configuration-schema)
22
+ - [`package`](#package)
23
+ - [`package.textdomain`](#packagetextdomain)
24
+ - [`package.msgid-bugs-address`](#packagemsgid-bugs-address)
25
+ - [`package.name`](#packagename)
26
+ - [`package.copyright-holder`](#packagecopyright-holder)
27
+ - [`package.version`](#packageversion)
28
+ - [`po`:](#po)
29
+ - [`po.directory`](#podirectory)
30
+ - [`po.locales`](#polocales)
31
+ - [`install`](#install)
32
+ - [`install.directory`](#installdirectory)
33
+ - [`programs`](#programs)
34
+ - [`programs.msgmerge`](#programsmsgmerge)
35
+ - [`programs.msgmerge.path`](#programsmsgmergepath)
36
+ - [`programs.msgmerge.options`](#programsmsgmergeoptions)
37
+ - [`programs.msgfmt`](#programsmsgfmt)
38
+ - [`programs.msgfmt.path`](#programsmsgfmtpath)
39
+ - [`programs.msgfmt.options`](#programsmsgfmtoptions)
18
40
  - [Copyright](#copyright)
19
41
 
20
42
  ## Status
@@ -75,51 +97,204 @@ $ sudo npm install --global @esgettext/tools
75
97
 
76
98
  For [Yarn](https://yarnpkg.com/), you would do this:
77
99
 
78
- ```
100
+ ```shell
79
101
  $ sudo yarn global add @esgettext/tools
80
102
  ```
81
103
 
82
- But you should rather install [npx](https://www.npmjs.com/package/npx), because
83
- then you can (es)gettextize your project like this:
104
+ Normally, it is better to install them just locally:
84
105
 
85
- ```
106
+ ```shell
86
107
  $ npm install --save-dev @esgettext/tools
87
- $ npx @esgettext/gettextize
88
108
  ```
89
109
 
90
110
  or for yarn:
91
111
 
92
- ```
112
+ ```shell
93
113
  $ yarn add --dev @esgettext/tools
94
- $ npx @esgettext/gettextize
95
114
  ```
96
115
 
97
- This will modify your `package.json` with a couple of scripts, all named
98
- `po:.*`. You can then finetune your setup by just editing `package.json`.
99
-
100
116
  ## The Tools
101
117
 
102
- ### `esgettext-gettextize`
118
+ The tools are really just one tool `esgettext` that supports several
119
+ commands.
103
120
 
104
- The script `esgettext-gettextize` sets up your project for being
105
- internationalized with esgettext.
121
+ In the following, it is assumed that you have installed the tools locally.
122
+ If you have installed them globally, just omit the leading `npx`.
106
123
 
107
- See the detailed
108
- [esgettext-gettextize usage information](./README-esgettext-gettextize.md)
109
- for more details.
124
+ Try this for an overview:
110
125
 
111
- ### `esgettext-xgettext`
126
+ ```shell
127
+ $ npx esgettext --help
128
+ ```
129
+
130
+ ### `esgettext xgettext`
112
131
 
113
- The script `esgetext-xgettext` extracts translatable strings from your source
132
+ The command `npx esgetext xgettext` extracts translatable strings from your source
114
133
  files into `.po` resp. `.pot` files.
115
134
 
135
+ You can use `npx esgettext extract` as an alias for `npx esgettext xgettext`.
136
+
116
137
  See the detailed
117
138
  [esgettext-xgettext usage information](./README-esgettext-xgettext.md)
118
139
  for more details.
119
140
 
141
+ ### Other Commands
142
+
143
+ All other commands should be understandable by their help output. Try
144
+ `npx esgettext --help` for an overview over all commands, and
145
+ `npx esgettext COMMAND --help` for help for a specific command.
146
+
147
+ ## Configuration
148
+
149
+ Instead of passing options on the command line, you can add a lot of defaults
150
+ in a configuration file or just in `package.json`.
151
+
152
+ ### Configuration Files
153
+
154
+ Configuration files are checked in this order:
155
+
156
+ * esgettext.config.mjs
157
+ * esgettext.config.cjs
158
+ * esgettext.config.js
159
+ * esgettext.config.json
160
+ * package.json
161
+
162
+ The JavaScript versions should have one default export with the configuration.
163
+ The JSON version just defines the configuration. Alteratively, you can add
164
+ a field "esgettext" to your `package.json`.
165
+
166
+ You should always configure `po.locales` with a list of locale identifiers that
167
+ your package supports. Otherwise, using `esgettext` does not make sense.
168
+
169
+ You should also set `package.textdomain` unless you are happy with the
170
+ default which is just your package name read from `package.json`.
171
+
172
+ All other configuration values have sane defaults.
173
+
174
+ ### Validation
175
+
176
+ The required format is always the same. Note that the configuration gets
177
+ validated against a schema. All tools will fail with a validation error if
178
+ you pass an invalid configuration.
179
+
180
+ ### Configuration Schema
181
+
182
+ All fields in the configuration are optional because you are always able to
183
+ pass them on the command line.
184
+
185
+ Options passed on the command line have higher precedence than options given
186
+ in a configuration file. Options that you define inside a section `esgettext`
187
+ in `package.json` have higher precedence than options that default to
188
+ general fields inside `package.json`.
189
+
190
+ A complete example for a configuration can look like this:
191
+
192
+ ```javascript
193
+ package: {
194
+ textdomain: 'com.example.my-package',
195
+ 'msgid-bugs-address': 'you@example.com',
196
+ name: 'my-package',
197
+ 'copyright-holder': 'Yours Truly <you@example.com>',
198
+ },
199
+ po: {
200
+ directory: 'po',
201
+ locales: ['de', 'fr-CA', 'fr-FR', 'it'],
202
+ }
203
+ ```
204
+
205
+ #### `package`
206
+
207
+ General information about your package.
208
+
209
+ ##### `package.textdomain`
210
+
211
+ The textdomain of your package, usually something like
212
+ `com.example.YOUR-PACKAGE`. You should configure this.
213
+
214
+ ##### `package.msgid-bugs-address`
215
+
216
+ An email address or URL where to send bug reports or questions about message
217
+ ids. This is added to the respective field in all generated po files.
218
+
219
+ If you omit this field, it will be read from the fields `bugs.email` or
220
+ `bugs.url` (in that order) in `package.json`.
221
+
222
+ ##### `package.name`
223
+
224
+ The name of your package.
225
+
226
+ If you omit this field, it will be read from the field `name` in
227
+ `package.json`.
228
+
229
+ ##### `package.copyright-holder`
230
+
231
+ The copyright holder of your package. This is added as a comment to all
232
+ generated po files.
233
+
234
+ If you omit this field, it will be read from the field `people.author` in
235
+ `package.json`.
236
+
237
+
238
+ ##### `package.version`
239
+
240
+ The version of your package.
241
+
242
+ If you omit this field, it will be read from the field `version` in
243
+ `package.json`.
244
+
245
+ #### `po`:
246
+
247
+ The location of your translation and translation workflow files and feature
248
+ thereof.
249
+
250
+ ##### `po.directory`
251
+
252
+ The directory where all translation related files reside, usually 'po'.
253
+
254
+ ##### `po.locales`
255
+
256
+ An array of locale identifiers. This should always be configured.
257
+
258
+ #### `install`
259
+
260
+ Installation options.
261
+
262
+ ##### `install.directory`
263
+
264
+ Where to install compiled translation files, usually something like
265
+ `src/locale`, `assets/locale`, or `dist/assets/locale`.
266
+
267
+ #### `programs`
268
+
269
+ Information for helper programs.
270
+
271
+ ##### `programs.msgmerge`
272
+
273
+ ###### `programs.msgmerge.path`
274
+
275
+ The path to the `msgmerge` program of your system.
276
+
277
+ ###### `programs.msgmerge.options`
278
+
279
+ An array of options to pass to the `msgmerge` program. Only boolean options
280
+ without arguments are supported. And you have to omit the leading hyphens,
281
+ for example `['verbose']` or `['v']`, and not `['--verbose']` or `['-v']`.
282
+
283
+ ##### `programs.msgfmt`
284
+
285
+ ###### `programs.msgfmt.path`
286
+
287
+ The path to the `msgfmt` program of your system.
288
+
289
+ ###### `programs.msgfmt.options`
290
+
291
+ An array of options to pass to the `msgmerge` program. Only boolean options
292
+ without arguments are supported. And you have to omit the leading hyphens,
293
+ for example `['verbose']` or `['v']`, and not `['--verbose']` or `['-v']`.
294
+
120
295
  ## Copyright
121
296
 
122
- Copyright (C) 2020 Guido Flohr <guido.flohr@cantanea.com>, all
297
+ Copyright (C) 2020-2024 Guido Flohr <guido.flohr@cantanea.com>, all
123
298
  rights reserved.
124
299
 
125
300
  This software is available under the terms and conditions of the
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.Convert = void 0;
27
+ const fs = __importStar(require("fs"));
28
+ const runtime_1 = require("@esgettext/runtime");
29
+ const package_1 = require("../package");
30
+ const gtx = runtime_1.Textdomain.getInstance('com.cantanea.esgettext-tools');
31
+ class Convert {
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ constructor(_) {
34
+ this.options = undefined;
35
+ }
36
+ synopsis() {
37
+ return `[${gtx._('INPUTFILE')}] [${gtx._('OUTPUTFILE')}]`;
38
+ }
39
+ description() {
40
+ return gtx._('Convert translation catalogs.');
41
+ }
42
+ aliases() {
43
+ return [];
44
+ }
45
+ args() {
46
+ return {
47
+ input: {
48
+ alias: 'i',
49
+ type: 'string',
50
+ describe: gtx._('Input file (stdin when omitted)'),
51
+ group: gtx._('File locations:'),
52
+ },
53
+ output: {
54
+ alias: 'o',
55
+ type: 'string',
56
+ describe: gtx._('Output file (stdout when omitted)'),
57
+ group: gtx._('File locations:'),
58
+ },
59
+ 'input-format': {
60
+ alias: 'I',
61
+ type: 'string',
62
+ describe: gtx._('Input file format (defaul: derived from filename)'),
63
+ choices: ['mo.json', 'mo', 'gmo', 'json'],
64
+ coerce: arg => arg.toLowerCase(),
65
+ group: gtx._('File formats:'),
66
+ },
67
+ 'output-format': {
68
+ alias: 'O',
69
+ type: 'string',
70
+ describe: gtx._('Output file format (default: derived from format)'),
71
+ choices: ['mo.json', 'json'],
72
+ coerce: arg => arg.toLowerCase(),
73
+ group: gtx._('File formats:'),
74
+ },
75
+ verbose: {
76
+ alias: 'V',
77
+ type: 'boolean',
78
+ describe: gtx._('Enable verbose output'),
79
+ },
80
+ };
81
+ }
82
+ additional(argv) {
83
+ argv.positional(gtx._('INPUTFILE'), {
84
+ type: 'string',
85
+ describe: gtx._('Input file (or option -i or standard input)'),
86
+ });
87
+ argv.positional(gtx._('OUTPUTFILE'), {
88
+ type: 'string',
89
+ describe: gtx._('Output file (or option -o or standard output)'),
90
+ });
91
+ argv.conflicts('input', 'INPUTFILE');
92
+ argv.conflicts('output', 'OUTPUTFILE');
93
+ }
94
+ init(argv) {
95
+ const options = argv;
96
+ this.options = options;
97
+ }
98
+ run(argv) {
99
+ this.init(argv);
100
+ const options = this.options;
101
+ return new Promise(resolve => {
102
+ const input = options.input || options[gtx._('INPUTFILE')];
103
+ const output = options.output || options[gtx._('OUTPUTFILE')];
104
+ const inputFormat = this.getFileFormat(input);
105
+ if (!this.checkInputFormat(inputFormat, input)) {
106
+ return resolve(1);
107
+ }
108
+ if (options.verbose) {
109
+ console.error(gtx._x('Detected input format {inputFormat}.', { inputFormat }));
110
+ }
111
+ const outputFormat = this.getFileFormat(output);
112
+ if (!this.checkOutputFormat(outputFormat, output)) {
113
+ return resolve(1);
114
+ }
115
+ if (options.verbose) {
116
+ console.error(gtx._x('Detected output format {outputFormat}.', { outputFormat }));
117
+ }
118
+ try {
119
+ const inBuffer = this.readInput(input);
120
+ let catalog;
121
+ switch (options.inputFormat) {
122
+ case 'mo.json':
123
+ catalog = (0, runtime_1.parseMoJsonCatalog)(inBuffer);
124
+ break;
125
+ case 'mo':
126
+ case 'gmo':
127
+ default:
128
+ catalog = (0, runtime_1.parseMoCatalog)(inBuffer);
129
+ break;
130
+ }
131
+ const converted = this.convert(catalog, input, outputFormat);
132
+ if (!converted) {
133
+ return resolve(1);
134
+ }
135
+ this.output(output, converted);
136
+ }
137
+ catch (e) {
138
+ console.error(gtx._x('{programName}: Error: {e}'));
139
+ return resolve(1);
140
+ }
141
+ return resolve(0);
142
+ });
143
+ }
144
+ getFileFormat(input) {
145
+ if (this.options.inputFormat) {
146
+ return this.options.inputFormat;
147
+ }
148
+ else if (typeof input !== 'undefined') {
149
+ const match = input.match(/\.(mo\.json|g?mo|json)$/i);
150
+ if (match) {
151
+ return match[1].toLowerCase();
152
+ }
153
+ }
154
+ return;
155
+ }
156
+ checkInputFormat(format, filename) {
157
+ if (!format) {
158
+ if (typeof filename === 'undefined') {
159
+ console.error(gtx._x("{programName}: Error: The option '--inputFormat' is mandatory, when reading from standard input", { programName: package_1.Package.getName() }));
160
+ }
161
+ else {
162
+ console.error(gtx._x("{programName}: Error: Please specify the input format with '--inputFormat'! Cannot guess it from input filename '{filename}'!", { programName: package_1.Package.getName(), filename }));
163
+ }
164
+ return false;
165
+ }
166
+ else if (format !== 'mo.json' &&
167
+ format !== 'mo' &&
168
+ format !== 'gmo' &&
169
+ format !== 'json') {
170
+ console.error(gtx._("Only 'mo.json', 'mo', 'gmo', and 'json' are allowed as input formats!"));
171
+ return false;
172
+ }
173
+ return true;
174
+ }
175
+ checkOutputFormat(format, filename) {
176
+ if (!format) {
177
+ if (typeof filename === 'undefined') {
178
+ console.error(gtx._x("{programName}: Error: The option '--outputFormat' is mandatory, when writing to standard input", { programName: package_1.Package.getName() }));
179
+ }
180
+ else {
181
+ console.error(gtx._x("{programName}: Error: Please specify the output format with '--outputFormat'! Cannot guess it from input filename '{filename}'!", { programName: package_1.Package.getName(), filename }));
182
+ }
183
+ return false;
184
+ }
185
+ else if (format !== 'mo.json' && format !== 'json') {
186
+ console.error(gtx._("Only 'mo.json' and 'json' are allowed as output formats!"));
187
+ return false;
188
+ }
189
+ return true;
190
+ }
191
+ readInput(filename) {
192
+ try {
193
+ if (filename === '-' && !fs.existsSync(filename)) {
194
+ filename = undefined;
195
+ }
196
+ if (typeof filename !== 'undefined') {
197
+ return fs.readFileSync(filename);
198
+ }
199
+ // Must read from standard input.
200
+ const stdinFd = 0;
201
+ const chunks = [];
202
+ let bytesRead;
203
+ const buffer = Buffer.alloc(8192);
204
+ while ((bytesRead = fs.readSync(stdinFd, buffer, 0, buffer.length, null)) > 0) {
205
+ chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
206
+ }
207
+ return Buffer.concat(chunks);
208
+ }
209
+ catch (error) {
210
+ if (typeof filename === 'undefined') {
211
+ filename = gtx._('[standard input]');
212
+ }
213
+ throw Error(gtx._x('{filename}: read failed: {error}', { filename, error }));
214
+ }
215
+ }
216
+ convert(catalog, filename, outputFormat) {
217
+ if (typeof filename === 'undefined') {
218
+ filename = gtx._('[standard input]');
219
+ }
220
+ if (outputFormat === 'json') {
221
+ const entries = {};
222
+ let errors = 0;
223
+ for (const [msgid, msgstr] of Object.entries(catalog.entries)) {
224
+ if (msgstr.length > 1) {
225
+ ++errors;
226
+ console.error(gtx._x("{filename}: Error: msgid '{msgid}': plural forms are not supported by the 'json' catalog format!", { filename, msgid: this.shortenAndEscapeString(msgid, 40) }));
227
+ }
228
+ else {
229
+ entries[msgid] = msgstr[0];
230
+ }
231
+ }
232
+ if (errors) {
233
+ return undefined;
234
+ }
235
+ return JSON.stringify(entries);
236
+ }
237
+ else {
238
+ return JSON.stringify(catalog);
239
+ }
240
+ }
241
+ shortenAndEscapeString(input, maxLength) {
242
+ const controlCharMap = {
243
+ '\0': '\\0', // Null character
244
+ '\b': '\\b', // Backspace
245
+ '\t': '\\t', // Horizontal Tab
246
+ '\n': '\\n', // New Line
247
+ '\v': '\\v', // Vertical Tab
248
+ '\f': '\\f', // Form Feed
249
+ '\r': '\\r', // Carriage Return
250
+ '"': '\\"', // Double quote
251
+ "'": "\\'", // Single quote
252
+ '\\': '\\\\', // Backslash
253
+ };
254
+ const escapeControlChar = (char) => {
255
+ if (controlCharMap[char]) {
256
+ return controlCharMap[char];
257
+ }
258
+ const code = char.charCodeAt(0);
259
+ if (code < 32) {
260
+ return `\\x${code.toString(16).padStart(2, '0')}`;
261
+ }
262
+ return char; // This line is technically redundant with the current regex.
263
+ };
264
+ // Escape all control characters
265
+ const escapedInput = input.replace(/[\x00-\x1F"'\\]/g, escapeControlChar);
266
+ let truncatedString = escapedInput;
267
+ if (escapedInput.length > maxLength) {
268
+ truncatedString = escapedInput.slice(0, maxLength - 3) + '...';
269
+ }
270
+ return truncatedString;
271
+ }
272
+ output(filename, data) {
273
+ if (typeof filename === 'undefined') {
274
+ console.log(data);
275
+ }
276
+ else {
277
+ try {
278
+ fs.writeFileSync(filename, data, { encoding: 'utf-8' });
279
+ }
280
+ catch (error) {
281
+ throw Error(gtx._x('{filename}: write failed: {error}', { filename, error }));
282
+ }
283
+ }
284
+ }
285
+ }
286
+ exports.Convert = Convert;