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,198 @@
1
+ # Hreflang Configuration
2
+
3
+ Use this file when implementing hreflang tags across a WordPress Multisite network — tag format, mu-plugin auto-generation, validation tools, and common mistakes.
4
+
5
+ ## Hreflang Tag Format
6
+
7
+ Hreflang tells search engines which language/region a page targets and where alternate versions exist:
8
+
9
+ ```html
10
+ <!-- On the English page -->
11
+ <link rel="alternate" hreflang="en" href="https://example.com/about/" />
12
+ <link rel="alternate" hreflang="it" href="https://example.com/it/chi-siamo/" />
13
+ <link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns/" />
14
+ <link rel="alternate" hreflang="x-default" href="https://example.com/about/" />
15
+ ```
16
+
17
+ ### Language Code Reference
18
+
19
+ | Code | Language | Notes |
20
+ |------|----------|-------|
21
+ | `en` | English | Use `en-us`, `en-gb` for regional |
22
+ | `it` | Italian | |
23
+ | `de` | German | Use `de-at` for Austrian German |
24
+ | `fr` | French | Use `fr-ca` for Canadian French |
25
+ | `es` | Spanish | Use `es-mx` for Mexican Spanish |
26
+ | `pt` | Portuguese | Use `pt-br` for Brazilian Portuguese |
27
+ | `zh-hans` | Chinese (Simplified) | |
28
+ | `zh-hant` | Chinese (Traditional) | |
29
+ | `x-default` | Fallback/default | One per page set |
30
+
31
+ ### Rules
32
+
33
+ 1. **Self-referencing required:** Every page must include an hreflang tag pointing to itself
34
+ 2. **Reciprocal required:** If page A links to page B with hreflang, page B must link back to page A
35
+ 3. **x-default:** One page per set should be designated as the default (usually English or main language)
36
+ 4. **Absolute URLs:** Always use full absolute URLs in href attributes
37
+ 5. **One tag per language-region:** Don't duplicate language codes
38
+
39
+ ## Implementation Methods
40
+
41
+ ### Method 1: HTML `<head>` Tags (Recommended)
42
+
43
+ ```html
44
+ <head>
45
+ <link rel="alternate" hreflang="en" href="https://example.com/products/" />
46
+ <link rel="alternate" hreflang="it" href="https://example.com/it/prodotti/" />
47
+ <link rel="alternate" hreflang="x-default" href="https://example.com/products/" />
48
+ </head>
49
+ ```
50
+
51
+ ### Method 2: HTTP Headers (for non-HTML content)
52
+
53
+ ```
54
+ Link: <https://example.com/products/>; rel="alternate"; hreflang="en",
55
+ <https://example.com/it/prodotti/>; rel="alternate"; hreflang="it"
56
+ ```
57
+
58
+ ### Method 3: XML Sitemap
59
+
60
+ ```xml
61
+ <url>
62
+ <loc>https://example.com/products/</loc>
63
+ <xhtml:link rel="alternate" hreflang="en" href="https://example.com/products/" />
64
+ <xhtml:link rel="alternate" hreflang="it" href="https://example.com/it/prodotti/" />
65
+ </url>
66
+ ```
67
+
68
+ ## mu-plugin for Automatic Hreflang Generation
69
+
70
+ This mu-plugin automatically generates hreflang tags across a multisite network by matching content via slug:
71
+
72
+ ```php
73
+ <?php
74
+ /**
75
+ * Plugin Name: Multisite Hreflang Generator
76
+ * Description: Automatically generates hreflang tags across multisite network.
77
+ * Version: 1.0.0
78
+ */
79
+
80
+ add_action('wp_head', function () {
81
+ if (!is_multisite()) return;
82
+
83
+ $current_blog_id = get_current_blog_id();
84
+ $current_path = trailingslashit(parse_url(get_permalink(), PHP_URL_PATH));
85
+
86
+ // Map blog IDs to language codes
87
+ $language_map = [
88
+ 1 => 'en', // Main site = English
89
+ 2 => 'it', // /it/ sub-site
90
+ 3 => 'de', // /de/ sub-site
91
+ 4 => 'fr', // /fr/ sub-site
92
+ // Add more as needed
93
+ ];
94
+
95
+ // Get current post slug for matching
96
+ $current_slug = '';
97
+ if (is_singular()) {
98
+ $current_slug = get_post_field('post_name', get_queried_object_id());
99
+ }
100
+
101
+ if (empty($current_slug)) return;
102
+
103
+ $sites = get_sites(['number' => 100]);
104
+ $alternates = [];
105
+
106
+ foreach ($sites as $site) {
107
+ $lang = $language_map[$site->blog_id] ?? null;
108
+ if (!$lang) continue;
109
+
110
+ switch_to_blog($site->blog_id);
111
+
112
+ // Find matching content by slug
113
+ $matched_post = get_page_by_path($current_slug, OBJECT, ['post', 'page', 'product']);
114
+ if ($matched_post && $matched_post->post_status === 'publish') {
115
+ $alternates[] = [
116
+ 'lang' => $lang,
117
+ 'href' => get_permalink($matched_post->ID),
118
+ ];
119
+ }
120
+
121
+ restore_current_blog();
122
+ }
123
+
124
+ // Only output if we found at least 2 alternates (including self)
125
+ if (count($alternates) < 2) return;
126
+
127
+ // Output hreflang tags
128
+ foreach ($alternates as $alt) {
129
+ printf(
130
+ '<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
131
+ esc_attr($alt['lang']),
132
+ esc_url($alt['href'])
133
+ );
134
+ }
135
+
136
+ // x-default = main site (blog_id 1)
137
+ $default = array_filter($alternates, fn($a) => $a['lang'] === ($language_map[1] ?? 'en'));
138
+ if (!empty($default)) {
139
+ printf(
140
+ '<link rel="alternate" hreflang="x-default" href="%s" />' . "\n",
141
+ esc_url(reset($default)['href'])
142
+ );
143
+ }
144
+ });
145
+ ```
146
+
147
+ **Installation:**
148
+ 1. Save as `wp-content/mu-plugins/multisite-hreflang.php`
149
+ 2. Update `$language_map` with your blog IDs and language codes
150
+ 3. Content matching is by slug — ensure translated pages share the same slug or use a custom meta field for cross-referencing
151
+
152
+ ## Hreflang Validation
153
+
154
+ ### Google Search Console
155
+
156
+ 1. Go to International Targeting report
157
+ 2. Check for hreflang errors: missing return tags, unknown language codes, conflicting tags
158
+ 3. Verify per-language properties are set up (if using subdomains/domains)
159
+
160
+ ### Online Validators
161
+
162
+ | Tool | URL | Checks |
163
+ |------|-----|--------|
164
+ | Hreflang Tags Checker | `hreflang.org` | Tag syntax, return links, x-default |
165
+ | Ahrefs Hreflang Audit | Ahrefs Site Audit | Reciprocal links, missing alternates |
166
+ | Screaming Frog | Desktop crawler | Bulk hreflang validation |
167
+
168
+ ### Manual Verification
169
+
170
+ ```bash
171
+ # Check hreflang tags on a page
172
+ curl -s https://example.com/products/ | grep -i "hreflang"
173
+
174
+ # Verify reciprocal: check that the Italian page links back
175
+ curl -s https://example.com/it/prodotti/ | grep -i "hreflang"
176
+ ```
177
+
178
+ ## Common Mistakes
179
+
180
+ | Mistake | Impact | Fix |
181
+ |---------|--------|-----|
182
+ | Missing self-referencing tag | Search engines ignore the set | Always include hreflang for current page |
183
+ | Missing reciprocal (A→B but not B→A) | Both tags get ignored | Ensure all pages in set link to each other |
184
+ | Wrong language codes | Tags treated as invalid | Use ISO 639-1 codes, verify spelling |
185
+ | Relative URLs in href | Tags get ignored | Always use absolute URLs with protocol |
186
+ | Mixing hreflang methods (head + sitemap) | Potential conflicts | Choose one method and use it consistently |
187
+ | Pointing hreflang to redirecting URLs | Tag gets ignored | Point to final destination URL |
188
+ | Duplicate language codes on same page | Ambiguous signal | One tag per language-region combination |
189
+
190
+ ## Decision Checklist
191
+
192
+ 1. Are all language sites created in the multisite network? → Verify with `list_sites`
193
+ 2. Is the `$language_map` in the mu-plugin correct? → Map each blog_id to its language code
194
+ 3. Do translated pages share slugs for matching? → If not, use custom meta field for cross-reference
195
+ 4. Are hreflang tags present on all public pages? → Spot-check with `curl | grep hreflang`
196
+ 5. Are reciprocal tags correct? → Check both directions for 3+ sample pages
197
+ 6. Is `x-default` set to the main language site? → Verify in page source
198
+ 7. Is Google Search Console showing no hreflang errors? → Check International Targeting report
@@ -0,0 +1,234 @@
1
+ # Language Routing
2
+
3
+ Use this file when configuring how users are directed to the correct language version — browser language detection, geo-IP redirect, language switcher widgets, and cookie-based preferences.
4
+
5
+ ## Browser Language Detection
6
+
7
+ The `Accept-Language` HTTP header indicates the user's preferred languages:
8
+
9
+ ```
10
+ Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7
11
+ ```
12
+
13
+ ### PHP Detection (mu-plugin)
14
+
15
+ ```php
16
+ <?php
17
+ /**
18
+ * Language redirect based on Accept-Language header.
19
+ * Only redirects on first visit (no cookie set yet).
20
+ */
21
+ add_action('template_redirect', function () {
22
+ if (!is_multisite() || is_admin()) return;
23
+
24
+ // Skip if user has language preference cookie
25
+ if (isset($_COOKIE['wp_lang_pref'])) return;
26
+
27
+ // Skip if already on a language sub-site
28
+ $current_blog = get_current_blog_id();
29
+ if ($current_blog !== 1) return; // Only redirect from main site
30
+
31
+ $accepted = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
32
+ $preferred_lang = substr($accepted, 0, 2); // Get primary language code
33
+
34
+ $language_sites = [
35
+ 'it' => '/it/',
36
+ 'de' => '/de/',
37
+ 'fr' => '/fr/',
38
+ 'es' => '/es/',
39
+ ];
40
+
41
+ if (isset($language_sites[$preferred_lang])) {
42
+ $target = home_url($language_sites[$preferred_lang]);
43
+ // Set cookie so we don't redirect again
44
+ setcookie('wp_lang_pref', $preferred_lang, time() + (365 * DAY_IN_SECONDS), '/');
45
+ wp_redirect($target, 302); // 302 = temporary (important for SEO)
46
+ exit;
47
+ }
48
+ });
49
+ ```
50
+
51
+ **Important:** Always use 302 (temporary) redirect for language detection, never 301 (permanent). A 301 would cache the redirect for all users, regardless of their language.
52
+
53
+ ## Geo-IP Based Language Redirect
54
+
55
+ Use server-side geo-IP to redirect by country:
56
+
57
+ ### Nginx Configuration
58
+
59
+ ```nginx
60
+ # Requires ngx_http_geoip2_module
61
+ geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
62
+ $geoip2_country_code default=US country iso_code;
63
+ }
64
+
65
+ server {
66
+ # Redirect Italian visitors to /it/
67
+ if ($geoip2_country_code = "IT") {
68
+ set $lang_redirect "/it/";
69
+ }
70
+ if ($geoip2_country_code = "DE") {
71
+ set $lang_redirect "/de/";
72
+ }
73
+
74
+ # Only redirect if no cookie and on root
75
+ location = / {
76
+ if ($cookie_wp_lang_pref = "") {
77
+ return 302 $scheme://$host$lang_redirect;
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### CloudFlare Workers
84
+
85
+ ```javascript
86
+ export default {
87
+ async fetch(request) {
88
+ const url = new URL(request.url);
89
+ const country = request.cf?.country || 'US';
90
+ const hasPref = request.headers.get('cookie')?.includes('wp_lang_pref');
91
+
92
+ if (url.pathname === '/' && !hasPref) {
93
+ const langMap = { IT: '/it/', DE: '/de/', FR: '/fr/', ES: '/es/' };
94
+ if (langMap[country]) {
95
+ return Response.redirect(url.origin + langMap[country], 302);
96
+ }
97
+ }
98
+
99
+ return fetch(request);
100
+ }
101
+ };
102
+ ```
103
+
104
+ ## Language Switcher Widget
105
+
106
+ ### HTML/CSS Language Switcher
107
+
108
+ ```html
109
+ <nav class="language-switcher" aria-label="Language selection">
110
+ <ul>
111
+ <li><a href="https://example.com/about/" hreflang="en" lang="en">English</a></li>
112
+ <li><a href="https://example.com/it/chi-siamo/" hreflang="it" lang="it">Italiano</a></li>
113
+ <li><a href="https://example.com/de/ueber-uns/" hreflang="de" lang="de">Deutsch</a></li>
114
+ <li><a href="https://example.com/fr/a-propos/" hreflang="fr" lang="fr">Français</a></li>
115
+ </ul>
116
+ </nav>
117
+ ```
118
+
119
+ ### WordPress Widget (mu-plugin)
120
+
121
+ ```php
122
+ add_action('widgets_init', function () {
123
+ register_widget('Multilang_Switcher_Widget');
124
+ });
125
+
126
+ class Multilang_Switcher_Widget extends WP_Widget {
127
+ public function __construct() {
128
+ parent::__construct('multilang_switcher', 'Language Switcher');
129
+ }
130
+
131
+ public function widget($args, $instance) {
132
+ $sites = get_sites(['number' => 20]);
133
+ $language_names = [
134
+ 1 => ['code' => 'en', 'name' => 'English'],
135
+ 2 => ['code' => 'it', 'name' => 'Italiano'],
136
+ 3 => ['code' => 'de', 'name' => 'Deutsch'],
137
+ // Add more...
138
+ ];
139
+
140
+ echo $args['before_widget'];
141
+ echo '<nav class="language-switcher" aria-label="Language"><ul>';
142
+ foreach ($sites as $site) {
143
+ $lang = $language_names[$site->blog_id] ?? null;
144
+ if (!$lang) continue;
145
+ $url = get_home_url($site->blog_id);
146
+ $active = ($site->blog_id === get_current_blog_id()) ? ' class="active"' : '';
147
+ printf('<li%s><a href="%s" hreflang="%s" lang="%s">%s</a></li>',
148
+ $active, esc_url($url), esc_attr($lang['code']),
149
+ esc_attr($lang['code']), esc_html($lang['name'])
150
+ );
151
+ }
152
+ echo '</ul></nav>';
153
+ echo $args['after_widget'];
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## URL Structure Consistency
159
+
160
+ Ensure consistent URL patterns across all language sites:
161
+
162
+ | English (primary) | Italian | German | Rule |
163
+ |-------------------|---------|--------|------|
164
+ | `/about/` | `/it/chi-siamo/` | `/de/ueber-uns/` | Translated slugs (better UX) |
165
+ | `/about/` | `/it/about/` | `/de/about/` | Same slugs (easier hreflang matching) |
166
+ | `/products/shoes/` | `/it/prodotti/scarpe/` | `/de/produkte/schuhe/` | Full translation |
167
+
168
+ **Recommendation:** Use **same slugs** for easier hreflang matching via the mu-plugin. If translated slugs are preferred for UX, maintain a cross-reference table or use the multilingual plugin's content connections.
169
+
170
+ ## Cookie-Based Language Preference
171
+
172
+ Store user's explicit language choice to persist across sessions:
173
+
174
+ ```php
175
+ // When user clicks language switcher, set preference cookie
176
+ add_action('init', function () {
177
+ if (isset($_GET['set_lang'])) {
178
+ $lang = sanitize_text_field($_GET['set_lang']);
179
+ $allowed = ['en', 'it', 'de', 'fr', 'es'];
180
+ if (in_array($lang, $allowed, true)) {
181
+ setcookie('wp_lang_pref', $lang, time() + (365 * DAY_IN_SECONDS), '/');
182
+ }
183
+ }
184
+ });
185
+ ```
186
+
187
+ **Priority order for language selection:**
188
+ 1. Explicit choice (cookie from language switcher click) — highest
189
+ 2. URL path (user navigated to `/it/` directly)
190
+ 3. Browser `Accept-Language` header (auto-detect)
191
+ 4. Geo-IP (server-side guess)
192
+ 5. Default language (x-default / main site) — lowest
193
+
194
+ ## Headless Frontend Language Routing
195
+
196
+ ### Next.js i18n
197
+
198
+ ```javascript
199
+ // next.config.js
200
+ module.exports = {
201
+ i18n: {
202
+ locales: ['en', 'it', 'de', 'fr'],
203
+ defaultLocale: 'en',
204
+ localeDetection: true, // Auto-detect from Accept-Language
205
+ },
206
+ };
207
+ ```
208
+
209
+ ### Nuxt i18n Module
210
+
211
+ ```javascript
212
+ // nuxt.config.ts
213
+ export default defineNuxtConfig({
214
+ modules: ['@nuxtjs/i18n'],
215
+ i18n: {
216
+ locales: ['en', 'it', 'de', 'fr'],
217
+ defaultLocale: 'en',
218
+ strategy: 'prefix_except_default', // /it/about, /de/about, /about (en)
219
+ detectBrowserLanguage: {
220
+ useCookie: true,
221
+ cookieKey: 'i18n_redirected',
222
+ },
223
+ },
224
+ });
225
+ ```
226
+
227
+ ## Decision Checklist
228
+
229
+ 1. Should language be detected automatically or only via switcher? → Auto-detect on first visit, respect explicit choice after
230
+ 2. Is geo-IP available at the hosting/CDN level? → CloudFlare/nginx = yes; shared hosting = maybe not
231
+ 3. Are redirect rules using 302 (not 301)? → 301 caches per-user, breaks multi-language
232
+ 4. Is a language switcher present on all pages? → Add to header/footer template
233
+ 5. Is cookie-based preference persisted? → Set on switcher click, check before redirect
234
+ 6. For headless: is frontend i18n routing configured? → Next.js `i18n` or Nuxt i18n module
@@ -0,0 +1,119 @@
1
+ # Network Architecture
2
+
3
+ Use this file when designing the multi-language WordPress Multisite network — choosing between subdomains, subdirectories, or separate domains, selecting a multilingual plugin, and establishing site creation workflows.
4
+
5
+ ## Architecture Patterns
6
+
7
+ | Pattern | Example | Pros | Cons |
8
+ |---------|---------|------|------|
9
+ | **Subdirectory** | `example.com/it/`, `example.com/de/` | Shares domain authority, easy SSL, one hosting | Less geographic signal, shared server resources |
10
+ | **Subdomain** | `it.example.com`, `de.example.com` | Separate GSC properties, clear language signal | SSL per subdomain, DNS configuration needed |
11
+ | **Separate domains** | `example.it`, `example.de` | Strongest geo-targeting signal, full independence | Multiple hosting/SSL/DNS, no shared authority |
12
+
13
+ ### When to Choose Each
14
+
15
+ **Subdirectory (recommended for most):**
16
+ - Starting a multilingual site with shared content
17
+ - Budget-conscious (one hosting, one SSL)
18
+ - Want to inherit domain authority across languages
19
+ - 2–5 languages
20
+
21
+ **Subdomain:**
22
+ - Each language needs significant independence
23
+ - Different teams manage different languages
24
+ - Want separate Google Search Console properties
25
+ - 3–10 languages
26
+
27
+ **Separate domains:**
28
+ - Strong country-specific targeting needed (ccTLDs)
29
+ - Completely independent content per market
30
+ - Enterprise with per-country marketing teams
31
+ - Already owns country domains
32
+
33
+ ## Sub-Site Creation Workflow
34
+
35
+ Using multisite MCP tools to create language sub-sites:
36
+
37
+ ```bash
38
+ # 1. List existing sites
39
+ list_sites()
40
+
41
+ # 2. Create language sub-sites (one per language)
42
+ ms_create_site(domain="example.com", path="/it/", title="Example - Italiano")
43
+ ms_create_site(domain="example.com", path="/de/", title="Example - Deutsch")
44
+ ms_create_site(domain="example.com", path="/fr/", title="Example - Français")
45
+ ms_create_site(domain="example.com", path="/es/", title="Example - Español")
46
+
47
+ # 3. Verify all sites created
48
+ list_sites()
49
+ ```
50
+
51
+ ### Naming Conventions
52
+
53
+ | Element | Convention | Example |
54
+ |---------|-----------|---------|
55
+ | Site slug/path | ISO 639-1 language code | `/it/`, `/de/`, `/fr/` |
56
+ | Site title | `{Brand} - {Language Name}` | "DolceZero - Italiano" |
57
+ | Admin email | Shared network admin email | `admin@example.com` |
58
+ | Language constant | WordPress locale code | `it_IT`, `de_DE`, `fr_FR` |
59
+
60
+ **For regional variants:**
61
+ - Use ISO 639-1 + country: `pt-br` (Brazilian Portuguese), `zh-cn` (Simplified Chinese)
62
+ - Path: `/pt-br/`, `/zh-cn/`
63
+
64
+ ## Shared vs Independent Content
65
+
66
+ | Strategy | Description | Best For |
67
+ |----------|-------------|----------|
68
+ | **Shared + translated** | Same content structure, translated per language | Marketing sites, blogs |
69
+ | **Shared core + local** | Common pages translated, plus local-only content | E-commerce with local products |
70
+ | **Fully independent** | Each site has its own editorial calendar | News sites, regional publications |
71
+
72
+ ## Network Plugin Management
73
+
74
+ ### Network-Activated Plugins (shared across all sites)
75
+
76
+ Plugins that should be network-activated for consistency:
77
+ - Multilingual plugin (WPML, Polylang, MultilingualPress)
78
+ - SEO plugin (Yoast, Rank Math)
79
+ - Caching plugin (WP Super Cache, W3 Total Cache)
80
+ - Security plugin (Wordfence, Sucuri)
81
+
82
+ ```bash
83
+ # Network-activate a plugin (WP-CLI)
84
+ wp plugin activate yoast-seo --network
85
+
86
+ # Or via MCP tool
87
+ ms_network_activate_plugin(plugin="wordpress-seo/wp-seo.php")
88
+ ```
89
+
90
+ ### Per-Site Plugins
91
+
92
+ Plugins that may differ per language site:
93
+ - Payment gateways (region-specific)
94
+ - Legal compliance (GDPR, cookie consent varies by country)
95
+ - Local directory integrations
96
+
97
+ ## Multilingual Plugin Comparison
98
+
99
+ | Feature | WPML | Polylang | MultilingualPress |
100
+ |---------|------|----------|-------------------|
101
+ | **Architecture** | Single-site (can work with multisite) | Single-site (multisite add-on available) | Native multisite |
102
+ | **License** | Paid ($39–$159/yr) | Free core + paid add-ons | Paid (€199/yr) |
103
+ | **Translation management** | Built-in TM, XLIFF export | Manual or XLIFF | Content connections between sites |
104
+ | **String translation** | Yes (wpml-string-translation) | Polylang Pro | Via WordPress i18n |
105
+ | **WooCommerce support** | WPML WooCommerce Multilingual | WooCommerce Polylang | Native multisite WC |
106
+ | **Performance impact** | Medium (additional DB queries) | Low | Low (native multisite) |
107
+ | **Best for** | Single-site multilingual | Small sites, budget-friendly | Multisite-native workflows |
108
+
109
+ ### Recommendation
110
+
111
+ For a **Multisite multi-language network**, prefer **MultilingualPress** (native multisite design) or **WPML** (most feature-rich). Polylang is best for single-site setups.
112
+
113
+ ## Decision Checklist
114
+
115
+ 1. How many languages are needed now and in 2 years? → 2–5 = subdirectory; 5+ = subdomain
116
+ 2. Do language sites share content or are they independent? → Shared = translation plugin; Independent = just multisite
117
+ 3. Is the team centralized or per-country? → Centralized = shared admin; Per-country = per-site admins
118
+ 4. Is WooCommerce involved? → Yes = verify plugin WC compatibility
119
+ 5. What's the budget for multilingual plugin licensing? → Free = Polylang; Paid = WPML or MultilingualPress