@gjsify/vite-plugin-gettext 0.3.21 → 0.4.3
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/package.json +60 -57
- package/src/gettext.ts +0 -104
- package/src/index.ts +0 -11
- package/src/msgfmt.ts +0 -248
- package/src/po2json.ts +0 -281
- package/src/types.ts +0 -138
- package/src/utils.ts +0 -119
- package/src/xgettext.ts +0 -525
- package/tsconfig.json +0 -15
package/src/xgettext.ts
DELETED
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
import { type Plugin } from "vite";
|
|
2
|
-
import { execa } from "execa";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import fs from "node:fs/promises";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
import glob from "fast-glob";
|
|
7
|
-
import type { XGettextPluginOptions } from "./types.js";
|
|
8
|
-
import {
|
|
9
|
-
checkDependencies,
|
|
10
|
-
ensureDirectory,
|
|
11
|
-
processFilename,
|
|
12
|
-
} from "./utils.js";
|
|
13
|
-
|
|
14
|
-
// Add GLib preset constants
|
|
15
|
-
// From https://github.com/mesonbuild/meson/blob/467da051c859ba3112803b035e317bddadd756ef/mesonbuild/modules/i18n.py
|
|
16
|
-
const GLIB_PRESET_ARGS = [
|
|
17
|
-
"--from-code=UTF-8",
|
|
18
|
-
"--add-comments",
|
|
19
|
-
// https://developer.gnome.org/glib/stable/glib-I18N.html
|
|
20
|
-
"--keyword=_",
|
|
21
|
-
"--keyword=N_",
|
|
22
|
-
"--keyword=C_:1c,2",
|
|
23
|
-
"--keyword=NC_:1c,2",
|
|
24
|
-
"--keyword=g_dcgettext:2",
|
|
25
|
-
"--keyword=g_dngettext:2,3",
|
|
26
|
-
"--keyword=g_dpgettext2:2c,3",
|
|
27
|
-
"--flag=N_:1:pass-c-format",
|
|
28
|
-
"--flag=C_:2:pass-c-format",
|
|
29
|
-
"--flag=NC_:2:pass-c-format",
|
|
30
|
-
"--flag=g_dngettext:2:pass-c-format",
|
|
31
|
-
"--flag=g_strdup_printf:1:c-format",
|
|
32
|
-
"--flag=g_string_printf:2:c-format",
|
|
33
|
-
"--flag=g_string_append_printf:2:c-format",
|
|
34
|
-
"--flag=g_error_new:3:c-format",
|
|
35
|
-
"--flag=g_set_error:4:c-format",
|
|
36
|
-
"--flag=g_markup_printf_escaped:1:c-format",
|
|
37
|
-
"--flag=g_log:3:c-format",
|
|
38
|
-
"--flag=g_print:1:c-format",
|
|
39
|
-
"--flag=g_printerr:1:c-format",
|
|
40
|
-
"--flag=g_printf:1:c-format",
|
|
41
|
-
"--flag=g_fprintf:2:c-format",
|
|
42
|
-
"--flag=g_sprintf:2:c-format",
|
|
43
|
-
"--flag=g_snprintf:3:c-format",
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Build command arguments with common options
|
|
48
|
-
* @param baseArgs Base arguments for the command
|
|
49
|
-
* @param options Options to add to arguments
|
|
50
|
-
* @returns Complete argument array
|
|
51
|
-
*/
|
|
52
|
-
function buildCommandArgs(
|
|
53
|
-
baseArgs: string[],
|
|
54
|
-
options: {
|
|
55
|
-
noLocation?: boolean;
|
|
56
|
-
noWrap?: boolean;
|
|
57
|
-
sortOutput?: boolean;
|
|
58
|
-
additionalOptions?: string[];
|
|
59
|
-
}
|
|
60
|
-
): string[] {
|
|
61
|
-
const args = [...baseArgs];
|
|
62
|
-
|
|
63
|
-
// Check if additional options already contain the flags to avoid duplicates
|
|
64
|
-
const additionalOptions = options.additionalOptions || [];
|
|
65
|
-
const hasNoLocation = additionalOptions.includes("--no-location");
|
|
66
|
-
const hasNoWrap = additionalOptions.includes("--no-wrap");
|
|
67
|
-
const hasSortOutput = additionalOptions.includes("--sort-output");
|
|
68
|
-
|
|
69
|
-
if (options.noLocation && !hasNoLocation) {
|
|
70
|
-
args.push("--no-location");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (options.noWrap && !hasNoWrap) {
|
|
74
|
-
args.push("--no-wrap");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (options.sortOutput && !hasSortOutput) {
|
|
78
|
-
args.push("--sort-output");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (additionalOptions.length > 0) {
|
|
82
|
-
args.push(...additionalOptions);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return args;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Creates a Vite plugin that extracts translatable strings from source files
|
|
90
|
-
* Uses GNU xgettext to generate a POT template file that can be used as basis for translations
|
|
91
|
-
* @param options Configuration options for the plugin
|
|
92
|
-
* @returns A Vite plugin that handles string extraction
|
|
93
|
-
*/
|
|
94
|
-
export function xgettextPlugin(options: XGettextPluginOptions): Plugin {
|
|
95
|
-
const pluginName = "vite-plugin-xgettext";
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
name: pluginName,
|
|
99
|
-
|
|
100
|
-
async buildStart() {
|
|
101
|
-
await checkDependencies("xgettext", pluginName, options.verbose ?? false);
|
|
102
|
-
const files = await glob(options.sources);
|
|
103
|
-
await extractStrings(files, options, pluginName);
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
configureServer(server) {
|
|
107
|
-
server.watcher.add(options.sources);
|
|
108
|
-
|
|
109
|
-
server.watcher.on("change", async (file) => {
|
|
110
|
-
if (options.sources.some((pattern) => file.match(pattern))) {
|
|
111
|
-
if (options.verbose) {
|
|
112
|
-
console.log(
|
|
113
|
-
`[${pluginName}] Source file changed: ${file}, re-running extraction`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
const files = await glob(options.sources);
|
|
117
|
-
await extractStrings(files, options, pluginName);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function generatePotfiles(
|
|
125
|
-
files: string[],
|
|
126
|
-
outputDir: string,
|
|
127
|
-
pluginName: string,
|
|
128
|
-
verbose = false
|
|
129
|
-
) {
|
|
130
|
-
// Group files by extension
|
|
131
|
-
const fileGroups = new Map<string, string[]>();
|
|
132
|
-
|
|
133
|
-
files.forEach((file) => {
|
|
134
|
-
const filename = path.basename(file);
|
|
135
|
-
const group = getFileGroup(filename);
|
|
136
|
-
if (!fileGroups.has(group)) {
|
|
137
|
-
fileGroups.set(group, []);
|
|
138
|
-
}
|
|
139
|
-
fileGroups.get(group)?.push(file);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Generate POTFILES for each group
|
|
143
|
-
const potFiles: string[] = [];
|
|
144
|
-
|
|
145
|
-
for (const [group, groupFiles] of fileGroups) {
|
|
146
|
-
const potfilePath = path.join(outputDir, `${group}.POTFILES`);
|
|
147
|
-
const content = groupFiles.join("\n");
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await fs.writeFile(potfilePath, content);
|
|
151
|
-
potFiles.push(potfilePath);
|
|
152
|
-
if (verbose) {
|
|
153
|
-
console.log(
|
|
154
|
-
`[${pluginName}] Generated ${group}.POTFILES with ${groupFiles.length} source files`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error(`[${pluginName}] Error writing ${group}.POTFILES:`, error);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return potFiles;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function getFileGroup(fullFilename: string): string {
|
|
166
|
-
// Process filename to handle .in extension
|
|
167
|
-
const { filename, extension } = processFilename(fullFilename);
|
|
168
|
-
|
|
169
|
-
// Special handling for metainfo.xml files
|
|
170
|
-
if (filename.endsWith(".metainfo.xml") || filename.endsWith(".appdata.xml")) {
|
|
171
|
-
return "metainfo";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
switch (extension) {
|
|
175
|
-
case ".ts":
|
|
176
|
-
case ".js":
|
|
177
|
-
case ".tsx":
|
|
178
|
-
return "js";
|
|
179
|
-
case ".ui":
|
|
180
|
-
case ".xml":
|
|
181
|
-
return "ui";
|
|
182
|
-
case ".blp":
|
|
183
|
-
return "blp";
|
|
184
|
-
case ".desktop":
|
|
185
|
-
return "desktop";
|
|
186
|
-
default:
|
|
187
|
-
return "other";
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function extractStrings(
|
|
192
|
-
files: string[],
|
|
193
|
-
options: XGettextPluginOptions,
|
|
194
|
-
pluginName: string
|
|
195
|
-
) {
|
|
196
|
-
const {
|
|
197
|
-
output,
|
|
198
|
-
domain = "messages",
|
|
199
|
-
keywords = [],
|
|
200
|
-
preset,
|
|
201
|
-
verbose = false,
|
|
202
|
-
} = options;
|
|
203
|
-
|
|
204
|
-
const noWrap = options.noWrap || false;
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const outputDir = path.dirname(output);
|
|
208
|
-
await ensureDirectory(outputDir);
|
|
209
|
-
|
|
210
|
-
// Read existing POT-Creation-Date from previous POT if present (for preservation)
|
|
211
|
-
let prevPotCreationDate: string | undefined;
|
|
212
|
-
try {
|
|
213
|
-
const existingPot = await fs.readFile(output, "utf-8");
|
|
214
|
-
const m = existingPot.match(/"POT-Creation-Date:\s*([^\n]+)\\n"/);
|
|
215
|
-
if (m && m[1]) {
|
|
216
|
-
prevPotCreationDate = m[1];
|
|
217
|
-
if (verbose) {
|
|
218
|
-
console.log(
|
|
219
|
-
`[${pluginName}] Found previous POT-Creation-Date '${prevPotCreationDate}'`
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} catch {
|
|
224
|
-
// No previous POT available
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Generate grouped POTFILES
|
|
228
|
-
const potFiles = await generatePotfiles(
|
|
229
|
-
files,
|
|
230
|
-
outputDir,
|
|
231
|
-
pluginName,
|
|
232
|
-
verbose
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
// Create temporary POT files for each group
|
|
236
|
-
const tempPotFiles: string[] = [];
|
|
237
|
-
|
|
238
|
-
for (const potFile of potFiles) {
|
|
239
|
-
const group = path.basename(potFile).split(".")[0];
|
|
240
|
-
const tempOutput = path.join(outputDir, `temp_${group}.pot`);
|
|
241
|
-
|
|
242
|
-
// Build base arguments
|
|
243
|
-
const baseArgs = [
|
|
244
|
-
"--package-name=" + domain,
|
|
245
|
-
options.version ? "--package-version=" + options.version : "",
|
|
246
|
-
"--output=" + tempOutput,
|
|
247
|
-
"--files-from=" + potFile,
|
|
248
|
-
"--from-code=UTF-8",
|
|
249
|
-
"--add-comments",
|
|
250
|
-
].filter(Boolean);
|
|
251
|
-
|
|
252
|
-
// Add bug report address if specified
|
|
253
|
-
if (options.msgidBugsAddress) {
|
|
254
|
-
baseArgs.push("--msgid-bugs-address=" + options.msgidBugsAddress);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Add copyright holder if specified
|
|
258
|
-
if (options.copyrightHolder) {
|
|
259
|
-
baseArgs.push("--copyright-holder=" + options.copyrightHolder);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Add language-specific settings
|
|
263
|
-
switch (group) {
|
|
264
|
-
case "js":
|
|
265
|
-
case "blp":
|
|
266
|
-
baseArgs.push("--language=JavaScript");
|
|
267
|
-
baseArgs.push(...keywords.map((k) => `--keyword=${k}`));
|
|
268
|
-
if (preset === "glib") {
|
|
269
|
-
baseArgs.push(...GLIB_PRESET_ARGS);
|
|
270
|
-
}
|
|
271
|
-
break;
|
|
272
|
-
case "ui":
|
|
273
|
-
baseArgs.push("--language=Glade");
|
|
274
|
-
break;
|
|
275
|
-
case "metainfo":
|
|
276
|
-
// Find the first existing metainfo.its file
|
|
277
|
-
const metainfoItsPath = await findMetainfoItsPath();
|
|
278
|
-
|
|
279
|
-
if (!metainfoItsPath) {
|
|
280
|
-
console.warn(
|
|
281
|
-
"Warning: Could not find metainfo.its in any of the expected locations"
|
|
282
|
-
);
|
|
283
|
-
// Continue without the ITS file
|
|
284
|
-
} else {
|
|
285
|
-
baseArgs.push(`--its=${metainfoItsPath}`);
|
|
286
|
-
}
|
|
287
|
-
break;
|
|
288
|
-
case "desktop":
|
|
289
|
-
baseArgs.push("--language=Desktop");
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Build final arguments with options handling
|
|
294
|
-
const args = buildCommandArgs(baseArgs, {
|
|
295
|
-
noLocation: options.noLocation,
|
|
296
|
-
noWrap,
|
|
297
|
-
additionalOptions: options.xgettextOptions
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
if (verbose) {
|
|
301
|
-
console.log(
|
|
302
|
-
`[${pluginName}] Running xgettext for ${group}:`,
|
|
303
|
-
args.join(" ")
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Enforce deterministic timestamps if requested
|
|
308
|
-
const env = { ...process.env };
|
|
309
|
-
if (options.deterministic) {
|
|
310
|
-
const epoch =
|
|
311
|
-
typeof options.sourceDateEpoch === "number"
|
|
312
|
-
? options.sourceDateEpoch
|
|
313
|
-
: 0;
|
|
314
|
-
env.SOURCE_DATE_EPOCH = String(epoch);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
await execa("xgettext", args, { env });
|
|
318
|
-
|
|
319
|
-
// Check if file exists before adding to tempPotFiles
|
|
320
|
-
try {
|
|
321
|
-
await fs.access(tempOutput);
|
|
322
|
-
tempPotFiles.push(tempOutput);
|
|
323
|
-
if (verbose) {
|
|
324
|
-
console.log(
|
|
325
|
-
`[${pluginName}] Successfully created temporary POT file: ${tempOutput}`
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
} catch (error) {
|
|
329
|
-
console.warn(
|
|
330
|
-
`[${pluginName}] Failed to create temporary POT file: ${tempOutput}`
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Combine all temporary POT files using msgcat
|
|
336
|
-
if (tempPotFiles.length > 0) {
|
|
337
|
-
const baseMsgcatArgs = ["--use-first", "-o", output, ...tempPotFiles];
|
|
338
|
-
const msgcatArgs = buildCommandArgs(baseMsgcatArgs, {
|
|
339
|
-
noLocation: options.noLocation,
|
|
340
|
-
sortOutput: options.sortOutput,
|
|
341
|
-
noWrap,
|
|
342
|
-
additionalOptions: options.msgcatOptions
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const env = { ...process.env };
|
|
346
|
-
if (options.deterministic) {
|
|
347
|
-
const epoch =
|
|
348
|
-
typeof options.sourceDateEpoch === "number"
|
|
349
|
-
? options.sourceDateEpoch
|
|
350
|
-
: 0;
|
|
351
|
-
env.SOURCE_DATE_EPOCH = String(epoch);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
await execa("msgcat", msgcatArgs, { env });
|
|
355
|
-
|
|
356
|
-
// Clean up temporary files
|
|
357
|
-
for (const tempFile of tempPotFiles) {
|
|
358
|
-
await fs.unlink(tempFile);
|
|
359
|
-
}
|
|
360
|
-
for (const potFile of potFiles) {
|
|
361
|
-
await fs.unlink(potFile);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Optionally normalize POT-Creation-Date header to a fixed or preserved value
|
|
366
|
-
if (options.fixedCreationDate || options.preserveCreationDate || options.deterministic) {
|
|
367
|
-
try {
|
|
368
|
-
let normalizedDate: string | undefined = undefined;
|
|
369
|
-
|
|
370
|
-
if (options.fixedCreationDate) {
|
|
371
|
-
normalizedDate = options.fixedCreationDate;
|
|
372
|
-
} else if (options.preserveCreationDate) {
|
|
373
|
-
if (prevPotCreationDate) {
|
|
374
|
-
normalizedDate = prevPotCreationDate;
|
|
375
|
-
if (verbose) {
|
|
376
|
-
console.log(
|
|
377
|
-
`[${pluginName}] Preserving existing POT-Creation-Date '${normalizedDate}'`
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (!normalizedDate && options.deterministic) {
|
|
384
|
-
normalizedDate = formatSourceDateEpoch(
|
|
385
|
-
typeof options.sourceDateEpoch === "number" ? options.sourceDateEpoch : 0
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (normalizedDate) {
|
|
390
|
-
const content = await fs.readFile(output, "utf-8");
|
|
391
|
-
const replaced = content.replace(
|
|
392
|
-
/^"POT-Creation-Date: .*\\n"$/m,
|
|
393
|
-
`"POT-Creation-Date: ${normalizedDate}\\n"`
|
|
394
|
-
);
|
|
395
|
-
if (replaced !== content) {
|
|
396
|
-
await fs.writeFile(output, replaced);
|
|
397
|
-
if (verbose) {
|
|
398
|
-
console.log(
|
|
399
|
-
`[${pluginName}] Normalized POT-Creation-Date to '${normalizedDate}'`
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} catch (e) {
|
|
405
|
-
console.warn(
|
|
406
|
-
`[${pluginName}] Failed to normalize POT-Creation-Date header:`,
|
|
407
|
-
e
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (options.autoUpdatePo) {
|
|
413
|
-
await updatePoFiles(
|
|
414
|
-
options.output,
|
|
415
|
-
pluginName,
|
|
416
|
-
options.verbose || false,
|
|
417
|
-
options
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
} catch (error) {
|
|
421
|
-
throw new Error(`Failed to extract translations: ${error}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async function updatePoFiles(
|
|
426
|
-
potFile: string,
|
|
427
|
-
pluginName: string,
|
|
428
|
-
verbose: boolean,
|
|
429
|
-
options: XGettextPluginOptions
|
|
430
|
-
) {
|
|
431
|
-
try {
|
|
432
|
-
const linguasPath = path.join(path.dirname(potFile), "LINGUAS");
|
|
433
|
-
const languages = (await fs.readFile(linguasPath, "utf-8"))
|
|
434
|
-
.split("\n")
|
|
435
|
-
.filter(Boolean);
|
|
436
|
-
|
|
437
|
-
for (const lang of languages) {
|
|
438
|
-
const poFile = path.join(path.dirname(potFile), `${lang}.po`);
|
|
439
|
-
if (verbose) {
|
|
440
|
-
console.log(`[${pluginName}] Updating ${poFile}`);
|
|
441
|
-
}
|
|
442
|
-
const baseMsgmergeArgs = ["--update", "--backup=none", poFile, potFile];
|
|
443
|
-
const args = buildCommandArgs(baseMsgmergeArgs, {
|
|
444
|
-
noLocation: options.noLocation,
|
|
445
|
-
noWrap: options.noWrap
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
const env = { ...process.env };
|
|
449
|
-
if (options.deterministic) {
|
|
450
|
-
const epoch =
|
|
451
|
-
typeof options.sourceDateEpoch === "number"
|
|
452
|
-
? options.sourceDateEpoch
|
|
453
|
-
: 0;
|
|
454
|
-
env.SOURCE_DATE_EPOCH = String(epoch);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
await execa("msgmerge", args, { env });
|
|
458
|
-
|
|
459
|
-
// Post-process with msgcat to unwrap existing wrapped lines
|
|
460
|
-
if (options.noWrap) {
|
|
461
|
-
const tempFile = poFile + ".tmp";
|
|
462
|
-
const msgcatArgs = ["--width=0", "--no-wrap", "-o", tempFile, poFile];
|
|
463
|
-
await execa("msgcat", msgcatArgs, { env });
|
|
464
|
-
await fs.rename(tempFile, poFile);
|
|
465
|
-
if (verbose) {
|
|
466
|
-
console.log(`[${pluginName}] Unwrapped lines in ${poFile}`);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
} catch (error) {
|
|
471
|
-
console.error(`[${pluginName}] Error updating PO files:`, error);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Formats a date in gettext header format using an epoch (seconds) in UTC timezone
|
|
477
|
-
* Example output: 1970-01-01 00:00+0000
|
|
478
|
-
*/
|
|
479
|
-
function formatSourceDateEpoch(epochSeconds: number): string {
|
|
480
|
-
const date = new Date(epochSeconds * 1000);
|
|
481
|
-
const pad = (n: number) => String(n).padStart(2, "0");
|
|
482
|
-
const year = date.getUTCFullYear();
|
|
483
|
-
const month = pad(date.getUTCMonth() + 1);
|
|
484
|
-
const day = pad(date.getUTCDate());
|
|
485
|
-
const hours = pad(date.getUTCHours());
|
|
486
|
-
const minutes = pad(date.getUTCMinutes());
|
|
487
|
-
return `${year}-${month}-${day} ${hours}:${minutes}+0000`;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Finds the first existing metainfo.its file from installed gettext versions
|
|
492
|
-
* @returns The path to the metainfo.its file if found, otherwise undefined
|
|
493
|
-
*/
|
|
494
|
-
async function findMetainfoItsPath(): Promise<string | undefined> {
|
|
495
|
-
// Default path
|
|
496
|
-
const defaultPath = "/usr/share/gettext/its/metainfo.its";
|
|
497
|
-
|
|
498
|
-
// Check default path first
|
|
499
|
-
if (existsSync(defaultPath)) {
|
|
500
|
-
return defaultPath;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
// Use glob to find all potential gettext version directories
|
|
505
|
-
const getTextDirs = await glob("/usr/share/gettext-*");
|
|
506
|
-
|
|
507
|
-
// Sort by version (newest first) if possible
|
|
508
|
-
getTextDirs.sort((a, b) => {
|
|
509
|
-
const versionA = a.replace("/usr/share/gettext-", "");
|
|
510
|
-
const versionB = b.replace("/usr/share/gettext-", "");
|
|
511
|
-
return versionB.localeCompare(versionA);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
// Add specific version paths we know about
|
|
515
|
-
const metainfoItsPaths = getTextDirs.map(
|
|
516
|
-
(dir) => `${dir}/its/metainfo.its`
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
// Find first existing path
|
|
520
|
-
return metainfoItsPaths.find((path) => existsSync(path));
|
|
521
|
-
} catch (error) {
|
|
522
|
-
console.warn("Error searching for metainfo.its:", error);
|
|
523
|
-
return undefined;
|
|
524
|
-
}
|
|
525
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"rootDir": "src",
|
|
4
|
-
"outDir": "lib",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"target": "ESNext",
|
|
7
|
-
"module": "ESNext",
|
|
8
|
-
"moduleResolution": "bundler",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"]
|
|
15
|
-
}
|