claude-plugin-wordpress-manager 2.2.1 → 2.3.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 +26 -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/docs/GUIDE.md +126 -10
- 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,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
|
|
@@ -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
|