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.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +21 -0
  3. package/agents/wp-content-strategist.md +25 -0
  4. package/agents/wp-ecommerce-manager.md +23 -0
  5. package/agents/wp-site-manager.md +26 -0
  6. package/package.json +8 -3
  7. package/skills/wordpress-router/references/decision-tree.md +8 -2
  8. package/skills/wp-content/SKILL.md +1 -0
  9. package/skills/wp-content-attribution/SKILL.md +97 -0
  10. package/skills/wp-content-attribution/references/attribution-models.md +189 -0
  11. package/skills/wp-content-attribution/references/conversion-funnels.md +137 -0
  12. package/skills/wp-content-attribution/references/reporting-dashboards.md +199 -0
  13. package/skills/wp-content-attribution/references/roi-calculation.md +202 -0
  14. package/skills/wp-content-attribution/references/utm-tracking-setup.md +161 -0
  15. package/skills/wp-content-attribution/scripts/attribution_inspect.mjs +277 -0
  16. package/skills/wp-headless/SKILL.md +1 -0
  17. package/skills/wp-i18n/SKILL.md +1 -0
  18. package/skills/wp-multilang-network/SKILL.md +107 -0
  19. package/skills/wp-multilang-network/references/content-sync.md +182 -0
  20. package/skills/wp-multilang-network/references/hreflang-config.md +198 -0
  21. package/skills/wp-multilang-network/references/language-routing.md +234 -0
  22. package/skills/wp-multilang-network/references/network-architecture.md +119 -0
  23. package/skills/wp-multilang-network/references/seo-international.md +213 -0
  24. package/skills/wp-multilang-network/scripts/multilang_inspect.mjs +308 -0
  25. package/skills/wp-multisite/SKILL.md +1 -0
  26. package/skills/wp-programmatic-seo/SKILL.md +97 -0
  27. package/skills/wp-programmatic-seo/references/data-sources.md +200 -0
  28. package/skills/wp-programmatic-seo/references/location-seo.md +134 -0
  29. package/skills/wp-programmatic-seo/references/product-seo.md +147 -0
  30. package/skills/wp-programmatic-seo/references/technical-seo.md +197 -0
  31. package/skills/wp-programmatic-seo/references/template-architecture.md +125 -0
  32. package/skills/wp-programmatic-seo/scripts/programmatic_seo_inspect.mjs +264 -0
  33. 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
@@ -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