@gjsify/vite-plugin-gettext 0.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.
- package/README.md +154 -0
- package/dist/gettext.d.ts +10 -0
- package/dist/gettext.js +73 -0
- package/dist/gettext.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/msgfmt.d.ts +9 -0
- package/dist/msgfmt.js +147 -0
- package/dist/msgfmt.js.map +1 -0
- package/dist/po2json.d.ts +9 -0
- package/dist/po2json.js +190 -0
- package/dist/po2json.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +37 -0
- package/dist/utils.js +94 -0
- package/dist/utils.js.map +1 -0
- package/dist/xgettext.d.ts +9 -0
- package/dist/xgettext.js +261 -0
- package/dist/xgettext.js.map +1 -0
- package/package.json +29 -0
- package/src/gettext.ts +104 -0
- package/src/index.ts +11 -0
- package/src/msgfmt.ts +209 -0
- package/src/po2json.ts +281 -0
- package/src/types.ts +120 -0
- package/src/utils.ts +119 -0
- package/src/xgettext.ts +337 -0
- package/tsconfig.json +22 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the xgettext plugin
|
|
3
|
+
* Used to extract translatable strings from source files
|
|
4
|
+
*/
|
|
5
|
+
export interface XGettextPluginOptions {
|
|
6
|
+
/** Glob patterns for source files to extract strings from */
|
|
7
|
+
sources: string[];
|
|
8
|
+
/** Output path for the POT template file */
|
|
9
|
+
output: string;
|
|
10
|
+
/** The gettext domain name, defaults to 'messages' */
|
|
11
|
+
domain?: string;
|
|
12
|
+
/** Keywords to look for when extracting strings, defaults to ['_', 'gettext', 'ngettext'] */
|
|
13
|
+
keywords?: string[];
|
|
14
|
+
/** Additional options to pass to xgettext command */
|
|
15
|
+
xgettextOptions?: string[];
|
|
16
|
+
/** Enable verbose logging */
|
|
17
|
+
verbose?: boolean;
|
|
18
|
+
/** Automatically update PO files after POT changes */
|
|
19
|
+
autoUpdatePo?: boolean;
|
|
20
|
+
/** Version of the POT file, defaults to '1.0' */
|
|
21
|
+
version?: string;
|
|
22
|
+
/** Preset to use for extracting strings, defaults to 'glib' */
|
|
23
|
+
preset?: "glib";
|
|
24
|
+
/** URL for reporting bugs in the POT file */
|
|
25
|
+
msgidBugsAddress?: string;
|
|
26
|
+
/** Copyright holder to set in the POT file */
|
|
27
|
+
copyrightHolder?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Configuration options for the gettext plugin
|
|
31
|
+
* Used to compile PO files to binary MO format
|
|
32
|
+
*/
|
|
33
|
+
export interface GettextPluginOptions {
|
|
34
|
+
/** Directory containing PO translation files */
|
|
35
|
+
poDirectory: string;
|
|
36
|
+
/** Output directory for compiled MO files */
|
|
37
|
+
moDirectory: string;
|
|
38
|
+
/** Filename of the MO file, defaults to 'messages.mo' */
|
|
39
|
+
filename?: string;
|
|
40
|
+
/** Enable verbose logging */
|
|
41
|
+
verbose?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Output format types for msgfmt
|
|
45
|
+
*/
|
|
46
|
+
export type MsgfmtFormat = "mo" | "java" | "java2" | "csharp" | "csharp-resources" | "tcl" | "desktop" | "xml" | "json" | "qt";
|
|
47
|
+
/**
|
|
48
|
+
* Configuration options for the msgfmt plugin
|
|
49
|
+
* Used to compile PO files to various formats including binary MO
|
|
50
|
+
*/
|
|
51
|
+
export interface MsgfmtPluginOptions {
|
|
52
|
+
/** Directory containing PO translation files */
|
|
53
|
+
poDirectory: string;
|
|
54
|
+
/** Output directory for compiled files */
|
|
55
|
+
outputDirectory: string;
|
|
56
|
+
/** The gettext domain name, defaults to 'messages' */
|
|
57
|
+
domain?: string;
|
|
58
|
+
/** Output filename, defaults to 'messages.mo' */
|
|
59
|
+
filename?: string;
|
|
60
|
+
/** Output format, defaults to 'mo' */
|
|
61
|
+
format?: MsgfmtFormat;
|
|
62
|
+
/** Path to template file, required for XML format */
|
|
63
|
+
templateFile?: string;
|
|
64
|
+
/** Enable verbose logging */
|
|
65
|
+
verbose?: boolean;
|
|
66
|
+
/** Additional options to pass to msgfmt command */
|
|
67
|
+
msgfmtOptions?: string[];
|
|
68
|
+
/** Whether to use the standard locale structure (locale/LANG/LC_MESSAGES/domain.mo) */
|
|
69
|
+
useLocaleStructure?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface PluginOptions {
|
|
72
|
+
pluginName: string;
|
|
73
|
+
verbose?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Options for the PO to JSON conversion plugin
|
|
77
|
+
*/
|
|
78
|
+
export interface GettextPo2JsonPluginOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Directory containing PO files
|
|
81
|
+
*/
|
|
82
|
+
poDirectory: string;
|
|
83
|
+
/**
|
|
84
|
+
* Directory where JSON files will be saved
|
|
85
|
+
*/
|
|
86
|
+
jsonDirectory: string;
|
|
87
|
+
/**
|
|
88
|
+
* Default language code (default: 'en')
|
|
89
|
+
*/
|
|
90
|
+
defaultLanguage?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Enable verbose logging
|
|
93
|
+
*/
|
|
94
|
+
verbose?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Additional translations to include in all language files
|
|
97
|
+
* Keys are identifiers and values are the English text
|
|
98
|
+
* The English text will be translated for non-default languages if translations exist
|
|
99
|
+
*/
|
|
100
|
+
additionalTranslations?: Record<string, string>;
|
|
101
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a gettext utility is installed and available
|
|
3
|
+
* @param command The command to check (msgfmt, xgettext, etc.)
|
|
4
|
+
* @param pluginName Name of the plugin for logging
|
|
5
|
+
* @param verbose Enable verbose logging
|
|
6
|
+
* @throws Error if the command is not found
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkDependencies(command: string, pluginName: string, verbose: boolean): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Scans the PO directory to find available language translations
|
|
11
|
+
* @param poDirectory Directory containing PO files
|
|
12
|
+
* @param pluginName Name of the plugin for logging
|
|
13
|
+
* @param verbose Enable verbose logging
|
|
14
|
+
* @returns Array of language codes found (e.g. ['de', 'fr', 'es'])
|
|
15
|
+
*/
|
|
16
|
+
export declare function findAvailableLanguages(poDirectory: string, pluginName: string, verbose: boolean): Promise<string[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Generates a LINGUAS file containing the list of available languages
|
|
19
|
+
* @param languages List of language codes
|
|
20
|
+
* @param poDirectory Directory where the LINGUAS file should be created
|
|
21
|
+
* @param verbose Enable verbose logging
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateLinguasFile(languages: string[], poDirectory: string, verbose?: boolean): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Creates directory structure recursively
|
|
26
|
+
* @param directory Directory path to create
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureDirectory(directory: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Processes a filename with potential .in suffix
|
|
31
|
+
* @param filePath Original file path or filename
|
|
32
|
+
* @returns Object with processed filename and extension
|
|
33
|
+
*/
|
|
34
|
+
export declare function processFilename(filePath: string): {
|
|
35
|
+
filename: string;
|
|
36
|
+
extension: string;
|
|
37
|
+
};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a gettext utility is installed and available
|
|
6
|
+
* @param command The command to check (msgfmt, xgettext, etc.)
|
|
7
|
+
* @param pluginName Name of the plugin for logging
|
|
8
|
+
* @param verbose Enable verbose logging
|
|
9
|
+
* @throws Error if the command is not found
|
|
10
|
+
*/
|
|
11
|
+
export async function checkDependencies(command, pluginName, verbose) {
|
|
12
|
+
try {
|
|
13
|
+
await execa(command, ["--version"]);
|
|
14
|
+
if (verbose) {
|
|
15
|
+
console.log(`[${pluginName}] Found ${command}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`${command} not found. Please install gettext:\n` +
|
|
20
|
+
" Ubuntu/Debian: sudo apt-get install gettext\n" +
|
|
21
|
+
" Fedora: sudo dnf install gettext\n" +
|
|
22
|
+
" Arch: sudo pacman -S gettext\n" +
|
|
23
|
+
" macOS: brew install gettext");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Scans the PO directory to find available language translations
|
|
28
|
+
* @param poDirectory Directory containing PO files
|
|
29
|
+
* @param pluginName Name of the plugin for logging
|
|
30
|
+
* @param verbose Enable verbose logging
|
|
31
|
+
* @returns Array of language codes found (e.g. ['de', 'fr', 'es'])
|
|
32
|
+
*/
|
|
33
|
+
export async function findAvailableLanguages(poDirectory, pluginName, verbose) {
|
|
34
|
+
try {
|
|
35
|
+
const files = await fs.readdir(poDirectory);
|
|
36
|
+
const languages = files
|
|
37
|
+
.filter((file) => file.endsWith(".po"))
|
|
38
|
+
.map((file) => path.basename(file, ".po"));
|
|
39
|
+
if (verbose) {
|
|
40
|
+
console.log(`[${pluginName}] Found languages: ${languages.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
return languages;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (verbose) {
|
|
46
|
+
console.log(`[${pluginName}] No PO directory found at ${poDirectory}`);
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generates a LINGUAS file containing the list of available languages
|
|
53
|
+
* @param languages List of language codes
|
|
54
|
+
* @param poDirectory Directory where the LINGUAS file should be created
|
|
55
|
+
* @param verbose Enable verbose logging
|
|
56
|
+
*/
|
|
57
|
+
export async function generateLinguasFile(languages, poDirectory, verbose = false) {
|
|
58
|
+
const linguasPath = path.join(poDirectory, "LINGUAS");
|
|
59
|
+
const content = languages.join("\n");
|
|
60
|
+
try {
|
|
61
|
+
await fs.writeFile(linguasPath, content);
|
|
62
|
+
if (verbose) {
|
|
63
|
+
console.log(`Generated LINGUAS file with languages: ${languages.join(", ")}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error("Error writing LINGUAS file:", error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates directory structure recursively
|
|
72
|
+
* @param directory Directory path to create
|
|
73
|
+
*/
|
|
74
|
+
export async function ensureDirectory(directory) {
|
|
75
|
+
await fs.mkdir(directory, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Processes a filename with potential .in suffix
|
|
79
|
+
* @param filePath Original file path or filename
|
|
80
|
+
* @returns Object with processed filename and extension
|
|
81
|
+
*/
|
|
82
|
+
export function processFilename(filePath) {
|
|
83
|
+
// Extract just the filename if a path is provided
|
|
84
|
+
const filename = path.basename(filePath);
|
|
85
|
+
let extension = path.extname(filename).toLowerCase();
|
|
86
|
+
let processedFilename = filename;
|
|
87
|
+
// Handle .in extension
|
|
88
|
+
if (filename.endsWith(".in")) {
|
|
89
|
+
processedFilename = filename.substring(0, filename.length - 3);
|
|
90
|
+
extension = path.extname(processedFilename).toLowerCase();
|
|
91
|
+
}
|
|
92
|
+
return { filename: processedFilename, extension };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
import type { XGettextPluginOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Vite plugin that extracts translatable strings from source files
|
|
5
|
+
* Uses GNU xgettext to generate a POT template file that can be used as basis for translations
|
|
6
|
+
* @param options Configuration options for the plugin
|
|
7
|
+
* @returns A Vite plugin that handles string extraction
|
|
8
|
+
*/
|
|
9
|
+
export declare function xgettextPlugin(options: XGettextPluginOptions): Plugin;
|
package/dist/xgettext.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {} 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 { checkDependencies, ensureDirectory, processFilename, } from "./utils.js";
|
|
8
|
+
// Add GLib preset constants
|
|
9
|
+
// From https://github.com/mesonbuild/meson/blob/467da051c859ba3112803b035e317bddadd756ef/mesonbuild/modules/i18n.py
|
|
10
|
+
const GLIB_PRESET_ARGS = [
|
|
11
|
+
"--from-code=UTF-8",
|
|
12
|
+
"--add-comments",
|
|
13
|
+
// https://developer.gnome.org/glib/stable/glib-I18N.html
|
|
14
|
+
"--keyword=_",
|
|
15
|
+
"--keyword=N_",
|
|
16
|
+
"--keyword=C_:1c,2",
|
|
17
|
+
"--keyword=NC_:1c,2",
|
|
18
|
+
"--keyword=g_dcgettext:2",
|
|
19
|
+
"--keyword=g_dngettext:2,3",
|
|
20
|
+
"--keyword=g_dpgettext2:2c,3",
|
|
21
|
+
"--flag=N_:1:pass-c-format",
|
|
22
|
+
"--flag=C_:2:pass-c-format",
|
|
23
|
+
"--flag=NC_:2:pass-c-format",
|
|
24
|
+
"--flag=g_dngettext:2:pass-c-format",
|
|
25
|
+
"--flag=g_strdup_printf:1:c-format",
|
|
26
|
+
"--flag=g_string_printf:2:c-format",
|
|
27
|
+
"--flag=g_string_append_printf:2:c-format",
|
|
28
|
+
"--flag=g_error_new:3:c-format",
|
|
29
|
+
"--flag=g_set_error:4:c-format",
|
|
30
|
+
"--flag=g_markup_printf_escaped:1:c-format",
|
|
31
|
+
"--flag=g_log:3:c-format",
|
|
32
|
+
"--flag=g_print:1:c-format",
|
|
33
|
+
"--flag=g_printerr:1:c-format",
|
|
34
|
+
"--flag=g_printf:1:c-format",
|
|
35
|
+
"--flag=g_fprintf:2:c-format",
|
|
36
|
+
"--flag=g_sprintf:2:c-format",
|
|
37
|
+
"--flag=g_snprintf:3:c-format",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Vite plugin that extracts translatable strings from source files
|
|
41
|
+
* Uses GNU xgettext to generate a POT template file that can be used as basis for translations
|
|
42
|
+
* @param options Configuration options for the plugin
|
|
43
|
+
* @returns A Vite plugin that handles string extraction
|
|
44
|
+
*/
|
|
45
|
+
export function xgettextPlugin(options) {
|
|
46
|
+
const pluginName = "vite-plugin-xgettext";
|
|
47
|
+
return {
|
|
48
|
+
name: pluginName,
|
|
49
|
+
async buildStart() {
|
|
50
|
+
await checkDependencies("xgettext", pluginName, options.verbose ?? false);
|
|
51
|
+
const files = await glob(options.sources);
|
|
52
|
+
await extractStrings(files, options, pluginName);
|
|
53
|
+
},
|
|
54
|
+
configureServer(server) {
|
|
55
|
+
server.watcher.add(options.sources);
|
|
56
|
+
server.watcher.on("change", async (file) => {
|
|
57
|
+
if (options.sources.some((pattern) => file.match(pattern))) {
|
|
58
|
+
if (options.verbose) {
|
|
59
|
+
console.log(`[${pluginName}] Source file changed: ${file}, re-running extraction`);
|
|
60
|
+
}
|
|
61
|
+
const files = await glob(options.sources);
|
|
62
|
+
await extractStrings(files, options, pluginName);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function generatePotfiles(files, outputDir, pluginName, verbose = false) {
|
|
69
|
+
// Group files by extension
|
|
70
|
+
const fileGroups = new Map();
|
|
71
|
+
files.forEach((file) => {
|
|
72
|
+
const filename = path.basename(file);
|
|
73
|
+
const group = getFileGroup(filename);
|
|
74
|
+
if (!fileGroups.has(group)) {
|
|
75
|
+
fileGroups.set(group, []);
|
|
76
|
+
}
|
|
77
|
+
fileGroups.get(group)?.push(file);
|
|
78
|
+
});
|
|
79
|
+
// Generate POTFILES for each group
|
|
80
|
+
const potFiles = [];
|
|
81
|
+
for (const [group, groupFiles] of fileGroups) {
|
|
82
|
+
const potfilePath = path.join(outputDir, `${group}.POTFILES`);
|
|
83
|
+
const content = groupFiles.join("\n");
|
|
84
|
+
try {
|
|
85
|
+
await fs.writeFile(potfilePath, content);
|
|
86
|
+
potFiles.push(potfilePath);
|
|
87
|
+
if (verbose) {
|
|
88
|
+
console.log(`[${pluginName}] Generated ${group}.POTFILES with ${groupFiles.length} source files`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`[${pluginName}] Error writing ${group}.POTFILES:`, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return potFiles;
|
|
96
|
+
}
|
|
97
|
+
function getFileGroup(fullFilename) {
|
|
98
|
+
// Process filename to handle .in extension
|
|
99
|
+
const { filename, extension } = processFilename(fullFilename);
|
|
100
|
+
// Special handling for metainfo.xml files
|
|
101
|
+
if (filename.endsWith(".metainfo.xml") || filename.endsWith(".appdata.xml")) {
|
|
102
|
+
return "metainfo";
|
|
103
|
+
}
|
|
104
|
+
switch (extension) {
|
|
105
|
+
case ".ts":
|
|
106
|
+
case ".js":
|
|
107
|
+
case ".tsx":
|
|
108
|
+
return "js";
|
|
109
|
+
case ".ui":
|
|
110
|
+
case ".xml":
|
|
111
|
+
return "ui";
|
|
112
|
+
case ".blp":
|
|
113
|
+
return "blp";
|
|
114
|
+
case ".desktop":
|
|
115
|
+
return "desktop";
|
|
116
|
+
default:
|
|
117
|
+
return "other";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function extractStrings(files, options, pluginName) {
|
|
121
|
+
const { output, domain = "messages", keywords = [], preset, verbose = false, } = options;
|
|
122
|
+
try {
|
|
123
|
+
const outputDir = path.dirname(output);
|
|
124
|
+
await ensureDirectory(outputDir);
|
|
125
|
+
// Generate grouped POTFILES
|
|
126
|
+
const potFiles = await generatePotfiles(files, outputDir, pluginName, verbose);
|
|
127
|
+
// Create temporary POT files for each group
|
|
128
|
+
const tempPotFiles = [];
|
|
129
|
+
for (const potFile of potFiles) {
|
|
130
|
+
const group = path.basename(potFile).split(".")[0];
|
|
131
|
+
const tempOutput = path.join(outputDir, `temp_${group}.pot`);
|
|
132
|
+
// Base arguments
|
|
133
|
+
let args = [
|
|
134
|
+
"--package-name=" + domain,
|
|
135
|
+
options.version ? "--package-version=" + options.version : "",
|
|
136
|
+
"--output=" + tempOutput,
|
|
137
|
+
"--files-from=" + potFile,
|
|
138
|
+
"--from-code=UTF-8",
|
|
139
|
+
"--add-comments",
|
|
140
|
+
];
|
|
141
|
+
// Add bug report address if specified
|
|
142
|
+
if (options.msgidBugsAddress) {
|
|
143
|
+
args.push("--msgid-bugs-address=" + options.msgidBugsAddress);
|
|
144
|
+
}
|
|
145
|
+
// Add copyright holder if specified
|
|
146
|
+
if (options.copyrightHolder) {
|
|
147
|
+
args.push("--copyright-holder=" + options.copyrightHolder);
|
|
148
|
+
}
|
|
149
|
+
// Add language-specific settings
|
|
150
|
+
switch (group) {
|
|
151
|
+
case "js":
|
|
152
|
+
case "blp":
|
|
153
|
+
args.push("--language=JavaScript");
|
|
154
|
+
args.push(...keywords.map((k) => `--keyword=${k}`));
|
|
155
|
+
if (preset === "glib") {
|
|
156
|
+
args.push(...GLIB_PRESET_ARGS);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case "ui":
|
|
160
|
+
args.push("--language=Glade");
|
|
161
|
+
break;
|
|
162
|
+
case "metainfo":
|
|
163
|
+
// Find the first existing metainfo.its file
|
|
164
|
+
const metainfoItsPath = await findMetainfoItsPath();
|
|
165
|
+
if (!metainfoItsPath) {
|
|
166
|
+
console.warn("Warning: Could not find metainfo.its in any of the expected locations");
|
|
167
|
+
// Continue without the ITS file
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
args.push(`--its=${metainfoItsPath}`);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "desktop":
|
|
174
|
+
args.push("--language=Desktop");
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (verbose) {
|
|
178
|
+
console.log(`[${pluginName}] Running xgettext for ${group}:`, args.join(" "));
|
|
179
|
+
}
|
|
180
|
+
await execa("xgettext", args);
|
|
181
|
+
// Check if file exists before adding to tempPotFiles
|
|
182
|
+
try {
|
|
183
|
+
await fs.access(tempOutput);
|
|
184
|
+
tempPotFiles.push(tempOutput);
|
|
185
|
+
if (verbose) {
|
|
186
|
+
console.log(`[${pluginName}] Successfully created temporary POT file: ${tempOutput}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
console.warn(`[${pluginName}] Failed to create temporary POT file: ${tempOutput}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Combine all temporary POT files using msgcat
|
|
194
|
+
if (tempPotFiles.length > 0) {
|
|
195
|
+
const msgcatArgs = ["--use-first", "-o", output, ...tempPotFiles];
|
|
196
|
+
await execa("msgcat", msgcatArgs);
|
|
197
|
+
// Clean up temporary files
|
|
198
|
+
for (const tempFile of tempPotFiles) {
|
|
199
|
+
await fs.unlink(tempFile);
|
|
200
|
+
}
|
|
201
|
+
for (const potFile of potFiles) {
|
|
202
|
+
await fs.unlink(potFile);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (options.autoUpdatePo) {
|
|
206
|
+
await updatePoFiles(options.output, pluginName, options.verbose || false);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
throw new Error(`Failed to extract translations: ${error}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function updatePoFiles(potFile, pluginName, verbose) {
|
|
214
|
+
try {
|
|
215
|
+
const linguasPath = path.join(path.dirname(potFile), "LINGUAS");
|
|
216
|
+
const languages = (await fs.readFile(linguasPath, "utf-8"))
|
|
217
|
+
.split("\n")
|
|
218
|
+
.filter(Boolean);
|
|
219
|
+
for (const lang of languages) {
|
|
220
|
+
const poFile = path.join(path.dirname(potFile), `${lang}.po`);
|
|
221
|
+
if (verbose) {
|
|
222
|
+
console.log(`[${pluginName}] Updating ${poFile}`);
|
|
223
|
+
}
|
|
224
|
+
await execa("msgmerge", ["--update", "--backup=none", poFile, potFile]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error(`[${pluginName}] Error updating PO files:`, error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Finds the first existing metainfo.its file from installed gettext versions
|
|
233
|
+
* @returns The path to the metainfo.its file if found, otherwise undefined
|
|
234
|
+
*/
|
|
235
|
+
async function findMetainfoItsPath() {
|
|
236
|
+
// Default path
|
|
237
|
+
const defaultPath = "/usr/share/gettext/its/metainfo.its";
|
|
238
|
+
// Check default path first
|
|
239
|
+
if (existsSync(defaultPath)) {
|
|
240
|
+
return defaultPath;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
// Use glob to find all potential gettext version directories
|
|
244
|
+
const getTextDirs = await glob("/usr/share/gettext-*");
|
|
245
|
+
// Sort by version (newest first) if possible
|
|
246
|
+
getTextDirs.sort((a, b) => {
|
|
247
|
+
const versionA = a.replace("/usr/share/gettext-", "");
|
|
248
|
+
const versionB = b.replace("/usr/share/gettext-", "");
|
|
249
|
+
return versionB.localeCompare(versionA);
|
|
250
|
+
});
|
|
251
|
+
// Add specific version paths we know about
|
|
252
|
+
const metainfoItsPaths = getTextDirs.map((dir) => `${dir}/its/metainfo.its`);
|
|
253
|
+
// Find first existing path
|
|
254
|
+
return metainfoItsPaths.find((path) => existsSync(path));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.warn("Error searching for metainfo.its:", error);
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=xgettext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xgettext.js","sourceRoot":"","sources":["../src/xgettext.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/vite-plugin-gettext",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Vite plugin for compiling Gettext PO files",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"clear": "rm -rf dist",
|
|
10
|
+
"check": "tsc --noEmit",
|
|
11
|
+
"build": "tsc --declaration"
|
|
12
|
+
},
|
|
13
|
+
"author": "Pascal Garber <pascal@mailfreun.de>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/gettext-parser": "^8.0.0",
|
|
17
|
+
"@types/node": "^24.3.0",
|
|
18
|
+
"gettext-parser": "^8.0.0",
|
|
19
|
+
"typescript": "^5.9.2",
|
|
20
|
+
"vite": "^7.1.2"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"vite": "*"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"execa": "^9.6.0",
|
|
27
|
+
"fast-glob": "^3.3.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/gettext.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { GettextPluginOptions } from "./types.js";
|
|
5
|
+
import {
|
|
6
|
+
checkDependencies,
|
|
7
|
+
findAvailableLanguages,
|
|
8
|
+
generateLinguasFile,
|
|
9
|
+
ensureDirectory,
|
|
10
|
+
} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Vite plugin that compiles PO translation files to binary MO format
|
|
14
|
+
* The MO files are placed in the standard gettext directory structure:
|
|
15
|
+
* {moDirectory}/locale/{lang}/LC_MESSAGES/messages.mo
|
|
16
|
+
* @param options Configuration options for the plugin
|
|
17
|
+
* @returns A Vite plugin that handles PO compilation
|
|
18
|
+
*/
|
|
19
|
+
export function gettextPlugin(options: GettextPluginOptions): Plugin {
|
|
20
|
+
const {
|
|
21
|
+
poDirectory,
|
|
22
|
+
moDirectory,
|
|
23
|
+
filename = "messages.mo",
|
|
24
|
+
verbose = false,
|
|
25
|
+
} = options;
|
|
26
|
+
|
|
27
|
+
const pluginName = "vite-plugin-gettext";
|
|
28
|
+
|
|
29
|
+
async function compileMoFiles() {
|
|
30
|
+
try {
|
|
31
|
+
// Check if PO directory exists
|
|
32
|
+
try {
|
|
33
|
+
await ensureDirectory(poDirectory);
|
|
34
|
+
} catch {
|
|
35
|
+
if (verbose) {
|
|
36
|
+
console.log(
|
|
37
|
+
`[${pluginName}] PO directory ${poDirectory} does not exist yet, skipping compilation`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Find available languages
|
|
44
|
+
const languages = await findAvailableLanguages(
|
|
45
|
+
poDirectory,
|
|
46
|
+
pluginName,
|
|
47
|
+
verbose
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (languages.length === 0) {
|
|
51
|
+
if (verbose) {
|
|
52
|
+
console.log(`[${pluginName}] No translation files found`);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate LINGUAS file
|
|
58
|
+
await generateLinguasFile(languages, poDirectory, verbose);
|
|
59
|
+
|
|
60
|
+
// Create MO directory
|
|
61
|
+
await ensureDirectory(path.join(moDirectory, "locale"));
|
|
62
|
+
|
|
63
|
+
for (const lang of languages) {
|
|
64
|
+
const poFile = path.join(poDirectory, `${lang}.po`);
|
|
65
|
+
const moPath = path.join(moDirectory, "locale", lang, "LC_MESSAGES");
|
|
66
|
+
const moFile = path.join(moPath, filename);
|
|
67
|
+
|
|
68
|
+
await ensureDirectory(moPath);
|
|
69
|
+
|
|
70
|
+
if (verbose) {
|
|
71
|
+
console.log(`[${pluginName}] Compiling ${poFile} to ${moFile}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await execa("msgfmt", ["--output-file=" + moFile, poFile]);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`Failed to compile MO files: ${error}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
name: pluginName,
|
|
83
|
+
|
|
84
|
+
async buildStart() {
|
|
85
|
+
await checkDependencies("msgfmt", pluginName, verbose);
|
|
86
|
+
await compileMoFiles();
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
configureServer(server) {
|
|
90
|
+
server.watcher.add(poDirectory);
|
|
91
|
+
|
|
92
|
+
server.watcher.on("change", async (file) => {
|
|
93
|
+
if (file.endsWith(".po")) {
|
|
94
|
+
if (verbose) {
|
|
95
|
+
console.log(
|
|
96
|
+
`[${pluginName}] PO file changed: ${file}, recompiling`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
await compileMoFiles();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|