claude-plugin-wordpress-manager 2.2.1 → 2.3.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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +21 -0
- package/agents/wp-content-strategist.md +25 -0
- package/agents/wp-ecommerce-manager.md +23 -0
- package/agents/wp-site-manager.md +26 -0
- package/package.json +8 -3
- package/skills/wordpress-router/references/decision-tree.md +8 -2
- package/skills/wp-content/SKILL.md +1 -0
- package/skills/wp-content-attribution/SKILL.md +97 -0
- package/skills/wp-content-attribution/references/attribution-models.md +189 -0
- package/skills/wp-content-attribution/references/conversion-funnels.md +137 -0
- package/skills/wp-content-attribution/references/reporting-dashboards.md +199 -0
- package/skills/wp-content-attribution/references/roi-calculation.md +202 -0
- package/skills/wp-content-attribution/references/utm-tracking-setup.md +161 -0
- package/skills/wp-content-attribution/scripts/attribution_inspect.mjs +277 -0
- package/skills/wp-headless/SKILL.md +1 -0
- package/skills/wp-i18n/SKILL.md +1 -0
- package/skills/wp-multilang-network/SKILL.md +107 -0
- package/skills/wp-multilang-network/references/content-sync.md +182 -0
- package/skills/wp-multilang-network/references/hreflang-config.md +198 -0
- package/skills/wp-multilang-network/references/language-routing.md +234 -0
- package/skills/wp-multilang-network/references/network-architecture.md +119 -0
- package/skills/wp-multilang-network/references/seo-international.md +213 -0
- package/skills/wp-multilang-network/scripts/multilang_inspect.mjs +308 -0
- package/skills/wp-multisite/SKILL.md +1 -0
- package/skills/wp-programmatic-seo/SKILL.md +97 -0
- package/skills/wp-programmatic-seo/references/data-sources.md +200 -0
- package/skills/wp-programmatic-seo/references/location-seo.md +134 -0
- package/skills/wp-programmatic-seo/references/product-seo.md +147 -0
- package/skills/wp-programmatic-seo/references/technical-seo.md +197 -0
- package/skills/wp-programmatic-seo/references/template-architecture.md +125 -0
- package/skills/wp-programmatic-seo/scripts/programmatic_seo_inspect.mjs +264 -0
- package/skills/wp-woocommerce/SKILL.md +1 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* attribution_inspect.mjs — Detect content-commerce attribution readiness.
|
|
3
|
+
*
|
|
4
|
+
* Scans for WooCommerce presence, analytics plugins, UTM tracking setup,
|
|
5
|
+
* content/product volume, and existing order meta with source fields.
|
|
6
|
+
* Outputs a JSON report to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node attribution_inspect.mjs [--cwd=/path/to/check]
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — attribution indicators detected
|
|
13
|
+
* 1 — no attribution indicators 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 WooCommerce
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function detectWooCommerce(cwd) {
|
|
83
|
+
const pluginsDir = path.join(cwd, "wp-content", "plugins");
|
|
84
|
+
if (statSafe(path.join(pluginsDir, "woocommerce"))?.isDirectory()) return true;
|
|
85
|
+
|
|
86
|
+
const composer = readJsonSafe(path.join(cwd, "composer.json"));
|
|
87
|
+
if (composer) {
|
|
88
|
+
const allDeps = { ...composer.require, ...composer["require-dev"] };
|
|
89
|
+
if (allDeps["woocommerce/woocommerce"] || allDeps["wpackagist-plugin/woocommerce"]) return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Detect analytics plugins
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function detectAnalyticsPlugin(cwd) {
|
|
100
|
+
const result = { detected: false, plugin: null };
|
|
101
|
+
|
|
102
|
+
const pluginsDir = path.join(cwd, "wp-content", "plugins");
|
|
103
|
+
const analyticsPlugins = [
|
|
104
|
+
{ dir: "google-analytics-for-wordpress", name: "MonsterInsights" },
|
|
105
|
+
{ dir: "google-site-kit", name: "Google Site Kit" },
|
|
106
|
+
{ dir: "woocommerce-google-analytics-integration", name: "WooCommerce Google Analytics" },
|
|
107
|
+
{ dir: "ga-google-analytics", name: "GA Google Analytics" },
|
|
108
|
+
{ dir: "analytify", name: "Analytify" },
|
|
109
|
+
{ dir: "matomo", name: "Matomo Analytics" },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const plugin of analyticsPlugins) {
|
|
113
|
+
if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
|
|
114
|
+
result.detected = true;
|
|
115
|
+
result.plugin = plugin.name;
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Detect UTM tracking
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
function detectUtmTracking(cwd) {
|
|
128
|
+
const result = { detected: false, sources: [] };
|
|
129
|
+
|
|
130
|
+
// Check mu-plugins for UTM capture
|
|
131
|
+
const muPluginsDir = path.join(cwd, "wp-content", "mu-plugins");
|
|
132
|
+
const muFiles = readdirSafe(muPluginsDir);
|
|
133
|
+
for (const file of muFiles) {
|
|
134
|
+
if (!file.endsWith(".php")) continue;
|
|
135
|
+
const content = readFileSafe(path.join(muPluginsDir, file));
|
|
136
|
+
if (content && /utm_source|utm_campaign|utm_medium/i.test(content)) {
|
|
137
|
+
result.detected = true;
|
|
138
|
+
result.sources.push(`mu-plugin: ${file}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check plugins for UTM tracking
|
|
143
|
+
const pluginsDir = path.join(cwd, "wp-content", "plugins");
|
|
144
|
+
const utmPlugins = [
|
|
145
|
+
{ dir: "utm-dot-io", name: "UTM.io" },
|
|
146
|
+
{ dir: "campaign-url-builder", name: "Campaign URL Builder" },
|
|
147
|
+
{ dir: "leadin", name: "HubSpot (UTM tracking)" },
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
for (const plugin of utmPlugins) {
|
|
151
|
+
if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
|
|
152
|
+
result.detected = true;
|
|
153
|
+
result.sources.push(`plugin: ${plugin.name}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check theme functions.php for UTM capture
|
|
158
|
+
const themesDir = path.join(cwd, "wp-content", "themes");
|
|
159
|
+
const themes = readdirSafe(themesDir);
|
|
160
|
+
for (const theme of themes) {
|
|
161
|
+
const functionsPhp = readFileSafe(path.join(themesDir, theme, "functions.php"));
|
|
162
|
+
if (functionsPhp && /utm_source|utm_campaign/i.test(functionsPhp)) {
|
|
163
|
+
result.detected = true;
|
|
164
|
+
result.sources.push(`theme: ${theme}/functions.php`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Detect content volume
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
function detectContentVolume(cwd) {
|
|
176
|
+
const result = { content_count: 0, product_count: 0 };
|
|
177
|
+
|
|
178
|
+
const postCount = execSafe("wp post list --post_type=post --post_status=publish --format=count 2>/dev/null", cwd);
|
|
179
|
+
if (postCount) result.content_count = parseInt(postCount) || 0;
|
|
180
|
+
|
|
181
|
+
const productCount = execSafe("wp post list --post_type=product --post_status=publish --format=count 2>/dev/null", cwd);
|
|
182
|
+
if (productCount) result.product_count = parseInt(productCount) || 0;
|
|
183
|
+
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Detect existing order meta with source fields
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
function detectOrderMeta(cwd) {
|
|
192
|
+
// Check if any completed orders have UTM meta
|
|
193
|
+
const orderMeta = execSafe(
|
|
194
|
+
`wp db query "SELECT COUNT(*) as c FROM $(wp db prefix 2>/dev/null)postmeta WHERE meta_key LIKE '%utm_source%'" --format=csv 2>/dev/null`,
|
|
195
|
+
cwd
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (orderMeta && /\d+/.test(orderMeta)) {
|
|
199
|
+
const count = parseInt(orderMeta.match(/\d+/)[0]);
|
|
200
|
+
return count > 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Main
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
function main() {
|
|
211
|
+
const cwd = parseCwd();
|
|
212
|
+
|
|
213
|
+
if (!statSafe(cwd)?.isDirectory()) {
|
|
214
|
+
console.error(`Error: directory not found: ${cwd}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hasWoocommerce = detectWooCommerce(cwd);
|
|
219
|
+
const analytics = detectAnalyticsPlugin(cwd);
|
|
220
|
+
const utm = detectUtmTracking(cwd);
|
|
221
|
+
const volume = detectContentVolume(cwd);
|
|
222
|
+
const hasOrderMeta = detectOrderMeta(cwd);
|
|
223
|
+
|
|
224
|
+
const detected = hasWoocommerce && (analytics.detected || utm.detected || volume.content_count > 0);
|
|
225
|
+
|
|
226
|
+
const report = {
|
|
227
|
+
tool: "attribution_inspect",
|
|
228
|
+
version: TOOL_VERSION,
|
|
229
|
+
cwd,
|
|
230
|
+
detected,
|
|
231
|
+
has_woocommerce: hasWoocommerce,
|
|
232
|
+
analytics_plugin: analytics.plugin,
|
|
233
|
+
has_utm_tracking: utm.detected,
|
|
234
|
+
utm_sources: utm.sources,
|
|
235
|
+
content_count: volume.content_count,
|
|
236
|
+
product_count: volume.product_count,
|
|
237
|
+
has_order_attribution_meta: hasOrderMeta,
|
|
238
|
+
attribution_readiness: "unknown",
|
|
239
|
+
recommendations: [],
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Assess readiness
|
|
243
|
+
if (hasWoocommerce && utm.detected && analytics.detected && volume.content_count > 10) {
|
|
244
|
+
report.attribution_readiness = "high";
|
|
245
|
+
} else if (hasWoocommerce && (utm.detected || analytics.detected) && volume.content_count > 0) {
|
|
246
|
+
report.attribution_readiness = "medium";
|
|
247
|
+
} else if (hasWoocommerce && volume.content_count > 0) {
|
|
248
|
+
report.attribution_readiness = "low";
|
|
249
|
+
} else {
|
|
250
|
+
report.attribution_readiness = "not_ready";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Recommendations
|
|
254
|
+
if (!hasWoocommerce) {
|
|
255
|
+
report.recommendations.push("WooCommerce not detected. Content-commerce attribution requires WooCommerce for sales data.");
|
|
256
|
+
}
|
|
257
|
+
if (hasWoocommerce && !utm.detected) {
|
|
258
|
+
report.recommendations.push("No UTM tracking detected. Install the UTM capture mu-plugin to link content visits to orders.");
|
|
259
|
+
}
|
|
260
|
+
if (hasWoocommerce && !analytics.detected) {
|
|
261
|
+
report.recommendations.push("No analytics plugin detected. Install MonsterInsights or Google Site Kit for traffic data.");
|
|
262
|
+
}
|
|
263
|
+
if (volume.content_count === 0) {
|
|
264
|
+
report.recommendations.push("No published content found. Create blog posts/content to drive traffic to products.");
|
|
265
|
+
}
|
|
266
|
+
if (volume.content_count > 0 && volume.product_count === 0) {
|
|
267
|
+
report.recommendations.push("Content exists but no products found. Verify WooCommerce products are published.");
|
|
268
|
+
}
|
|
269
|
+
if (hasWoocommerce && utm.detected && !hasOrderMeta) {
|
|
270
|
+
report.recommendations.push("UTM tracking is set up but no order attribution meta found yet. Place a test order to verify.");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log(JSON.stringify(report, null, 2));
|
|
274
|
+
process.exit(detected ? 0 : 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
main();
|
|
@@ -167,3 +167,4 @@ Read: `references/webhooks.md`
|
|
|
167
167
|
- For REST endpoint development, use the `wp-rest-api` skill
|
|
168
168
|
- For authentication security, use the `wp-security` skill
|
|
169
169
|
- For webhook configuration and management, use the `wp-webhooks` skill
|
|
170
|
+
- For scalable programmatic page generation with ISR/SSG, use the `wp-programmatic-seo` skill
|
package/skills/wp-i18n/SKILL.md
CHANGED
|
@@ -168,3 +168,4 @@ Re-run: `node skills/wp-i18n/scripts/i18n_inspect.mjs --cwd=/path`
|
|
|
168
168
|
- WordPress i18n Handbook: https://developer.wordpress.org/plugins/internationalization/
|
|
169
169
|
- CLDR Plural Rules: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
|
|
170
170
|
- Translator Handbook: https://make.wordpress.org/polyglots/handbook/
|
|
171
|
+
- For multisite multi-language network orchestration (hreflang, content sync, international SEO), use the `wp-multilang-network` skill
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wp-multilang-network
|
|
3
|
+
description: |
|
|
4
|
+
This skill should be used when the user asks to "set up multilingual site",
|
|
5
|
+
"multisite per language", "hreflang tags", "international SEO", "translate site
|
|
6
|
+
network", "language-specific sub-sites", "multi-language WordPress", "localize
|
|
7
|
+
content across sites", "content translation sync", "geo-targeting",
|
|
8
|
+
or mentions orchestrating a WordPress Multisite network where each sub-site
|
|
9
|
+
serves a different language or locale.
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Multi-Language Network uses WordPress Multisite to create a coordinated network where each sub-site serves a different language or locale. Content is synchronized across sites via translation plugins or manual workflows, with hreflang tags ensuring search engines serve the correct language variant to users.
|
|
16
|
+
|
|
17
|
+
This skill orchestrates existing multisite MCP tools (10 tools), the `wp-i18n` skill for translation best practices, and multilingual plugin workflows (WPML, Polylang, MultilingualPress). No new tools are required.
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- User needs international presence with 2+ languages
|
|
22
|
+
- "Set up Italian and Spanish versions of our site"
|
|
23
|
+
- "Add hreflang tags across our multisite network"
|
|
24
|
+
- "Translate content and keep translations in sync"
|
|
25
|
+
- "Set up language-specific sub-sites"
|
|
26
|
+
- "Configure international SEO for multiple languages"
|
|
27
|
+
- "Redirect users based on browser language"
|
|
28
|
+
|
|
29
|
+
## Multi-Language Network vs Single-Site Plugin
|
|
30
|
+
|
|
31
|
+
| Aspect | Multisite Network | Single-Site Plugin |
|
|
32
|
+
|--------|------------------|-------------------|
|
|
33
|
+
| Scalability | Excellent — independent sites per language | Good for 2–5 languages, complex beyond |
|
|
34
|
+
| SEO control | Full — separate sitemaps, robots.txt, GSC per site | Shared — one sitemap with hreflang |
|
|
35
|
+
| Content independence | Each site can have unique content | All content on one database |
|
|
36
|
+
| Maintenance overhead | Higher — plugins/themes per site (unless network-activated) | Lower — one site to maintain |
|
|
37
|
+
| Performance | Better — smaller DB per language | Can degrade with many languages |
|
|
38
|
+
| Domain flexibility | Subdomains, subdirectories, or separate domains | Subdirectories or query params |
|
|
39
|
+
| Plugin compatibility | Standard WP plugins work per-site | Must be compatible with multilingual plugin |
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- WordPress Multisite enabled (reference: `wp-multisite` skill)
|
|
44
|
+
- WP-CLI access for network operations
|
|
45
|
+
- Multilingual plugin installed (WPML, Polylang, or MultilingualPress)
|
|
46
|
+
- DNS configured for subdomains or subdirectories
|
|
47
|
+
|
|
48
|
+
## Prerequisites / Detection
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node skills/wp-multilang-network/scripts/multilang_inspect.mjs --cwd=/path/to/wordpress
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The script checks Multisite status, multilingual plugins, sub-site count, language patterns in slugs, hreflang presence, and WPLANG configuration.
|
|
55
|
+
|
|
56
|
+
## Multi-Language Network Operations Decision Tree
|
|
57
|
+
|
|
58
|
+
1. **What multi-language task?**
|
|
59
|
+
|
|
60
|
+
- "set up multisite per language" / "create language sites" / "network architecture"
|
|
61
|
+
→ **Network Architecture** — Read: `references/network-architecture.md`
|
|
62
|
+
|
|
63
|
+
- "hreflang" / "language alternates" / "x-default" / "hreflang tags"
|
|
64
|
+
→ **Hreflang Configuration** — Read: `references/hreflang-config.md`
|
|
65
|
+
|
|
66
|
+
- "translate content" / "sync content" / "keep translations in sync" / "translation workflow"
|
|
67
|
+
→ **Content Synchronization** — Read: `references/content-sync.md`
|
|
68
|
+
|
|
69
|
+
- "language switcher" / "language detection" / "redirect by language" / "browser language"
|
|
70
|
+
→ **Language Routing** — Read: `references/language-routing.md`
|
|
71
|
+
|
|
72
|
+
- "international SEO" / "geo-targeting" / "language sitemaps" / "search console per language"
|
|
73
|
+
→ **International SEO** — Read: `references/seo-international.md`
|
|
74
|
+
|
|
75
|
+
2. **Common workflow (new multi-language network):**
|
|
76
|
+
1. Assess current network: `list_sites` + run `multilang_inspect.mjs`
|
|
77
|
+
2. Create language sub-sites: `ms_create_site` per language (slug = ISO 639-1 code)
|
|
78
|
+
3. Install and configure multilingual plugin network-wide
|
|
79
|
+
4. Set up hreflang tags (mu-plugin or plugin)
|
|
80
|
+
5. Establish content sync workflow (manual or plugin-assisted)
|
|
81
|
+
6. Configure language routing and switcher widget
|
|
82
|
+
7. Set up per-language XML sitemaps
|
|
83
|
+
8. Verify with international SEO checklist
|
|
84
|
+
|
|
85
|
+
## Recommended Agent
|
|
86
|
+
|
|
87
|
+
`wp-site-manager` — handles multisite network operations, sub-site management, and cross-site coordination.
|
|
88
|
+
|
|
89
|
+
## Additional Resources
|
|
90
|
+
|
|
91
|
+
### Reference Files
|
|
92
|
+
|
|
93
|
+
| File | Description |
|
|
94
|
+
|------|-------------|
|
|
95
|
+
| **`references/network-architecture.md`** | Subdomain vs subdirectory vs domains, plugin comparison, naming conventions |
|
|
96
|
+
| **`references/hreflang-config.md`** | Hreflang format, mu-plugin auto-generation, validation, common mistakes |
|
|
97
|
+
| **`references/content-sync.md`** | WPML/Polylang/MultilingualPress workflows, translation status tracking |
|
|
98
|
+
| **`references/language-routing.md`** | Browser detection, geo-IP redirect, language switcher, cookie preference |
|
|
99
|
+
| **`references/seo-international.md`** | GSC per language, language sitemaps, schema localization, CDN per region |
|
|
100
|
+
|
|
101
|
+
### Related Skills
|
|
102
|
+
|
|
103
|
+
- `wp-multisite` — multisite network management (10 MCP tools)
|
|
104
|
+
- `wp-i18n` — internationalization and localization best practices
|
|
105
|
+
- `wp-headless` — headless frontend with i18n routing (Next.js i18n, Nuxt i18n)
|
|
106
|
+
- `wp-programmatic-seo` — scalable page generation (complement for multi-language SEO)
|
|
107
|
+
- `wp-content` — content management for each language sub-site
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Content Synchronization
|
|
2
|
+
|
|
3
|
+
Use this file when establishing content translation and synchronization workflows across a WordPress Multisite network — plugin-specific workflows, manual sync procedures, and translation status tracking.
|
|
4
|
+
|
|
5
|
+
## Synchronization Strategies
|
|
6
|
+
|
|
7
|
+
| Strategy | Description | Effort | Best For |
|
|
8
|
+
|----------|-------------|--------|----------|
|
|
9
|
+
| **Manual** | Create content independently per site | High ongoing | Fully independent content |
|
|
10
|
+
| **Semi-automatic** | Create on primary, replicate structure, translate manually | Medium | Shared structure, unique translations |
|
|
11
|
+
| **Fully automatic** | Plugin syncs content, human reviews translation | Low ongoing | High-volume, similar content |
|
|
12
|
+
|
|
13
|
+
## WPML Network Mode
|
|
14
|
+
|
|
15
|
+
WPML can operate across multisite with the "WPML for Multisite" add-on:
|
|
16
|
+
|
|
17
|
+
### Setup
|
|
18
|
+
|
|
19
|
+
1. Network-activate WPML on all sites
|
|
20
|
+
2. Configure primary language per site (each site = one language)
|
|
21
|
+
3. Enable translation management across network
|
|
22
|
+
|
|
23
|
+
### Workflow
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
1. Create content on primary site (e.g., English)
|
|
27
|
+
2. WPML sends content to translation queue
|
|
28
|
+
3. Translator works via WPML Translation Management or XLIFF export
|
|
29
|
+
4. Translated content auto-publishes on target language site
|
|
30
|
+
5. WPML maintains content connections (post ID mapping)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Key WPML Functions
|
|
34
|
+
|
|
35
|
+
```php
|
|
36
|
+
// Get translation of a post on another site
|
|
37
|
+
$translated_id = apply_filters('wpml_object_id', $post_id, 'post', false, 'it');
|
|
38
|
+
|
|
39
|
+
// Get all translations of current post
|
|
40
|
+
$translations = apply_filters('wpml_active_languages', null);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Polylang for Multisite
|
|
44
|
+
|
|
45
|
+
Polylang's multisite extension assigns one language per sub-site:
|
|
46
|
+
|
|
47
|
+
### Setup
|
|
48
|
+
|
|
49
|
+
1. Install Polylang Pro + Polylang for Multisite
|
|
50
|
+
2. Assign language to each sub-site in Network Admin → Sites → Polylang
|
|
51
|
+
3. Configure which content types are translatable
|
|
52
|
+
|
|
53
|
+
### Workflow
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
1. Create post on primary site
|
|
57
|
+
2. Polylang shows "Translate" button for each target language
|
|
58
|
+
3. Click creates a linked draft on the target language site
|
|
59
|
+
4. Translator fills in translation
|
|
60
|
+
5. Publish triggers hreflang auto-generation
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Polylang API
|
|
64
|
+
|
|
65
|
+
```php
|
|
66
|
+
// Get translation link
|
|
67
|
+
$translation_id = pll_get_post($post_id, 'it');
|
|
68
|
+
|
|
69
|
+
// Get language of current post
|
|
70
|
+
$language = pll_get_post_language($post_id);
|
|
71
|
+
|
|
72
|
+
// Set translation relationship
|
|
73
|
+
pll_set_post_language($post_id, 'it');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## MultilingualPress
|
|
77
|
+
|
|
78
|
+
MultilingualPress is built specifically for WordPress Multisite:
|
|
79
|
+
|
|
80
|
+
### Setup
|
|
81
|
+
|
|
82
|
+
1. Install and network-activate MultilingualPress
|
|
83
|
+
2. Go to Network Admin → MultilingualPress → set language per site
|
|
84
|
+
3. Define content relationships between sites
|
|
85
|
+
|
|
86
|
+
### Workflow
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
1. Create content on any site in the network
|
|
90
|
+
2. In the editor, MultilingualPress shows "Translation" metabox
|
|
91
|
+
3. Select target sites and either:
|
|
92
|
+
a. Copy content (for later manual translation)
|
|
93
|
+
b. Link existing content on target site
|
|
94
|
+
4. MultilingualPress maintains bidirectional content connections
|
|
95
|
+
5. Hreflang tags generated automatically from connections
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Advantages for Multisite
|
|
99
|
+
|
|
100
|
+
- Native multisite architecture (no complex DB tables like WPML)
|
|
101
|
+
- Each site has its own standard WP database tables
|
|
102
|
+
- Content connections stored as post meta (lightweight)
|
|
103
|
+
- Works with standard WordPress REST API per-site
|
|
104
|
+
|
|
105
|
+
## Manual Sync Workflow
|
|
106
|
+
|
|
107
|
+
When no multilingual plugin is used, sync content manually via MCP tools:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 1. Create content on primary site
|
|
111
|
+
switch_site(site_id=1) # Switch to English site
|
|
112
|
+
create_content(type="post", title="Cactus Water Benefits", slug="cactus-water-benefits",
|
|
113
|
+
content="...", status="publish")
|
|
114
|
+
|
|
115
|
+
# 2. Replicate structure on Italian site
|
|
116
|
+
switch_site(site_id=2) # Switch to Italian site
|
|
117
|
+
create_content(type="post", title="Benefici dell'Acqua di Cactus", slug="cactus-water-benefits",
|
|
118
|
+
content="[Italian translation]", status="draft")
|
|
119
|
+
|
|
120
|
+
# 3. Replicate on German site
|
|
121
|
+
switch_site(site_id=3) # Switch to German site
|
|
122
|
+
create_content(type="post", title="Vorteile von Kaktuswasser", slug="cactus-water-benefits",
|
|
123
|
+
content="[German translation]", status="draft")
|
|
124
|
+
|
|
125
|
+
# 4. Switch back to primary
|
|
126
|
+
switch_site(site_id=1)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Important:** Keep the **slug identical** across all language sites for hreflang matching (the mu-plugin matches by slug).
|
|
130
|
+
|
|
131
|
+
## Translation Status Tracking
|
|
132
|
+
|
|
133
|
+
Track the translation state of each content piece:
|
|
134
|
+
|
|
135
|
+
| Status | Meaning | Visual Indicator |
|
|
136
|
+
|--------|---------|-----------------|
|
|
137
|
+
| `untranslated` | No translation exists on target site | Red dot |
|
|
138
|
+
| `draft` | Translation created but not reviewed | Yellow dot |
|
|
139
|
+
| `pending_review` | Translation complete, awaiting review | Orange dot |
|
|
140
|
+
| `published` | Translation live and approved | Green dot |
|
|
141
|
+
| `outdated` | Source content updated after translation | Blue dot with warning |
|
|
142
|
+
|
|
143
|
+
### Custom Meta for Tracking
|
|
144
|
+
|
|
145
|
+
```php
|
|
146
|
+
// On the primary site post, track translation status
|
|
147
|
+
update_post_meta($post_id, '_translation_status_it', 'published');
|
|
148
|
+
update_post_meta($post_id, '_translation_status_de', 'draft');
|
|
149
|
+
update_post_meta($post_id, '_translation_status_fr', 'untranslated');
|
|
150
|
+
|
|
151
|
+
// When source is updated, mark translations as outdated
|
|
152
|
+
add_action('post_updated', function ($post_id) {
|
|
153
|
+
$languages = ['it', 'de', 'fr', 'es'];
|
|
154
|
+
foreach ($languages as $lang) {
|
|
155
|
+
$current = get_post_meta($post_id, "_translation_status_{$lang}", true);
|
|
156
|
+
if ($current === 'published') {
|
|
157
|
+
update_post_meta($post_id, "_translation_status_{$lang}", 'outdated');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Media Library Sharing
|
|
164
|
+
|
|
165
|
+
By default, each multisite sub-site has its own media library. Options for sharing:
|
|
166
|
+
|
|
167
|
+
| Approach | Plugin | Description |
|
|
168
|
+
|----------|--------|-------------|
|
|
169
|
+
| **Network Media Library** | Network Media Library plugin | Shared library accessible from all sites |
|
|
170
|
+
| **Global Media** | Network Shared Media plugin | Central media site, others embed |
|
|
171
|
+
| **Manual upload** | None | Upload same images per site (wasteful but independent) |
|
|
172
|
+
|
|
173
|
+
**Recommendation:** Use Network Media Library for brand assets (logo, product images) that are identical across languages. Allow per-site uploads for language-specific images (localized banners, team photos).
|
|
174
|
+
|
|
175
|
+
## Decision Checklist
|
|
176
|
+
|
|
177
|
+
1. Which sync strategy: manual, semi-automatic, or fully automatic? → Match to team size and budget
|
|
178
|
+
2. Is a multilingual plugin chosen and installed? → MultilingualPress for multisite-native; WPML for feature-rich
|
|
179
|
+
3. Are content connections established between sites? → Verify via plugin admin or custom meta
|
|
180
|
+
4. Do translated pages use matching slugs for hreflang? → Audit sample pages
|
|
181
|
+
5. Is translation status tracked? → Custom meta or plugin dashboard
|
|
182
|
+
6. Is the media library shared or per-site? → Shared for brand assets, per-site for localized content
|