Skip to content
Merged
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ npx @wp-blocks/make-pot src languages --charset='utf-8' --include="src/**/*.{ts,
- `--package-name <name>`: Specifies the package name.
- `--location`: Includes location information in the `.pot` file.
- `--ignore-domain`: Ignores text domain in the processing.
- `--translation-domains`: Restrict to specific translation domains.
- `--mergePaths <paths>`: Merges with existing POT file(s).
- `--subtractPaths <paths>`: Subtracts strings from existing POT file(s).
- `--subtractAndMerge`: Subtracts and merges strings from existing POT file(s).
- `--include <files>`: Includes specific files for processing.
- `--exclude <files>`: Excludes specific files from processing.
- `--silent`: Suppresses output to stdout.
- `--silent`: Suppresses verbose output to stdout.
- `--json`: Outputs the JSON gettext data.
- `--charset`: Defines the encoding charset of the pot file, you can choose "iso-8859-1" and "uft-8" (defaults to iso-8859-1)
- `--translation-domains`: Restrict to specific translation domains.
- `--output`: Outputs the gettext data.

### Example usage
Expand Down
45 changes: 2 additions & 43 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wp-blocks/make-pot",
"version": "1.6.5",
"version": "1.6.6",
"license": "GPL-3.0-or-later",
"homepage": "https://wp-blocks.github.io/make-pot/",
"description": "A Node.js script for generating a POT file from source code",
Expand Down Expand Up @@ -68,7 +68,6 @@
"gettext-merger": "^1.2.1",
"gettext-parser": "^4.2.0",
"glob": "^11.1.0",
"tannin": "^1.2.0",
"tree-sitter": "^0.21.1",
"tree-sitter-javascript": "^0.23.1",
"tree-sitter-php": "^0.23.12",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/getArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { parseCliArgs } from "./parseCli.js";
export function getArgs(userArgs = {}): Args | MakeJsonArgs {
const args = yargs
.default(hideBin(process.argv))
.help("h")
.alias("help", "help")
.help("help")
.alias("h", "help")
.usage("Usage: $0 <source> [destination] [options]")
.positional("sourceDirectory", {
describe: "Source directory",
Expand Down
2 changes: 1 addition & 1 deletion src/extractors/auditStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function audit(args: Args, translationsUnion: SetOfBlocks) {
auditor.auditStrings(translationsUnion.blocks);

/** Get and print the results of the audit process */
console.log("\nAudit Complete!");
console.log("Audit Complete!");
if (auditor.getResults().length === 0) {
console.log("No issues found! 🎉");
// if there are no errors, we can remove the audit.log file
Expand Down
18 changes: 10 additions & 8 deletions src/extractors/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function validateRequiredFields(

if (missingFields.length > 0) {
if (!silent) {
console.error("\n! Missing required information for POT file header:\n");
console.error("\n⚠️ Missing required information for POT file header:\n");

for (const field of missingFields) {
console.error(
Expand Down Expand Up @@ -343,11 +343,13 @@ export function translationsHeaders(args: Args): SetOfBlocks {
// the main file is the plugin main php file or the css file
const fakePath = domain === "plugin" ? `${args.slug}.php` : "style.css";

return new SetOfBlocks([
buildBlock(`Name of the ${domain}`, name, [fakePath]),
buildBlock(`Url of the ${domain}`, url, [fakePath]),
buildBlock(`Description of the ${domain}`, description, [fakePath]),
buildBlock(`Author of the ${domain}`, author, [fakePath]),
buildBlock(`Author URI of the ${domain}`, authorUri, [fakePath]),
]);
const blocks = [];

if (name) blocks.push(buildBlock(`Name of the ${domain}`, name, [fakePath]));
if (url) blocks.push(buildBlock(`Url of the ${domain}`, url, [fakePath]));
if (description) blocks.push(buildBlock(`Description of the ${domain}`, description, [fakePath]));
if (author) blocks.push(buildBlock(`Author of the ${domain}`, author, [fakePath]));
if (authorUri) blocks.push(buildBlock(`Author URI of the ${domain}`, authorUri, [fakePath]));

return new SetOfBlocks(blocks);
}
4 changes: 2 additions & 2 deletions src/extractors/php.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function extractPhpPluginData(args: Args): Record<string, string> {
* @return {Record<string, string>} - A record containing the plugin information.
*/
export function parsePHPFile(phpContent: string): Record<string, string> {
const match = phpContent.match(/\/\*\*([\s\S]*?)\*\//);
const match = phpContent.match(/\/\*\*?([\s\S]*?)\*\//);

if (match?.[1] && match) {
const commentBlock = match[1];
Expand All @@ -42,7 +42,7 @@ export function parsePHPFile(phpContent: string): Record<string, string> {
const pluginInfo: Record<string, string> = {};

for (const line of lines) {
const keyValueMatch = line.match(/^\s*\*\s*([^:]+):\s*(.*)/);
const keyValueMatch = line.match(/^\s*(?:\*\s*)?([^:]+):\s*(.*)/);

if (!keyValueMatch) {
continue;
Expand Down
3 changes: 3 additions & 0 deletions src/parser/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export async function exec(args: Args): Promise<string> {
/** The pot file header contains the data about the plugin or theme */
const potHeader = await generateHeader(args);

/** Capture the start time */
args.timeStart = new Date();

/** We need to find the main file data so that the definitions are extracted from the plugin or theme files */
let translationsUnion = translationsHeaders(args);

Expand Down
18 changes: 0 additions & 18 deletions src/parser/taskRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export async function taskRunner(
args: Args,
progressBar: SingleBar,
) {
const messages: string[] = [];
// Create a new array of promises that update the bar when they finish.
const tasksWithProgress = tasks.map((task) =>
task.then((result) => {
Expand Down Expand Up @@ -48,16 +47,6 @@ export async function taskRunner(
* Add the strings to the destination set
*/
destination.addArray(result.blocks);
const strings = result.blocks.map((b) => b.msgid);

/* Log the results */
if (args.options?.silent !== true) {
messages.push(
`✅ ${result.path} - ${strings.length} strings found [${strings.join(", ")}]`,
);
}
} else if (args.options?.silent !== true) {
messages.push(`❌ ${result.path} - has no strings`);
}
}
})
Expand All @@ -67,12 +56,5 @@ export async function taskRunner(

progressBar.stop();

console.log("\n🎉 Done!");
console.log(
`📝 Found ${Object.values(destination.blocks).length} translation strings in ${path.resolve(args.paths.cwd)}.`,
);

console.log(messages.join(os.EOL));

return destination;
}
4 changes: 1 addition & 3 deletions src/potCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import { printModuleInfo, printTimeElapsed } from "./utils/common.js";
export default function potCommand(args: Args) {
if (Object.keys(args).length > 0) {
printModuleInfo();
/* capture the start time */
const timeStart = new Date();
/** make the pot file */
makePot(args)
.then(() => {
/* output the end time */
printTimeElapsed("Make-Pot", timeStart);
printTimeElapsed("Make-Pot", args.timeStart);
})
.catch((error) => {
console.error(`🫤 Make-pot - ${error}`);
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface Args {
};
headers?: { [key in PotHeaders]: string };
patterns: Patterns;
timeStart?: Date;
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export function getPkgJsonData(
export function printModuleInfo() {
const { version, name } = getPkgJsonData(modulePath, "name", "version");
/* print the version */
console.log(`${name} version: ${version}`);
console.log(`\n${name} version: ${version}`);
}

/**
Expand All @@ -159,11 +159,14 @@ export function printModuleInfo() {
*/
export function printTimeElapsed(
scriptName: "Make-Pot" | "Make-Json",
timeStart: Date,
timeStart?: Date,
timeEnd: Date = new Date(),
) {
if (!timeStart) {
return;
}
console.log(
`\n🚀 ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${timeEnd.getTime() - timeStart.getTime()
`\n🚀 ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${timeEnd.getTime() - timeStart.getTime()
}ms`,
);
}
Expand Down
57 changes: 45 additions & 12 deletions src/utils/output.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { SetOfBlocks } from "gettext-merger";
import Tannin from "tannin";
import type { Args } from "../types.js";
import type { Args, JedData, MakeJson } from "../types.js";
import type { GetTextTranslation } from 'gettext-parser'

/**
Expand All @@ -16,17 +15,51 @@ export function outputJson(
potHeader: Record<string, string> | null,
translationsUnion: SetOfBlocks,
): string {
const jedData = {
[args.slug]: {
"": potHeader ?? {},
...(translationsUnion.toJson() as{
[key: string]: {
[key: string]: GetTextTranslation;
};
}),
const domain = args.slug;
const gettextTranslations = translationsUnion.toJson() as {
[key: string]: {
[key: string]: GetTextTranslation;
};
};

const jedData: JedData = {
[domain]: {
"": {
domain,
lang: potHeader?.Language || "en",
plural_forms:
potHeader?.["Plural-Forms"] || "nplurals=2; plural=(n != 1);",
...potHeader,
},
},
};
const i18n = new Tannin(jedData);

return i18n.toString();
// Process all translations
for (const msgctxt of Object.keys(gettextTranslations)) {
const contextTranslations = gettextTranslations[msgctxt];

for (const msgid of Object.keys(contextTranslations)) {
const translation = contextTranslations[msgid];

// Skip empty msgid (header) as we've already handled it
if (msgid === "") continue;

// Construct the key using context if available
const key =
msgctxt && msgctxt !== "" ? `${msgctxt}\u0004${msgid}` : msgid;

// Add the translation to the Jed data structure
jedData[domain][key] = translation.msgstr;
}
}

const makeJson: MakeJson = {
domain,
"translation-revision-date": new Date().toISOString(),
generator: "makePot",
source: "",
locale_data: jedData,
};

return JSON.stringify(makeJson, null, 2);
}
47 changes: 47 additions & 0 deletions tests/parse-php.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { describe, it } = require("node:test");
const assert = require("node:assert");
const { parsePHPFile } = require("../lib/extractors/php.js");

describe("parsePHPFile", () => {
it("correctly extracts headers from standard docblock (/** ... */)", () => {
const phpContent = `<?php
/**
* Plugin Name: My Plugin
* Description: A basic WordPress plugin template with translation support.
* Version: 1.0
* Author: Your Name
* Text Domain: my-plugin
* Domain Path: /languages
*/`;
const result = parsePHPFile(phpContent);
assert.deepStrictEqual(result, {
name: "My Plugin",
description: "A basic WordPress plugin template with translation support.",
version: "1.0",
author: "Your Name",
textDomain: "my-plugin",
domainPath: "/languages",
});
});

it("correctly extracts headers from simple comment block (/* ... */)", () => {
const phpContent = `<?php
/*
Plugin Name: My Plugin
Description: A basic WordPress plugin template with translation support.
Version: 1.0
Author: Your Name
Text Domain: my-plugin
Domain Path: /languages
*/`;
const result = parsePHPFile(phpContent);
assert.deepStrictEqual(result, {
name: "My Plugin",
description: "A basic WordPress plugin template with translation support.",
version: "1.0",
author: "Your Name",
textDomain: "my-plugin",
domainPath: "/languages",
});
});
});