@wpelevator/wp-release 0.16.0 → 0.17.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 (56) hide show
  1. package/README.md +124 -6
  2. package/dist/changeset-config.d.ts +5 -0
  3. package/dist/changeset-config.js +3 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +10 -0
  6. package/dist/commands/i18n-audit.d.ts +2 -0
  7. package/dist/commands/i18n-audit.js +2 -0
  8. package/dist/commands/i18n-compile.d.ts +6 -0
  9. package/dist/commands/i18n-compile.js +76 -0
  10. package/dist/commands/i18n-json.d.ts +2 -0
  11. package/dist/commands/i18n-json.js +2 -0
  12. package/dist/commands/i18n-mo.d.ts +2 -0
  13. package/dist/commands/i18n-mo.js +2 -0
  14. package/dist/commands/i18n-php.d.ts +2 -0
  15. package/dist/commands/i18n-php.js +2 -0
  16. package/dist/commands/i18n-pot.d.ts +2 -0
  17. package/dist/commands/i18n-pot.js +2 -0
  18. package/dist/commands/i18n.d.ts +12 -0
  19. package/dist/commands/i18n.js +174 -0
  20. package/dist/i18n/atomic-output.d.ts +23 -0
  21. package/dist/i18n/atomic-output.js +90 -0
  22. package/dist/i18n/audit.d.ts +7 -0
  23. package/dist/i18n/audit.js +69 -0
  24. package/dist/i18n/catalog.d.ts +54 -0
  25. package/dist/i18n/catalog.js +86 -0
  26. package/dist/i18n/compile.d.ts +27 -0
  27. package/dist/i18n/compile.js +71 -0
  28. package/dist/i18n/extraction.d.ts +45 -0
  29. package/dist/i18n/extraction.js +105 -0
  30. package/dist/i18n/files.d.ts +7 -0
  31. package/dist/i18n/files.js +45 -0
  32. package/dist/i18n/generate.d.ts +49 -0
  33. package/dist/i18n/generate.js +240 -0
  34. package/dist/i18n/javascript.d.ts +7 -0
  35. package/dist/i18n/javascript.js +143 -0
  36. package/dist/i18n/metadata.d.ts +16 -0
  37. package/dist/i18n/metadata.js +117 -0
  38. package/dist/i18n/options.d.ts +52 -0
  39. package/dist/i18n/options.js +156 -0
  40. package/dist/i18n/packages.d.ts +24 -0
  41. package/dist/i18n/packages.js +86 -0
  42. package/dist/i18n/php.d.ts +9 -0
  43. package/dist/i18n/php.js +238 -0
  44. package/dist/i18n/placeholders.d.ts +9 -0
  45. package/dist/i18n/placeholders.js +13 -0
  46. package/dist/i18n/po.d.ts +24 -0
  47. package/dist/i18n/po.js +180 -0
  48. package/dist/i18n/pot.d.ts +16 -0
  49. package/dist/i18n/pot.js +119 -0
  50. package/dist/i18n/translation-json.d.ts +14 -0
  51. package/dist/i18n/translation-json.js +96 -0
  52. package/dist/i18n/translation-php.d.ts +6 -0
  53. package/dist/i18n/translation-php.js +35 -0
  54. package/dist/index.d.ts +22 -0
  55. package/dist/index.js +14 -0
  56. package/package.json +15 -3
@@ -0,0 +1,180 @@
1
+ import { po, mo } from 'gettext-parser';
2
+ import postfixModule from '@tannin/postfix';
3
+ import { ValidationError } from "../errors.js";
4
+ import { compareText } from "./catalog.js";
5
+ const postfix = postfixModule;
6
+ /**
7
+ * Validate gettext plural expressions with Tannin's parser, without eval or Function.
8
+ * @param header
9
+ */
10
+ function validatePluralForms(header) {
11
+ const match = /^\s*nplurals\s*=\s*([1-9]\d*)\s*;\s*plural\s*=\s*([^;]+)\s*;?\s*$/.exec(header);
12
+ if (!match || Number(match[1]) > 100) {
13
+ throw new ValidationError('PO requires a valid Plural-Forms header with nplurals and a plural expression.');
14
+ }
15
+ const expression = match[2] ?? '';
16
+ if (/[^\dn\s!%*/+<>=&|?:()\-]/.test(expression)) {
17
+ throw new ValidationError('Plural-Forms contains an unsupported expression token.');
18
+ }
19
+ try {
20
+ let depth = 0;
21
+ for (const term of postfix(expression)) {
22
+ if ('n' === term || /^\d+$/.test(String(term))) {
23
+ depth++;
24
+ }
25
+ else {
26
+ if (![
27
+ '!',
28
+ '*',
29
+ '/',
30
+ '%',
31
+ '+',
32
+ '-',
33
+ '<',
34
+ '<=',
35
+ '>',
36
+ '>=',
37
+ '==',
38
+ '!=',
39
+ '&&',
40
+ '||',
41
+ '?:',
42
+ ].includes(term)) {
43
+ throw new Error('Unsupported plural operator.');
44
+ }
45
+ let arity = 2;
46
+ if ('!' === term) {
47
+ arity = 1;
48
+ }
49
+ if ('?:' === term) {
50
+ arity = 3;
51
+ }
52
+ if (depth < arity) {
53
+ throw new Error('Invalid operand count.');
54
+ }
55
+ depth -= arity - 1;
56
+ }
57
+ }
58
+ if (1 !== depth) {
59
+ throw new Error('Incomplete expression.');
60
+ }
61
+ }
62
+ catch (error) {
63
+ throw new ValidationError(`Malformed Plural-Forms expression: ${String(error)}`);
64
+ }
65
+ return Number(match[1]);
66
+ }
67
+ /**
68
+ * Parse translated PO as data, filtering fuzzy, obsolete and untranslated entries.
69
+ * @param contents
70
+ * @param domain
71
+ */
72
+ export function parseTranslatedPo(contents, domain) {
73
+ let parsed;
74
+ try {
75
+ parsed = po.parse(contents, {
76
+ validation: true,
77
+ defaultCharset: 'UTF-8',
78
+ });
79
+ }
80
+ catch (error) {
81
+ throw new ValidationError(`Malformed PO catalog: ${String(error)}`);
82
+ }
83
+ if (!parsed.headers || !parsed.translations) {
84
+ throw new ValidationError('Malformed PO catalog: missing gettext headers or translations.');
85
+ }
86
+ const headers = Object.fromEntries(Object.entries(parsed.headers).map(([key, value]) => [
87
+ key.toLowerCase(),
88
+ value,
89
+ ]));
90
+ const locale = headers.language;
91
+ if (!locale || !/^[A-Za-z][A-Za-z0-9_@.-]*$/.test(locale)) {
92
+ throw new ValidationError('PO requires a non-empty valid Language header.');
93
+ }
94
+ const pluralForms = headers['plural-forms'] ?? '';
95
+ const nplurals = validatePluralForms(pluralForms);
96
+ const messages = [];
97
+ for (const entry of Object.values(parsed.translations).flatMap((context) => Object.values(context))) {
98
+ const flags = (entry.comments?.flag ?? '')
99
+ .split(',')
100
+ .map((flag) => flag.trim())
101
+ .filter(Boolean);
102
+ if (entry.msgid &&
103
+ !entry.obsolete &&
104
+ !flags.includes('fuzzy') &&
105
+ entry.msgstr.every((translation) => !!translation)) {
106
+ if (entry.msgstr.length !==
107
+ (undefined !== entry.msgid_plural ? nplurals : 1) ||
108
+ /[\0\u0004]/.test(entry.msgid) ||
109
+ /[\0\u0004]/.test(entry.msgctxt ?? '') ||
110
+ entry.msgstr.some((value) => value.includes('\0'))) {
111
+ throw new ValidationError(`Invalid translation forms or reserved separators for ${JSON.stringify(entry.msgid)}.`);
112
+ }
113
+ const references = (entry.comments?.reference ?? '')
114
+ .split(/\s+/)
115
+ .filter(Boolean)
116
+ .map((reference) => {
117
+ const location = /^(.*?)(?::(\d+))?$/.exec(reference);
118
+ return {
119
+ file: location?.[1] ?? reference,
120
+ ...(location?.[2]
121
+ ? { line: Number(location[2]) }
122
+ : {}),
123
+ };
124
+ });
125
+ messages.push({
126
+ id: entry.msgid,
127
+ ...(undefined !== entry.msgid_plural
128
+ ? { plural: entry.msgid_plural }
129
+ : {}),
130
+ ...(undefined !== entry.msgctxt
131
+ ? { context: entry.msgctxt }
132
+ : {}),
133
+ translations: [...entry.msgstr],
134
+ flags,
135
+ references,
136
+ comments: (entry.comments?.extracted ?? '')
137
+ .split('\n')
138
+ .filter(Boolean),
139
+ });
140
+ }
141
+ }
142
+ messages.sort((a, b) => compareText(translationKey(a), translationKey(b)));
143
+ return { domain, headers, messages, locale, pluralForms, nplurals };
144
+ }
145
+ /**
146
+ * WordPress/Jed lookup key uses context + EOT + singular, even for plural messages.
147
+ * @param message
148
+ */
149
+ export function translationKey(message) {
150
+ return (message.context ? `${message.context}\u0004` : '') + message.id;
151
+ }
152
+ /**
153
+ * Compile MO through gettext-parser; never write or mutate the input PO.
154
+ * @param catalog
155
+ */
156
+ export function compileMo(catalog) {
157
+ const table = {
158
+ charset: 'UTF-8',
159
+ headers: {
160
+ ...catalog.headers,
161
+ 'content-type': 'text/plain; charset=UTF-8',
162
+ },
163
+ translations: Object.create(null),
164
+ };
165
+ for (const message of catalog.messages) {
166
+ const context = message.context ?? '';
167
+ table.translations[context] ??= Object.create(null);
168
+ table.translations[context][message.id] = {
169
+ msgid: message.id,
170
+ ...(undefined !== message.context
171
+ ? { msgctxt: message.context }
172
+ : {}),
173
+ ...(undefined !== message.plural
174
+ ? { msgid_plural: message.plural }
175
+ : {}),
176
+ msgstr: [...message.translations],
177
+ };
178
+ }
179
+ return mo.compile(table);
180
+ }
@@ -0,0 +1,16 @@
1
+ import type { Catalog } from './catalog.ts';
2
+ /**
3
+ * Serialize deterministic UTF-8 POT bytes in the gettext layout, with no wall-clock data.
4
+ *
5
+ * Messages keep the catalog order, and each entry lists extracted comments, references and flags before its strings.
6
+ *
7
+ * @param catalog Catalog to serialize.
8
+ * @param options Serialization options.
9
+ * @param options.references Whether to include source references (default true).
10
+ * @param options.fileComment Static text to prepend as gettext comments.
11
+ * @return POT file contents.
12
+ */
13
+ export declare function serializePot(catalog: Catalog, options?: {
14
+ references?: boolean;
15
+ fileComment?: string;
16
+ }): Buffer;
@@ -0,0 +1,119 @@
1
+ import { mergeI18nHeaders, validateI18nFileComment, validateI18nHeaders, } from "./options.js";
2
+ const ESCAPES = {
3
+ '\\': '\\\\',
4
+ '"': '\\"',
5
+ '\x07': '\\a',
6
+ '\b': '\\b',
7
+ '\f': '\\f',
8
+ '\n': '\\n',
9
+ '\r': '\\r',
10
+ '\t': '\\t',
11
+ '\v': '\\v',
12
+ };
13
+ /**
14
+ * Serialize deterministic UTF-8 POT bytes in the gettext layout, with no wall-clock data.
15
+ *
16
+ * Messages keep the catalog order, and each entry lists extracted comments, references and flags before its strings.
17
+ *
18
+ * @param catalog Catalog to serialize.
19
+ * @param options Serialization options.
20
+ * @param options.references Whether to include source references (default true).
21
+ * @param options.fileComment Static text to prepend as gettext comments.
22
+ * @return POT file contents.
23
+ */
24
+ export function serializePot(catalog, options = {}) {
25
+ validateI18nHeaders(catalog.headers);
26
+ if (undefined !== options.fileComment) {
27
+ validateI18nFileComment(options.fileComment);
28
+ }
29
+ const fileComment = undefined === options.fileComment
30
+ ? ''
31
+ : options.fileComment
32
+ .split(/\r\n|\r|\n/)
33
+ .map((line) => `# ${line}\n`)
34
+ .join('');
35
+ const entries = catalog.messages.map((message) => serializeMessage(message, false !== options.references));
36
+ return Buffer.from(fileComment + [serializeHeaders(catalog), ...entries].join('\n'));
37
+ }
38
+ /**
39
+ * Serialize the header entry: the default headers in a fixed order, overridden in place or removed with `false`, then custom headers in the given order.
40
+ *
41
+ * @param catalog Catalog with the domain and header overrides.
42
+ * @return Header entry.
43
+ */
44
+ function serializeHeaders(catalog) {
45
+ const defaults = {
46
+ 'Project-Id-Version': catalog.domain,
47
+ 'Report-Msgid-Bugs-To': '',
48
+ 'Last-Translator': '',
49
+ 'Language-Team': '',
50
+ 'MIME-Version': '1.0',
51
+ 'Content-Type': 'text/plain; charset=UTF-8',
52
+ 'Content-Transfer-Encoding': '8bit',
53
+ 'POT-Creation-Date': '',
54
+ 'PO-Revision-Date': '',
55
+ 'X-Domain': catalog.domain,
56
+ };
57
+ const headers = Object.entries(mergeI18nHeaders(defaults, catalog.headers))
58
+ .filter(([, value]) => false !== value)
59
+ .map(([name, value]) => `"${escape(`${name}: ${value}\n`)}"\n`);
60
+ return `msgid ""\nmsgstr ""\n${headers.join('')}`;
61
+ }
62
+ /**
63
+ * Serialize one message entry.
64
+ *
65
+ * @param message Catalog message.
66
+ * @param references Whether to include source references.
67
+ * @return Message entry.
68
+ */
69
+ function serializeMessage(message, references) {
70
+ const lines = [
71
+ ...message.comments.flatMap((comment) => comment.split(/\r\n|\r|\n/).map((line) => `#. ${line}`)),
72
+ ...(references
73
+ ? [
74
+ ...new Set(message.references.map((reference) => `#: ${reference.file}${reference.line ? `:${reference.line}` : ''}`)),
75
+ ]
76
+ : []),
77
+ ...(message.flags.length
78
+ ? [`#, ${message.flags.join(', ')}`]
79
+ : []),
80
+ ...(undefined !== message.context
81
+ ? [poString('msgctxt', message.context)]
82
+ : []),
83
+ poString('msgid', message.id),
84
+ ];
85
+ if (undefined === message.plural) {
86
+ lines.push('msgstr ""');
87
+ }
88
+ else {
89
+ lines.push(poString('msgid_plural', message.plural), 'msgstr[0] ""', 'msgstr[1] ""');
90
+ }
91
+ return lines.join('\n') + '\n';
92
+ }
93
+ /**
94
+ * Format a gettext keyword and string, splitting strings with newlines into one line per segment.
95
+ *
96
+ * @param keyword Gettext keyword, like msgid.
97
+ * @param value String value.
98
+ * @return Serialized keyword lines.
99
+ */
100
+ function poString(keyword, value) {
101
+ if (!value.includes('\n')) {
102
+ return `${keyword} "${escape(value)}"`;
103
+ }
104
+ return [
105
+ `${keyword} ""`,
106
+ ...value
107
+ .split(/(?<=\n)/)
108
+ .map((segment) => `"${escape(segment)}"`),
109
+ ].join('\n');
110
+ }
111
+ /**
112
+ * Escape a string for a quoted gettext string.
113
+ *
114
+ * @param value Raw string.
115
+ * @return Escaped string.
116
+ */
117
+ function escape(value) {
118
+ return value.replace(/[\\"\x07\b\f\n\r\t\v]/g, (character) => ESCAPES[character] ?? character);
119
+ }
@@ -0,0 +1,14 @@
1
+ import type { TranslatedCatalog } from './po.ts';
2
+ /** Many-to-many source references to runtime scripts, both relative to the package. */
3
+ export type ScriptTranslationMap = Record<string, string[]>;
4
+ export interface ScriptTranslationOutput {
5
+ file: string;
6
+ source: string;
7
+ bytes: Buffer;
8
+ }
9
+ /**
10
+ * Compile deterministic Jed catalogs using explicit source/build mapping or unbundled identity paths.
11
+ * @param catalog
12
+ * @param mapping
13
+ */
14
+ export declare function compileScriptTranslations(catalog: TranslatedCatalog, mapping?: ScriptTranslationMap): ScriptTranslationOutput[];
@@ -0,0 +1,96 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { posix } from 'node:path';
3
+ import { ValidationError } from "../errors.js";
4
+ import { compareText } from "./catalog.js";
5
+ import { translationKey } from "./po.js";
6
+ function normalizedPath(path) {
7
+ if (!path ||
8
+ path.includes('\\') ||
9
+ path.includes('\0') ||
10
+ posix.isAbsolute(path) ||
11
+ /^[A-Za-z]:/.test(path) ||
12
+ path
13
+ .split('/')
14
+ .some((component) => ['', '.', '..'].includes(component))) {
15
+ throw new ValidationError(`Script translation paths must be normalized package-relative paths: ${path}.`);
16
+ }
17
+ return path;
18
+ }
19
+ /**
20
+ * Compile deterministic Jed catalogs using explicit source/build mapping or unbundled identity paths.
21
+ * @param catalog
22
+ * @param mapping
23
+ */
24
+ export function compileScriptTranslations(catalog, mapping) {
25
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(catalog.domain)) {
26
+ throw new ValidationError('JSON compilation requires a safe text domain filename component.');
27
+ }
28
+ if (mapping) {
29
+ for (const [source, scripts] of Object.entries(mapping)) {
30
+ normalizedPath(source);
31
+ if (!Array.isArray(scripts) ||
32
+ scripts.some((script) => 'string' !== typeof script)) {
33
+ throw new ValidationError('Script maps must contain arrays of runtime script paths.');
34
+ }
35
+ scripts.forEach(normalizedPath);
36
+ }
37
+ }
38
+ const groups = new Map();
39
+ for (const message of catalog.messages) {
40
+ for (const reference of message.references) {
41
+ const source = normalizedPath(reference.file);
42
+ let scripts = [];
43
+ if (mapping) {
44
+ scripts = Object.hasOwn(mapping, source)
45
+ ? mapping[source] ?? []
46
+ : [];
47
+ }
48
+ else if (/\.(?:js|mjs|cjs)$/.test(source)) {
49
+ scripts = [source];
50
+ }
51
+ else if (/\.(?:jsx|tsx?|mts|cts)$/.test(source)) {
52
+ throw new ValidationError(`A source-to-runtime script map is required for ${source}.`);
53
+ }
54
+ for (const script of scripts) {
55
+ // WordPress hashes the unminified script path when the registered URL ends in .min.js.
56
+ const runtime = normalizedPath(script).replace(/\.min\.js$/, '.js');
57
+ if (!/\.(?:js|mjs|cjs)$/.test(runtime)) {
58
+ throw new ValidationError(`Runtime translation target must be a JavaScript file: ${runtime}.`);
59
+ }
60
+ const entries = groups.get(runtime) ??
61
+ new Map();
62
+ entries.set(translationKey(message), message);
63
+ groups.set(runtime, entries);
64
+ }
65
+ }
66
+ }
67
+ return [...groups.entries()]
68
+ .sort(([a], [b]) => compareText(a, b))
69
+ .map(([source, entries]) => {
70
+ const translations = Object.create(null);
71
+ translations[''] = {
72
+ domain: catalog.domain,
73
+ lang: catalog.locale,
74
+ 'plural-forms': catalog.pluralForms,
75
+ };
76
+ for (const [key, message] of [...entries].sort(([a], [b]) => compareText(a, b))) {
77
+ translations[key] = [...message.translations];
78
+ }
79
+ const data = {
80
+ 'translation-revision-date': catalog.headers['po-revision-date'] ?? '',
81
+ generator: '@wpelevator/wp-release',
82
+ domain: catalog.domain,
83
+ locale_data: { messages: translations },
84
+ source,
85
+ };
86
+ return {
87
+ source,
88
+ file: `${'default' === catalog.domain
89
+ ? catalog.locale
90
+ : `${catalog.domain}-${catalog.locale}`}-${createHash('md5')
91
+ .update(source)
92
+ .digest('hex')}.json`,
93
+ bytes: Buffer.from(JSON.stringify(data) + '\n'),
94
+ };
95
+ });
96
+ }
@@ -0,0 +1,6 @@
1
+ import type { TranslatedCatalog } from './po.ts';
2
+ /**
3
+ * WordPress 6.5+ translation-array format; catalog text is never interpolated as PHP code.
4
+ * @param catalog
5
+ */
6
+ export declare function compilePhpTranslations(catalog: TranslatedCatalog): Buffer;
@@ -0,0 +1,35 @@
1
+ import { compareText } from "./catalog.js";
2
+ import { translationKey } from "./po.js";
3
+ /**
4
+ * Single-quoted PHP data values; NUL separators use an explicit safe literal expression.
5
+ * @param value
6
+ */
7
+ function phpString(value) {
8
+ return value
9
+ .split('\0')
10
+ .map((part) => `'${part
11
+ .replaceAll('\\', '\\\\')
12
+ .replaceAll("'", "\\'")}'`)
13
+ .join(' . "\\0" . ');
14
+ }
15
+ /**
16
+ * WordPress 6.5+ translation-array format; catalog text is never interpolated as PHP code.
17
+ * @param catalog
18
+ */
19
+ export function compilePhpTranslations(catalog) {
20
+ const headers = Object.entries(catalog.headers)
21
+ .filter(([key]) => 'messages' !== key)
22
+ .sort(([a], [b]) => compareText(a, b));
23
+ const messages = catalog.messages.map((message) => `\t\t${phpString(translationKey(message))} => ${phpString(message.translations.join('\0'))},`);
24
+ const lines = [
25
+ '<?php',
26
+ 'return [',
27
+ ...headers.map(([key, value]) => `\t${phpString(key)} => ${phpString(value)},`),
28
+ "\t'messages' => [",
29
+ ...messages,
30
+ '\t],',
31
+ '];',
32
+ '',
33
+ ];
34
+ return Buffer.from(lines.join('\n'));
35
+ }
package/dist/index.d.ts CHANGED
@@ -65,3 +65,25 @@ export type { BuildPackageInput, BuildPackageResult } from './package-build.ts';
65
65
  export type { BuildResult } from './commands/build.ts';
66
66
  export { wordpressCodec, sha256sumCodec, resolveManifestCodec, readIntegrity, } from './integrity.ts';
67
67
  export type { ManifestCodec, IntegrityConfig } from './integrity.ts';
68
+ export { aggregateCatalog, hasFatalDiagnostics } from './i18n/catalog.ts';
69
+ export type { Catalog, CatalogMessage, ExtractedMessage, SourceReference, I18nDiagnostic, ExtractionResult, CatalogResult, } from './i18n/catalog.ts';
70
+ export { parsePhpTranslations } from './i18n/php.ts';
71
+ export { parseJavascriptTranslations } from './i18n/javascript.ts';
72
+ export { parseMetadataTranslations, extractHeaderTranslations, } from './i18n/metadata.ts';
73
+ export type { ParseSourceInput } from './i18n/extraction.ts';
74
+ export { auditCatalog } from './i18n/audit.ts';
75
+ export { serializePot } from './i18n/pot.ts';
76
+ export { discoverI18nFiles } from './i18n/files.ts';
77
+ export { resolveI18nOptions, DEFAULT_I18N_INCLUDE, DEFAULT_I18N_EXCLUDE, } from './i18n/options.ts';
78
+ export type { I18nHeaders, I18nOptions } from './i18n/options.ts';
79
+ export { extractCatalog, generatePackagePot, resolvePotPath, validatePotDestinations, } from './i18n/generate.ts';
80
+ export type { ExtractCatalogInput, ExtractCatalogResult, PackagePotInput, PackagePotResult, } from './i18n/generate.ts';
81
+ export { resolveI18nPackages } from './i18n/packages.ts';
82
+ export type { I18nPackageInput, I18nPackage } from './i18n/packages.ts';
83
+ export { parseTranslatedPo, compileMo } from './i18n/po.ts';
84
+ export type { TranslatedCatalog } from './i18n/po.ts';
85
+ export { compileScriptTranslations } from './i18n/translation-json.ts';
86
+ export type { ScriptTranslationMap, ScriptTranslationOutput, } from './i18n/translation-json.ts';
87
+ export { compilePhpTranslations } from './i18n/translation-php.ts';
88
+ export { compileTranslations } from './i18n/compile.ts';
89
+ export type { CompileTranslationsInput, CompileTranslationsResult, } from './i18n/compile.ts';
package/dist/index.js CHANGED
@@ -29,3 +29,17 @@ export { getDeployPackages, getGitDeploys, getPackageFiles, getSvnDeploys, } fro
29
29
  export { zipPackage } from "./package-zip.js";
30
30
  export { buildPackage } from "./package-build.js";
31
31
  export { wordpressCodec, sha256sumCodec, resolveManifestCodec, readIntegrity, } from "./integrity.js";
32
+ export { aggregateCatalog, hasFatalDiagnostics } from "./i18n/catalog.js";
33
+ export { parsePhpTranslations } from "./i18n/php.js";
34
+ export { parseJavascriptTranslations } from "./i18n/javascript.js";
35
+ export { parseMetadataTranslations, extractHeaderTranslations, } from "./i18n/metadata.js";
36
+ export { auditCatalog } from "./i18n/audit.js";
37
+ export { serializePot } from "./i18n/pot.js";
38
+ export { discoverI18nFiles } from "./i18n/files.js";
39
+ export { resolveI18nOptions, DEFAULT_I18N_INCLUDE, DEFAULT_I18N_EXCLUDE, } from "./i18n/options.js";
40
+ export { extractCatalog, generatePackagePot, resolvePotPath, validatePotDestinations, } from "./i18n/generate.js";
41
+ export { resolveI18nPackages } from "./i18n/packages.js";
42
+ export { parseTranslatedPo, compileMo } from "./i18n/po.js";
43
+ export { compileScriptTranslations } from "./i18n/translation-json.js";
44
+ export { compilePhpTranslations } from "./i18n/translation-php.js";
45
+ export { compileTranslations } from "./i18n/compile.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wpelevator/wp-release",
3
- "version": "0.16.0",
4
- "description": "Package, checksum, sign, verify and version-check WordPress plugins and themes, and deploy plugins to WordPress.org SVN",
3
+ "version": "0.17.0",
4
+ "description": "Package, checksum, sign, verify and version-check WordPress plugins and themes, extract and compile their translations, and deploy plugins to WordPress.org SVN",
5
5
  "license": "MIT",
6
6
  "author": "WP Elevator (https://wpelevator.com)",
7
7
  "homepage": "https://github.com/wpelevator/basement/tree/main/packages/js/wp-release#readme",
@@ -42,15 +42,27 @@
42
42
  "svn",
43
43
  "zip",
44
44
  "checksums",
45
- "signing"
45
+ "signing",
46
+ "i18n",
47
+ "gettext",
48
+ "pot"
46
49
  ],
47
50
  "dependencies": {
51
+ "@tannin/postfix": "^1.1.0",
52
+ "@babel/parser": "^7.29.7",
53
+ "@babel/traverse": "^7.29.7",
54
+ "gettext-parser": "^8.0.0",
48
55
  "ignore": "^7.0.12",
56
+ "minimatch": "^10.2.6",
57
+ "php-parser": "^3.7.0",
49
58
  "semver": "^7.7.1",
50
59
  "yauzl": "^3.4.0",
51
60
  "yazl": "^3.3.1"
52
61
  },
53
62
  "devDependencies": {
63
+ "@babel/types": "^7.29.7",
64
+ "@types/babel__traverse": "^7.28.0",
65
+ "@types/gettext-parser": "^9.0.0",
54
66
  "@types/node": "^22.20.5",
55
67
  "@types/semver": "^7.8.0",
56
68
  "@types/yauzl": "^3.4.0",