claude-plugin-wordpress-manager 2.2.0 → 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 +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 +116 -28
- 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,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
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# International SEO
|
|
2
|
+
|
|
3
|
+
Use this file when optimizing a multi-language WordPress Multisite network for international search — Google Search Console per language, language-specific sitemaps, schema localization, and performance per region.
|
|
4
|
+
|
|
5
|
+
## International SEO Fundamentals
|
|
6
|
+
|
|
7
|
+
| Principle | Description |
|
|
8
|
+
|-----------|-------------|
|
|
9
|
+
| One page per language | Each language has its own URL (no dynamic translation) |
|
|
10
|
+
| Unique content | Translations should be human-quality, not raw machine output |
|
|
11
|
+
| Hreflang everywhere | Every page declares all its language alternates |
|
|
12
|
+
| Language-specific sitemaps | One XML sitemap per sub-site/language |
|
|
13
|
+
| GSC per property | Separate Search Console properties for monitoring |
|
|
14
|
+
|
|
15
|
+
## Google Search Console Setup
|
|
16
|
+
|
|
17
|
+
### Per-Language Properties
|
|
18
|
+
|
|
19
|
+
For a multisite with subdirectories:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Property 1: https://example.com/ (English — main)
|
|
23
|
+
Property 2: https://example.com/it/ (Italian)
|
|
24
|
+
Property 3: https://example.com/de/ (German)
|
|
25
|
+
Property 4: https://example.com/fr/ (French)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For subdomains:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Property 1: https://example.com (English)
|
|
32
|
+
Property 2: https://it.example.com (Italian)
|
|
33
|
+
Property 3: https://de.example.com (German)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### International Targeting
|
|
37
|
+
|
|
38
|
+
1. Open each language property in GSC
|
|
39
|
+
2. Go to Legacy tools → International Targeting
|
|
40
|
+
3. Set target country (optional — only if site targets a specific country, not just a language)
|
|
41
|
+
4. Monitor hreflang errors in the same section
|
|
42
|
+
|
|
43
|
+
### Per-Language Monitoring
|
|
44
|
+
|
|
45
|
+
For each language property, monitor:
|
|
46
|
+
- **Coverage:** Indexed pages count (should match published pages on that sub-site)
|
|
47
|
+
- **Performance:** Clicks, impressions, CTR, position — filter by country
|
|
48
|
+
- **Sitemaps:** Verify language-specific sitemap is submitted and processed
|
|
49
|
+
|
|
50
|
+
## Language-Specific XML Sitemaps
|
|
51
|
+
|
|
52
|
+
Each WordPress sub-site in the multisite network generates its own sitemap:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
https://example.com/sitemap_index.xml (English)
|
|
56
|
+
https://example.com/it/sitemap_index.xml (Italian)
|
|
57
|
+
https://example.com/de/sitemap_index.xml (German)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Sitemap Index at Network Level
|
|
61
|
+
|
|
62
|
+
Create a network-level sitemap index that references all sub-site sitemaps:
|
|
63
|
+
|
|
64
|
+
```xml
|
|
65
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
67
|
+
<sitemap>
|
|
68
|
+
<loc>https://example.com/sitemap_index.xml</loc>
|
|
69
|
+
<lastmod>2025-01-15</lastmod>
|
|
70
|
+
</sitemap>
|
|
71
|
+
<sitemap>
|
|
72
|
+
<loc>https://example.com/it/sitemap_index.xml</loc>
|
|
73
|
+
<lastmod>2025-01-14</lastmod>
|
|
74
|
+
</sitemap>
|
|
75
|
+
<sitemap>
|
|
76
|
+
<loc>https://example.com/de/sitemap_index.xml</loc>
|
|
77
|
+
<lastmod>2025-01-13</lastmod>
|
|
78
|
+
</sitemap>
|
|
79
|
+
</sitemapindex>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Hreflang in Sitemaps
|
|
83
|
+
|
|
84
|
+
For maximum hreflang coverage, include language alternates in each sitemap:
|
|
85
|
+
|
|
86
|
+
```xml
|
|
87
|
+
<url>
|
|
88
|
+
<loc>https://example.com/products/</loc>
|
|
89
|
+
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products/" />
|
|
90
|
+
<xhtml:link rel="alternate" hreflang="it" href="https://example.com/it/prodotti/" />
|
|
91
|
+
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/produkte/" />
|
|
92
|
+
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products/" />
|
|
93
|
+
</url>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Note:** If using the hreflang mu-plugin in `<head>`, you can skip sitemap hreflang. Using both is not harmful but adds maintenance.
|
|
97
|
+
|
|
98
|
+
## Structured Data Localization
|
|
99
|
+
|
|
100
|
+
### Schema.org `inLanguage` Property
|
|
101
|
+
|
|
102
|
+
Add language to all structured data:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"@context": "https://schema.org",
|
|
107
|
+
"@type": "WebPage",
|
|
108
|
+
"name": "Benefici dell'Acqua di Cactus",
|
|
109
|
+
"inLanguage": "it",
|
|
110
|
+
"url": "https://example.com/it/benefici-acqua-cactus/",
|
|
111
|
+
"isPartOf": {
|
|
112
|
+
"@type": "WebSite",
|
|
113
|
+
"name": "DolceZero Italia",
|
|
114
|
+
"url": "https://example.com/it/",
|
|
115
|
+
"inLanguage": "it"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Localized Organization Schema
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"@context": "https://schema.org",
|
|
125
|
+
"@type": "Organization",
|
|
126
|
+
"name": "DolceZero",
|
|
127
|
+
"url": "https://example.com/",
|
|
128
|
+
"alternateName": ["DolceZero Italia", "DolceZero Deutschland"],
|
|
129
|
+
"contactPoint": [
|
|
130
|
+
{
|
|
131
|
+
"@type": "ContactPoint",
|
|
132
|
+
"telephone": "+39-xxx-xxx-xxxx",
|
|
133
|
+
"contactType": "customer service",
|
|
134
|
+
"areaServed": "IT",
|
|
135
|
+
"availableLanguage": "Italian"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"@type": "ContactPoint",
|
|
139
|
+
"telephone": "+49-xxx-xxx-xxxx",
|
|
140
|
+
"contactType": "customer service",
|
|
141
|
+
"areaServed": "DE",
|
|
142
|
+
"availableLanguage": "German"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Canonical URLs in Multi-Language Context
|
|
149
|
+
|
|
150
|
+
| Scenario | Canonical Rule |
|
|
151
|
+
|----------|---------------|
|
|
152
|
+
| English product page | Self-canonical: `https://example.com/products/cactus-water/` |
|
|
153
|
+
| Italian product page | Self-canonical: `https://example.com/it/prodotti/acqua-cactus/` |
|
|
154
|
+
| English = Italian (untranslated) | **Do NOT canonical Italian → English** — either translate or noindex |
|
|
155
|
+
| URL with `?lang=` parameter | Canonical → clean URL without parameter |
|
|
156
|
+
|
|
157
|
+
**Critical rule:** Never use canonical across languages. Each language URL is canonical for its language. Use hreflang to declare relationships.
|
|
158
|
+
|
|
159
|
+
## Avoiding Duplicate Content
|
|
160
|
+
|
|
161
|
+
| Issue | Solution |
|
|
162
|
+
|-------|----------|
|
|
163
|
+
| Untranslated pages showing English on Italian site | Set as `noindex` until translated, or serve 404 |
|
|
164
|
+
| Machine-translated thin content | Block from indexing; translate properly or remove |
|
|
165
|
+
| Same content on main site and language site | Ensure distinct translations; use hreflang for signals |
|
|
166
|
+
| Language switcher creating `?lang=` URLs | Canonical to clean URL; use path-based routing |
|
|
167
|
+
|
|
168
|
+
## Performance: CDN per Region
|
|
169
|
+
|
|
170
|
+
### CloudFlare Configuration
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
CDN cache by language:
|
|
174
|
+
- English: Edge servers worldwide (default)
|
|
175
|
+
- Italian: Prioritize EU-South edge locations
|
|
176
|
+
- German: Prioritize EU-Central edge locations
|
|
177
|
+
- Japanese: Prioritize Asia-Pacific edge locations
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Image Localization
|
|
181
|
+
|
|
182
|
+
- Product images: shared across languages (CDN serves nearest edge)
|
|
183
|
+
- Marketing banners: may differ per language (upload per sub-site)
|
|
184
|
+
- OG images: localized per language (localized text overlays)
|
|
185
|
+
|
|
186
|
+
## Measuring International SEO
|
|
187
|
+
|
|
188
|
+
### Metrics per Language
|
|
189
|
+
|
|
190
|
+
| Metric | Source | Frequency |
|
|
191
|
+
|--------|--------|-----------|
|
|
192
|
+
| Organic traffic by language | GA4 (filter by page path prefix) | Weekly |
|
|
193
|
+
| Rankings by country | GSC Performance → Country filter | Weekly |
|
|
194
|
+
| Indexed pages per language | GSC Coverage per property | Monthly |
|
|
195
|
+
| Hreflang errors | GSC International Targeting | Monthly |
|
|
196
|
+
| Conversion rate by language | WooCommerce + GA4 | Monthly |
|
|
197
|
+
|
|
198
|
+
### Success Indicators
|
|
199
|
+
|
|
200
|
+
- Indexed pages per language ≈ published pages per sub-site
|
|
201
|
+
- Zero hreflang errors in GSC
|
|
202
|
+
- Organic traffic growing in target language markets
|
|
203
|
+
- Conversion rate per language within 20% of primary language
|
|
204
|
+
|
|
205
|
+
## Decision Checklist
|
|
206
|
+
|
|
207
|
+
1. Is GSC set up per language property? → Create and verify each
|
|
208
|
+
2. Are language-specific sitemaps submitted? → One per sub-site
|
|
209
|
+
3. Is `inLanguage` set in structured data? → Add to schema on each sub-site
|
|
210
|
+
4. Are canonicals self-referencing per language (never cross-language)? → Audit sample pages
|
|
211
|
+
5. Are untranslated pages noindexed? → Never show English content as Italian page
|
|
212
|
+
6. Is CDN configured for international edge delivery? → Check CDN dashboard
|
|
213
|
+
7. Are international SEO metrics tracked per language? → Set up monthly report
|