claude-plugin-wordpress-manager 1.5.0 → 1.7.1
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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +97 -0
- package/README.md +27 -13
- package/agents/wp-accessibility-auditor.md +206 -0
- package/agents/wp-content-strategist.md +18 -0
- package/agents/wp-deployment-engineer.md +34 -2
- package/agents/wp-performance-optimizer.md +12 -0
- package/agents/wp-security-auditor.md +20 -0
- package/agents/wp-security-hardener.md +266 -0
- package/agents/wp-site-manager.md +14 -0
- package/agents/wp-test-engineer.md +207 -0
- package/docs/guides/INDEX.md +46 -0
- package/docs/guides/wp-blog.md +590 -0
- package/docs/guides/wp-design-system.md +976 -0
- package/docs/guides/wp-ecommerce.md +786 -0
- package/docs/guides/wp-landing-page.md +762 -0
- package/docs/guides/wp-portfolio.md +713 -0
- package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
- package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
- package/package.json +2 -2
- package/skills/wordpress-router/references/decision-tree.md +12 -2
- package/skills/wp-accessibility/SKILL.md +170 -0
- package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
- package/skills/wp-accessibility/references/a11y-testing.md +222 -0
- package/skills/wp-accessibility/references/block-a11y.md +247 -0
- package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
- package/skills/wp-accessibility/references/media-a11y.md +254 -0
- package/skills/wp-accessibility/references/theme-a11y.md +309 -0
- package/skills/wp-audit/SKILL.md +4 -0
- package/skills/wp-block-development/SKILL.md +5 -0
- package/skills/wp-block-themes/SKILL.md +4 -0
- package/skills/wp-e2e-testing/SKILL.md +186 -0
- package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
- package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
- package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
- package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
- package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
- package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
- package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
- package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
- package/skills/wp-headless/SKILL.md +168 -0
- package/skills/wp-headless/references/api-layer-choice.md +160 -0
- package/skills/wp-headless/references/cors-config.md +245 -0
- package/skills/wp-headless/references/frontend-integration.md +331 -0
- package/skills/wp-headless/references/headless-auth.md +286 -0
- package/skills/wp-headless/references/webhooks.md +277 -0
- package/skills/wp-headless/references/wpgraphql.md +331 -0
- package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
- package/skills/wp-i18n/SKILL.md +170 -0
- package/skills/wp-i18n/references/js-i18n.md +201 -0
- package/skills/wp-i18n/references/multilingual-setup.md +219 -0
- package/skills/wp-i18n/references/php-i18n.md +196 -0
- package/skills/wp-i18n/references/rtl-support.md +206 -0
- package/skills/wp-i18n/references/translation-workflow.md +178 -0
- package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
- package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
- package/skills/wp-interactivity-api/SKILL.md +4 -0
- package/skills/wp-plugin-development/SKILL.md +6 -0
- package/skills/wp-rest-api/SKILL.md +4 -0
- package/skills/wp-security/SKILL.md +179 -0
- package/skills/wp-security/references/api-restriction.md +147 -0
- package/skills/wp-security/references/authentication-hardening.md +105 -0
- package/skills/wp-security/references/filesystem-hardening.md +105 -0
- package/skills/wp-security/references/http-headers.md +105 -0
- package/skills/wp-security/references/incident-response.md +144 -0
- package/skills/wp-security/references/user-capabilities.md +115 -0
- package/skills/wp-security/references/wp-config-security.md +129 -0
- package/skills/wp-security/scripts/security_inspect.mjs +393 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# WP-CLI i18n Commands
|
|
2
|
+
|
|
3
|
+
Use this file when working with WP-CLI's `i18n` command group for translation management.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# WP-CLI i18n is built-in since WP-CLI 2.0
|
|
9
|
+
wp i18n --help
|
|
10
|
+
|
|
11
|
+
# If missing, install the i18n command package
|
|
12
|
+
wp package install wp-cli/i18n-command
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## make-pot — Generate POT template
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Basic usage (from plugin/theme root)
|
|
19
|
+
wp i18n make-pot . languages/my-text-domain.pot
|
|
20
|
+
|
|
21
|
+
# Full options
|
|
22
|
+
wp i18n make-pot . languages/my-text-domain.pot \
|
|
23
|
+
--slug=my-plugin \
|
|
24
|
+
--domain=my-text-domain \
|
|
25
|
+
--include="src/,includes/,templates/" \
|
|
26
|
+
--exclude="node_modules/,vendor/,tests/,build/" \
|
|
27
|
+
--skip-js \
|
|
28
|
+
--skip-php \
|
|
29
|
+
--skip-block-json \
|
|
30
|
+
--skip-theme-json \
|
|
31
|
+
--skip-audit \
|
|
32
|
+
--headers='{"Report-Msgid-Bugs-To":"support@example.com","Last-Translator":"Dev Team"}' \
|
|
33
|
+
--file-comment="Copyright (c) 2026 My Company"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Key options
|
|
37
|
+
|
|
38
|
+
| Option | Purpose |
|
|
39
|
+
|--------|---------|
|
|
40
|
+
| `--domain=<domain>` | Only extract strings with this text domain |
|
|
41
|
+
| `--include=<paths>` | Comma-separated list of directories to scan |
|
|
42
|
+
| `--exclude=<paths>` | Comma-separated list of directories to skip |
|
|
43
|
+
| `--skip-js` | Skip JavaScript file scanning |
|
|
44
|
+
| `--skip-php` | Skip PHP file scanning |
|
|
45
|
+
| `--skip-block-json` | Skip block.json translation extraction |
|
|
46
|
+
| `--skip-theme-json` | Skip theme.json translation extraction |
|
|
47
|
+
| `--skip-audit` | Skip string auditing (faster, no warnings) |
|
|
48
|
+
| `--headers` | JSON object of PO headers |
|
|
49
|
+
|
|
50
|
+
### What it scans
|
|
51
|
+
|
|
52
|
+
- PHP: `__()`, `_e()`, `_x()`, `_ex()`, `_n()`, `_nx()`, `esc_html__()`, `esc_html_e()`, `esc_attr__()`, `esc_attr_e()`, `esc_html_x()`, `esc_attr_x()`
|
|
53
|
+
- JS: `@wordpress/i18n` functions via static analysis
|
|
54
|
+
- block.json: `title`, `description`, `keywords`, `styles[].label`, `variations[].title`
|
|
55
|
+
- theme.json: Custom template names, style variation names
|
|
56
|
+
|
|
57
|
+
## update-po — Merge new strings into PO files
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Update all PO files in the directory
|
|
61
|
+
wp i18n update-po languages/my-text-domain.pot languages/
|
|
62
|
+
|
|
63
|
+
# Update a specific PO file
|
|
64
|
+
wp i18n update-po languages/my-text-domain.pot languages/my-text-domain-it_IT.po
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This is equivalent to `msgmerge --update` but integrated into WP-CLI. Preserves existing translations and marks removed strings as obsolete.
|
|
68
|
+
|
|
69
|
+
## make-mo — Compile PO to MO
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Compile all PO files in directory
|
|
73
|
+
wp i18n make-mo languages/
|
|
74
|
+
|
|
75
|
+
# Compile a specific file
|
|
76
|
+
wp i18n make-mo languages/my-text-domain-it_IT.po
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## make-json — Generate JS translation files
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Generate JSON files for all PO files
|
|
83
|
+
wp i18n make-json languages/
|
|
84
|
+
|
|
85
|
+
# Keep JS strings in PO files (don't purge)
|
|
86
|
+
wp i18n make-json languages/ --no-purge
|
|
87
|
+
|
|
88
|
+
# Pretty-print JSON output
|
|
89
|
+
wp i18n make-json languages/ --pretty-print
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Output: `{text-domain}-{locale}-{md5}.json` where `{md5}` is the hash of the relative JS file path.
|
|
93
|
+
|
|
94
|
+
### When to use `--no-purge`
|
|
95
|
+
|
|
96
|
+
- Use `--no-purge` if strings appear in both PHP and JS files
|
|
97
|
+
- Without it, JS-only strings are removed from PO, breaking PHP translations if shared
|
|
98
|
+
|
|
99
|
+
## make-php — Generate PHP translation files (WP 6.5+)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Convert PO files to PHP format
|
|
103
|
+
wp i18n make-php languages/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
PHP translation files load faster than MO files. WordPress 6.5+ supports this format natively.
|
|
107
|
+
|
|
108
|
+
## Complete workflow example
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# 1. Generate fresh POT
|
|
112
|
+
wp i18n make-pot . languages/my-text-domain.pot \
|
|
113
|
+
--exclude="node_modules/,vendor/"
|
|
114
|
+
|
|
115
|
+
# 2. Update existing translations
|
|
116
|
+
wp i18n update-po languages/my-text-domain.pot languages/
|
|
117
|
+
|
|
118
|
+
# 3. (Translate the PO files — manual or via Poedit)
|
|
119
|
+
|
|
120
|
+
# 4. Compile MO files
|
|
121
|
+
wp i18n make-mo languages/
|
|
122
|
+
|
|
123
|
+
# 5. Generate JSON for JavaScript
|
|
124
|
+
wp i18n make-json languages/ --no-purge
|
|
125
|
+
|
|
126
|
+
# 6. (Optional) Generate PHP translations
|
|
127
|
+
wp i18n make-php languages/
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Audit strings
|
|
131
|
+
|
|
132
|
+
The `make-pot` command includes a string auditor. Common warnings:
|
|
133
|
+
|
|
134
|
+
| Warning | Meaning |
|
|
135
|
+
|---------|---------|
|
|
136
|
+
| `Mismatched placeholders` | Printf placeholders differ between singular/plural |
|
|
137
|
+
| `Multiple text domains` | File mixes text domains |
|
|
138
|
+
| `Missing translator comment` | Placeholder string without `/* translators: */` |
|
|
139
|
+
|
|
140
|
+
Run the audit explicitly:
|
|
141
|
+
```bash
|
|
142
|
+
wp i18n make-pot . /dev/null --skip-js
|
|
143
|
+
# Warnings are printed to stderr
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Language management
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Install a language pack for core
|
|
150
|
+
wp language core install it_IT
|
|
151
|
+
|
|
152
|
+
# Set site language
|
|
153
|
+
wp site switch-language it_IT
|
|
154
|
+
|
|
155
|
+
# List installed languages
|
|
156
|
+
wp language core list --status=installed
|
|
157
|
+
|
|
158
|
+
# Install plugin language pack
|
|
159
|
+
wp language plugin install my-plugin it_IT
|
|
160
|
+
|
|
161
|
+
# Install theme language pack
|
|
162
|
+
wp language theme install my-theme it_IT
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Verification
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Verify POT is up to date (compare counts)
|
|
169
|
+
wp i18n make-pot . /tmp/fresh.pot --quiet
|
|
170
|
+
diff <(grep -c "^msgid" languages/my-text-domain.pot) <(grep -c "^msgid" /tmp/fresh.pot)
|
|
171
|
+
|
|
172
|
+
# Check MO files are current (MO should be newer than PO)
|
|
173
|
+
find languages/ -name "*.po" -newer languages/*.mo
|
|
174
|
+
|
|
175
|
+
# Verify JSON files exist for JS translations
|
|
176
|
+
ls languages/*.json
|
|
177
|
+
```
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n_inspect.mjs — Detect internationalization setup in a WordPress project.
|
|
3
|
+
*
|
|
4
|
+
* Scans for text domain, .pot/.po/.mo files, i18n function usage,
|
|
5
|
+
* and WP-CLI i18n availability.
|
|
6
|
+
* Outputs a JSON report to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node i18n_inspect.mjs [--cwd=/path/to/check]
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — i18n setup detected
|
|
13
|
+
* 1 — no i18n setup detected
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import process from "node:process";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const TOOL_VERSION = "1.0.0";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function statSafe(p) {
|
|
28
|
+
try {
|
|
29
|
+
return fs.statSync(p);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFileSafe(p) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(p, "utf8");
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJsonSafe(p) {
|
|
44
|
+
const raw = readFileSafe(p);
|
|
45
|
+
if (!raw) return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function execSafe(cmd, cwd, timeoutMs = 5000) {
|
|
54
|
+
try {
|
|
55
|
+
return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readdirSafe(dir) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.readdirSync(dir);
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Parse --cwd argument
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function parseCwd() {
|
|
74
|
+
const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
|
|
75
|
+
return cwdArg ? cwdArg.slice(6) : process.cwd();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Detect text domain
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function detectTextDomain(cwd) {
|
|
83
|
+
const result = { found: false, domain: null, source: null };
|
|
84
|
+
|
|
85
|
+
// Check plugin header
|
|
86
|
+
const phpFiles = readdirSafe(cwd).filter((f) => f.endsWith(".php"));
|
|
87
|
+
for (const file of phpFiles) {
|
|
88
|
+
const content = readFileSafe(path.join(cwd, file));
|
|
89
|
+
if (!content) continue;
|
|
90
|
+
|
|
91
|
+
const domainMatch = content.match(/Text\s*Domain:\s*(\S+)/i);
|
|
92
|
+
if (domainMatch) {
|
|
93
|
+
result.found = true;
|
|
94
|
+
result.domain = domainMatch[1];
|
|
95
|
+
result.source = file;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check style.css (theme)
|
|
101
|
+
const styleContent = readFileSafe(path.join(cwd, "style.css"));
|
|
102
|
+
if (styleContent) {
|
|
103
|
+
const domainMatch = styleContent.match(/Text\s*Domain:\s*(\S+)/i);
|
|
104
|
+
if (domainMatch) {
|
|
105
|
+
result.found = true;
|
|
106
|
+
result.domain = domainMatch[1];
|
|
107
|
+
result.source = "style.css";
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check block.json files
|
|
113
|
+
const blockJson = readJsonSafe(path.join(cwd, "block.json"));
|
|
114
|
+
if (blockJson?.textdomain) {
|
|
115
|
+
result.found = true;
|
|
116
|
+
result.domain = blockJson.textdomain;
|
|
117
|
+
result.source = "block.json";
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const srcBlockJson = readJsonSafe(path.join(cwd, "src", "block.json"));
|
|
122
|
+
if (srcBlockJson?.textdomain) {
|
|
123
|
+
result.found = true;
|
|
124
|
+
result.domain = srcBlockJson.textdomain;
|
|
125
|
+
result.source = "src/block.json";
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Detect translation files
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
function detectTranslationFiles(cwd) {
|
|
137
|
+
const result = { languagesDir: null, pot: [], po: [], mo: [], json: [] };
|
|
138
|
+
|
|
139
|
+
const langDirs = ["languages", "lang", "i18n"];
|
|
140
|
+
for (const dir of langDirs) {
|
|
141
|
+
const full = path.join(cwd, dir);
|
|
142
|
+
if (statSafe(full)?.isDirectory()) {
|
|
143
|
+
result.languagesDir = dir;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!result.languagesDir) return result;
|
|
149
|
+
|
|
150
|
+
const files = readdirSafe(path.join(cwd, result.languagesDir));
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
if (file.endsWith(".pot")) result.pot.push(file);
|
|
153
|
+
else if (file.endsWith(".po")) result.po.push(file);
|
|
154
|
+
else if (file.endsWith(".mo")) result.mo.push(file);
|
|
155
|
+
else if (file.endsWith(".json") && !file.startsWith(".")) result.json.push(file);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Detect i18n function usage
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
function detectI18nUsage(cwd) {
|
|
166
|
+
const result = { php: { detected: false, functions: {} }, js: { detected: false, functions: {} } };
|
|
167
|
+
|
|
168
|
+
// PHP i18n functions
|
|
169
|
+
const phpFunctions = ["__", "_e", "_x", "_ex", "_n", "_nx", "esc_html__", "esc_html_e", "esc_attr__", "esc_attr_e"];
|
|
170
|
+
const phpPattern = phpFunctions.map((f) => f.replace(/_/g, "_")).join("|");
|
|
171
|
+
const phpCount = execSafe(
|
|
172
|
+
`grep -rl --include="*.php" -E "(${phpPattern})\\s*\\(" . 2>/dev/null | wc -l`,
|
|
173
|
+
cwd
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (phpCount && parseInt(phpCount) > 0) {
|
|
177
|
+
result.php.detected = true;
|
|
178
|
+
|
|
179
|
+
// Count per function
|
|
180
|
+
for (const func of phpFunctions) {
|
|
181
|
+
const count = execSafe(
|
|
182
|
+
`grep -r --include="*.php" -c "${func}(" . 2>/dev/null | awk -F: '{s+=$2} END {print s}'`,
|
|
183
|
+
cwd
|
|
184
|
+
);
|
|
185
|
+
if (count && parseInt(count) > 0) {
|
|
186
|
+
result.php.functions[func] = parseInt(count);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// JS i18n functions (@wordpress/i18n)
|
|
192
|
+
const jsCount = execSafe(
|
|
193
|
+
`grep -rl --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" "@wordpress/i18n" . 2>/dev/null | wc -l`,
|
|
194
|
+
cwd
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (jsCount && parseInt(jsCount) > 0) {
|
|
198
|
+
result.js.detected = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Also check for import
|
|
202
|
+
const jsImportCount = execSafe(
|
|
203
|
+
`grep -rl --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" "from '@wordpress/i18n'" . 2>/dev/null | wc -l`,
|
|
204
|
+
cwd
|
|
205
|
+
);
|
|
206
|
+
if (jsImportCount && parseInt(jsImportCount) > 0) {
|
|
207
|
+
result.js.detected = true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Detect Domain Path header
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
function detectDomainPath(cwd) {
|
|
218
|
+
const phpFiles = readdirSafe(cwd).filter((f) => f.endsWith(".php"));
|
|
219
|
+
for (const file of phpFiles) {
|
|
220
|
+
const content = readFileSafe(path.join(cwd, file));
|
|
221
|
+
if (!content) continue;
|
|
222
|
+
const match = content.match(/Domain\s*Path:\s*(\S+)/i);
|
|
223
|
+
if (match) return match[1];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const styleContent = readFileSafe(path.join(cwd, "style.css"));
|
|
227
|
+
if (styleContent) {
|
|
228
|
+
const match = styleContent.match(/Domain\s*Path:\s*(\S+)/i);
|
|
229
|
+
if (match) return match[1];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Detect load_textdomain calls
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
function detectTextdomainLoading(cwd) {
|
|
240
|
+
const functions = [
|
|
241
|
+
"load_plugin_textdomain",
|
|
242
|
+
"load_theme_textdomain",
|
|
243
|
+
"load_child_theme_textdomain",
|
|
244
|
+
"wp_set_script_translations",
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const detected = [];
|
|
248
|
+
for (const func of functions) {
|
|
249
|
+
const found = execSafe(
|
|
250
|
+
`grep -rl --include="*.php" "${func}" . 2>/dev/null | head -1`,
|
|
251
|
+
cwd
|
|
252
|
+
);
|
|
253
|
+
if (found) detected.push(func);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return detected;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Check WP-CLI i18n availability
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
function checkWpCliI18n(cwd) {
|
|
264
|
+
const wpCli = execSafe("command -v wp", cwd);
|
|
265
|
+
if (!wpCli) return { available: false };
|
|
266
|
+
|
|
267
|
+
const i18nHelp = execSafe("wp i18n --help 2>/dev/null", cwd);
|
|
268
|
+
return { available: !!i18nHelp };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Main
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
function main() {
|
|
276
|
+
const cwd = parseCwd();
|
|
277
|
+
|
|
278
|
+
if (!statSafe(cwd)?.isDirectory()) {
|
|
279
|
+
console.error(`Error: directory not found: ${cwd}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const textDomain = detectTextDomain(cwd);
|
|
284
|
+
const translationFiles = detectTranslationFiles(cwd);
|
|
285
|
+
const i18nUsage = detectI18nUsage(cwd);
|
|
286
|
+
const domainPath = detectDomainPath(cwd);
|
|
287
|
+
const textdomainLoading = detectTextdomainLoading(cwd);
|
|
288
|
+
const wpCliI18n = checkWpCliI18n(cwd);
|
|
289
|
+
|
|
290
|
+
const detected = textDomain.found || i18nUsage.php.detected || i18nUsage.js.detected || translationFiles.pot.length > 0;
|
|
291
|
+
|
|
292
|
+
const report = {
|
|
293
|
+
tool: "i18n_inspect",
|
|
294
|
+
version: TOOL_VERSION,
|
|
295
|
+
cwd,
|
|
296
|
+
detected,
|
|
297
|
+
textDomain,
|
|
298
|
+
domainPath,
|
|
299
|
+
textdomainLoading,
|
|
300
|
+
translationFiles,
|
|
301
|
+
i18nUsage,
|
|
302
|
+
wpCliI18n,
|
|
303
|
+
recommendations: [],
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Recommendations
|
|
307
|
+
if (!textDomain.found && (i18nUsage.php.detected || i18nUsage.js.detected)) {
|
|
308
|
+
report.recommendations.push("i18n functions used but no Text Domain header found. Add 'Text Domain:' to plugin/theme header.");
|
|
309
|
+
}
|
|
310
|
+
if (textDomain.found && translationFiles.pot.length === 0) {
|
|
311
|
+
report.recommendations.push("Text domain set but no .pot file found. Run: wp i18n make-pot . languages/" + textDomain.domain + ".pot");
|
|
312
|
+
}
|
|
313
|
+
if (translationFiles.po.length > 0 && translationFiles.mo.length === 0) {
|
|
314
|
+
report.recommendations.push("PO files found but no compiled MO files. Run: wp i18n make-mo languages/");
|
|
315
|
+
}
|
|
316
|
+
if (i18nUsage.js.detected && translationFiles.json.length === 0) {
|
|
317
|
+
report.recommendations.push("JS i18n detected but no JSON translation files. Run: wp i18n make-json languages/ --no-purge");
|
|
318
|
+
}
|
|
319
|
+
if (textDomain.found && textdomainLoading.length === 0) {
|
|
320
|
+
report.recommendations.push("Text domain found but no load_plugin_textdomain/load_theme_textdomain call detected.");
|
|
321
|
+
}
|
|
322
|
+
if (!domainPath && textDomain.found) {
|
|
323
|
+
report.recommendations.push("Consider adding 'Domain Path: /languages' to the plugin/theme header.");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log(JSON.stringify(report, null, 2));
|
|
327
|
+
process.exit(detected ? 0 : 1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
main();
|
|
@@ -179,3 +179,7 @@ See `references/debugging.md`.
|
|
|
179
179
|
- `references/server-side-rendering.md`
|
|
180
180
|
- `references/directives-quickref.md`
|
|
181
181
|
- `references/debugging.md`
|
|
182
|
+
|
|
183
|
+
## Related skills
|
|
184
|
+
|
|
185
|
+
- `wp-accessibility` — ARIA for interactive components, keyboard navigation, focus management, screen reader testing
|
|
@@ -112,3 +112,9 @@ See:
|
|
|
112
112
|
## Escalation
|
|
113
113
|
|
|
114
114
|
For canonical detail, consult the Plugin Handbook and security guidelines before inventing patterns.
|
|
115
|
+
|
|
116
|
+
## Related skills
|
|
117
|
+
|
|
118
|
+
- `wp-e2e-testing` — PHPUnit for plugin unit tests, Playwright for E2E, CI integration
|
|
119
|
+
- `wp-i18n` — Text domain setup, translation workflow, WP-CLI i18n commands
|
|
120
|
+
- `wp-security` — Filesystem hardening, wp-config constants, authentication security
|
|
@@ -114,3 +114,7 @@ Read `references/discovery-and-params.md`.
|
|
|
114
114
|
## Escalation
|
|
115
115
|
|
|
116
116
|
If version support or behavior is unclear, consult the REST API Handbook and core docs before inventing patterns.
|
|
117
|
+
|
|
118
|
+
## Related skills
|
|
119
|
+
|
|
120
|
+
- `wp-headless` — Decoupled architecture, WPGraphQL, CORS configuration, frontend framework integration, webhooks
|