claude-plugin-wordpress-manager 1.5.0 → 1.7.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.
Files changed (68) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +97 -0
  3. package/README.md +27 -13
  4. package/agents/wp-accessibility-auditor.md +206 -0
  5. package/agents/wp-content-strategist.md +18 -0
  6. package/agents/wp-deployment-engineer.md +34 -2
  7. package/agents/wp-performance-optimizer.md +12 -0
  8. package/agents/wp-security-auditor.md +20 -0
  9. package/agents/wp-security-hardener.md +266 -0
  10. package/agents/wp-site-manager.md +14 -0
  11. package/agents/wp-test-engineer.md +207 -0
  12. package/docs/guides/INDEX.md +46 -0
  13. package/docs/guides/wp-blog.md +590 -0
  14. package/docs/guides/wp-design-system.md +976 -0
  15. package/docs/guides/wp-ecommerce.md +786 -0
  16. package/docs/guides/wp-landing-page.md +762 -0
  17. package/docs/guides/wp-portfolio.md +713 -0
  18. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  19. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  20. package/package.json +2 -2
  21. package/skills/wordpress-router/references/decision-tree.md +12 -2
  22. package/skills/wp-accessibility/SKILL.md +170 -0
  23. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  24. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  25. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  26. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  27. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  28. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  29. package/skills/wp-audit/SKILL.md +4 -0
  30. package/skills/wp-block-development/SKILL.md +5 -0
  31. package/skills/wp-block-themes/SKILL.md +4 -0
  32. package/skills/wp-e2e-testing/SKILL.md +186 -0
  33. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  34. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  35. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  36. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  37. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  38. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  39. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  40. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  41. package/skills/wp-headless/SKILL.md +168 -0
  42. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  43. package/skills/wp-headless/references/cors-config.md +245 -0
  44. package/skills/wp-headless/references/frontend-integration.md +331 -0
  45. package/skills/wp-headless/references/headless-auth.md +286 -0
  46. package/skills/wp-headless/references/webhooks.md +277 -0
  47. package/skills/wp-headless/references/wpgraphql.md +331 -0
  48. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  49. package/skills/wp-i18n/SKILL.md +170 -0
  50. package/skills/wp-i18n/references/js-i18n.md +201 -0
  51. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  52. package/skills/wp-i18n/references/php-i18n.md +196 -0
  53. package/skills/wp-i18n/references/rtl-support.md +206 -0
  54. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  55. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  56. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  57. package/skills/wp-interactivity-api/SKILL.md +4 -0
  58. package/skills/wp-plugin-development/SKILL.md +6 -0
  59. package/skills/wp-rest-api/SKILL.md +4 -0
  60. package/skills/wp-security/SKILL.md +179 -0
  61. package/skills/wp-security/references/api-restriction.md +147 -0
  62. package/skills/wp-security/references/authentication-hardening.md +105 -0
  63. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  64. package/skills/wp-security/references/http-headers.md +105 -0
  65. package/skills/wp-security/references/incident-response.md +144 -0
  66. package/skills/wp-security/references/user-capabilities.md +115 -0
  67. package/skills/wp-security/references/wp-config-security.md +129 -0
  68. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: wp-i18n
3
+ description: "Use when internationalizing WordPress plugins or themes: PHP gettext functions (__/esc_html__/etc.), JavaScript i18n (@wordpress/i18n), .pot/.po/.mo translation workflow, WP-CLI i18n commands, RTL stylesheet support, and multilingual plugin setup (Polylang/WPML)."
4
+ compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. WP-CLI required for i18n make-pot/make-json."
5
+ version: 1.0.0
6
+ source: "vinmor/wordpress-manager"
7
+ ---
8
+
9
+ # WP i18n
10
+
11
+ ## When to use
12
+
13
+ - Adding internationalization (i18n) to a WordPress plugin or theme
14
+ - Preparing a plugin or theme for WordPress.org submission (i18n is required)
15
+ - Generating `.pot` template files from source code
16
+ - Adding JavaScript translations via `@wordpress/i18n`
17
+ - Supporting right-to-left (RTL) languages such as Arabic, Hebrew, or Persian
18
+ - Setting up a multilingual WordPress site with Polylang or WPML
19
+
20
+ ## Inputs required
21
+
22
+ - **Repo root** — path to the plugin or theme project directory
23
+ - **Text domain** — the unique slug used in all gettext calls (matches the plugin/theme slug)
24
+ - **Project kind** — `plugin` or `theme` (determines how text domain is loaded)
25
+ - **Target languages** — locale codes for translations (e.g., `it_IT`, `de_DE`, `ar`)
26
+ - **Whether JS translation is needed** — true if the project has JavaScript files with user-facing strings
27
+
28
+ ## Procedure
29
+
30
+ ### 0) Detect i18n status
31
+
32
+ Run the detection script to assess the current state of internationalization:
33
+
34
+ ```bash
35
+ node skills/wp-i18n/scripts/i18n_inspect.mjs --cwd=/path/to/project
36
+ node skills/wp-project-triage/scripts/detect_wp_project.mjs
37
+ ```
38
+
39
+ The i18n inspection script outputs JSON with:
40
+ - `textDomain` — the text domain declared in the plugin/theme header
41
+ - `phpI18n` — count and distribution of PHP gettext function usage
42
+ - `jsI18n` — whether `@wordpress/i18n` is installed and configured
43
+ - `translationFiles` — existing .pot, .po, .mo, and .json files
44
+ - `issues[]` — problems found with severity and suggested fixes
45
+
46
+ If the project has no i18n at all, start from step 1. If partial i18n exists, jump to the step that addresses the gaps.
47
+
48
+ ### 1) PHP internationalization
49
+
50
+ Wrap all user-facing strings in PHP files with the appropriate gettext function:
51
+
52
+ | Function | Use case |
53
+ |----------|----------|
54
+ | `__('text', 'domain')` | Return translated string |
55
+ | `_e('text', 'domain')` | Echo translated string |
56
+ | `esc_html__('text', 'domain')` | Return translated + HTML-escaped |
57
+ | `esc_html_e('text', 'domain')` | Echo translated + HTML-escaped |
58
+ | `esc_attr__('text', 'domain')` | Return translated + attribute-escaped |
59
+ | `_n('single', 'plural', $count, 'domain')` | Pluralization |
60
+ | `_x('text', 'context', 'domain')` | Disambiguation context |
61
+ | `sprintf(__('Hello %s', 'domain'), $name)` | Variable substitution |
62
+
63
+ Key rules:
64
+ - The text domain **must** match the `Text Domain:` header in the main plugin file or `style.css`
65
+ - Never pass variables as the first argument — strings must be literal for extraction
66
+ - Use `sprintf()` / `printf()` for dynamic content, never string concatenation
67
+ - Load the text domain: `load_plugin_textdomain()` on `init` or `load_theme_textdomain()` on `after_setup_theme`
68
+
69
+ Read: `references/php-i18n.md`
70
+
71
+ ### 2) JavaScript internationalization
72
+
73
+ For JavaScript files with user-facing strings, use `@wordpress/i18n`:
74
+
75
+ ```js
76
+ import { __, _n, _x, sprintf } from '@wordpress/i18n';
77
+ const label = __('Save Changes', 'my-plugin');
78
+ ```
79
+
80
+ Setup requires:
81
+ 1. Add `@wordpress/i18n` as a dependency (or use `wp-scripts` which includes it)
82
+ 2. Register script translations in PHP via `wp_set_script_translations()`
83
+ 3. Generate JSON translation files from `.po` files via `wp i18n make-json`
84
+ 4. For blocks, set the `textdomain` field in `block.json`
85
+
86
+ Read: `references/js-i18n.md`
87
+
88
+ ### 3) Translation file workflow
89
+
90
+ The WordPress translation pipeline has four file types:
91
+
92
+ 1. **`.pot`** — Template with all extractable strings (generated, never edited manually)
93
+ 2. **`.po`** — Per-locale translations (human-edited or via Poedit/GlotPress)
94
+ 3. **`.mo`** — Compiled binary for PHP runtime (generated from `.po`)
95
+ 4. **`.json`** — Compiled translations for JavaScript (generated from `.po`)
96
+
97
+ File naming: `{text-domain}-{locale}.{po|mo}` (e.g., `my-plugin-it_IT.po`)
98
+
99
+ Workflow: extract → `.pot` → create/update `.po` → compile `.mo` + `.json`
100
+
101
+ Read: `references/translation-workflow.md`
102
+
103
+ ### 4) WP-CLI i18n commands
104
+
105
+ ```bash
106
+ wp i18n make-pot . languages/my-plugin.pot --domain=my-plugin
107
+ wp i18n make-json languages/ --no-purge
108
+ wp i18n make-mo languages/
109
+ wp i18n update-po languages/my-plugin.pot languages/
110
+ ```
111
+
112
+ Integrate into `package.json`:
113
+ ```json
114
+ {
115
+ "scripts": {
116
+ "i18n:pot": "wp i18n make-pot . languages/my-plugin.pot",
117
+ "i18n:json": "wp i18n make-json languages/ --no-purge",
118
+ "i18n:mo": "wp i18n make-mo languages/",
119
+ "i18n:build": "npm run i18n:pot && npm run i18n:mo && npm run i18n:json"
120
+ }
121
+ }
122
+ ```
123
+
124
+ Read: `references/wpcli-i18n.md`
125
+
126
+ ### 5) RTL support
127
+
128
+ WordPress auto-loads `style-rtl.css` when the locale is RTL:
129
+ - Use CSS logical properties (`margin-inline-start` vs `margin-left`)
130
+ - Use `@wordpress/scripts` with `rtlcss` to auto-generate RTL stylesheets
131
+ - Use `is_rtl()` in PHP for conditional logic
132
+ - Test with an RTL locale (Arabic `ar` or Hebrew `he_IL`)
133
+
134
+ Read: `references/rtl-support.md`
135
+
136
+ ### 6) Multilingual site setup
137
+
138
+ For content translation (not just UI strings):
139
+ - **Polylang** (free) — `pll_register_string()`, `pll__()`, `pll_e()`
140
+ - **WPML** (paid) — `wpml-config.xml` for custom post types and taxonomies
141
+ - Both require hreflang tags and a language switcher
142
+
143
+ Read: `references/multilingual-setup.md`
144
+
145
+ ## Verification
146
+
147
+ - `.pot` file generates without errors: `wp i18n make-pot . languages/<domain>.pot`
148
+ - Translations load in the UI: switch locale and confirm translated strings
149
+ - RTL renders correctly: switch to Arabic and check layout
150
+ - Text domain matches: `Text Domain:` header matches domain in all gettext calls
151
+ - JS translations work: `wp.i18n.__()` returns translated strings in browser console
152
+ - No untranslated strings: search for hardcoded user-facing strings
153
+
154
+ Re-run: `node skills/wp-i18n/scripts/i18n_inspect.mjs --cwd=/path`
155
+
156
+ ## Failure modes / debugging
157
+
158
+ - **Translations not loading** — `load_plugin_textdomain()` missing or called on wrong hook; `languages/` path incorrect
159
+ - **Wrong text domain** — domain in gettext calls must exactly match `Text Domain:` header
160
+ - **JS translations missing** — `wp_set_script_translations()` must be called after `wp_enqueue_script()`; JSON files must exist with correct handle-based naming
161
+ - **`.mo` not found** — file must follow `{domain}-{locale}.mo` naming convention
162
+ - **Plural forms broken** — verify `Plural-Forms` header in `.po` matches target language
163
+ - **`make-pot` misses strings** — strings with variables or concatenation cannot be extracted; refactor to literals with `sprintf()`
164
+ - **RTL layout broken** — hardcoded `left`/`right` in CSS; use logical properties or `rtlcss`
165
+
166
+ ## Escalation
167
+
168
+ - WordPress i18n Handbook: https://developer.wordpress.org/plugins/internationalization/
169
+ - CLDR Plural Rules: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
170
+ - Translator Handbook: https://make.wordpress.org/polyglots/handbook/
@@ -0,0 +1,201 @@
1
+ # JavaScript Internationalization
2
+
3
+ Use this file when internationalizing JavaScript code in WordPress blocks, plugins, and themes.
4
+
5
+ ## @wordpress/i18n package
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ npm install @wordpress/i18n
11
+ ```
12
+
13
+ ### Core functions
14
+
15
+ ```js
16
+ import { __, _x, _n, _nx, sprintf } from '@wordpress/i18n';
17
+
18
+ // Simple translation
19
+ const label = __('Save', 'my-text-domain');
20
+
21
+ // With context
22
+ const post = _x('Post', 'verb', 'my-text-domain');
23
+
24
+ // Plurals
25
+ const message = sprintf(
26
+ _n('%d item', '%d items', count, 'my-text-domain'),
27
+ count
28
+ );
29
+
30
+ // Plurals with context
31
+ const posts = sprintf(
32
+ _nx('%d post', '%d posts', count, 'blog posts', 'my-text-domain'),
33
+ count
34
+ );
35
+ ```
36
+
37
+ ### sprintf for variable substitution
38
+
39
+ ```js
40
+ import { __, sprintf } from '@wordpress/i18n';
41
+
42
+ // Positional placeholders
43
+ const text = sprintf(
44
+ /* translators: 1: product name, 2: price */
45
+ __('%1$s costs %2$s', 'my-text-domain'),
46
+ productName,
47
+ price
48
+ );
49
+
50
+ // %s placeholder
51
+ const greeting = sprintf(
52
+ /* translators: %s: user name */
53
+ __('Hello, %s!', 'my-text-domain'),
54
+ userName
55
+ );
56
+ ```
57
+
58
+ ## Block editor (Gutenberg) usage
59
+
60
+ ### Block registration
61
+
62
+ ```js
63
+ import { registerBlockType } from '@wordpress/blocks';
64
+ import { __ } from '@wordpress/i18n';
65
+
66
+ registerBlockType('my-plugin/my-block', {
67
+ title: __('My Block', 'my-text-domain'),
68
+ description: __('A custom block.', 'my-text-domain'),
69
+ keywords: [
70
+ __('example', 'my-text-domain'),
71
+ __('custom', 'my-text-domain'),
72
+ ],
73
+ // ...
74
+ });
75
+ ```
76
+
77
+ ### InspectorControls
78
+
79
+ ```js
80
+ import { InspectorControls } from '@wordpress/block-editor';
81
+ import { PanelBody, ToggleControl } from '@wordpress/components';
82
+ import { __ } from '@wordpress/i18n';
83
+
84
+ <InspectorControls>
85
+ <PanelBody title={__('Settings', 'my-text-domain')}>
86
+ <ToggleControl
87
+ label={__('Show title', 'my-text-domain')}
88
+ help={__('Toggle the title visibility.', 'my-text-domain')}
89
+ checked={showTitle}
90
+ onChange={(val) => setAttributes({ showTitle: val })}
91
+ />
92
+ </PanelBody>
93
+ </InspectorControls>
94
+ ```
95
+
96
+ ## Translation file loading
97
+
98
+ ### For blocks (block.json method — recommended)
99
+
100
+ ```json
101
+ {
102
+ "name": "my-plugin/my-block",
103
+ "title": "My Block",
104
+ "textdomain": "my-text-domain"
105
+ }
106
+ ```
107
+
108
+ WordPress automatically loads translation files for blocks with `textdomain` in `block.json`.
109
+
110
+ ### For non-block scripts
111
+
112
+ Register script translations in PHP:
113
+
114
+ ```php
115
+ add_action('init', function() {
116
+ wp_set_script_translations(
117
+ 'my-script-handle', // Same handle used in wp_register_script
118
+ 'my-text-domain',
119
+ plugin_dir_path(__FILE__) . 'languages'
120
+ );
121
+ });
122
+ ```
123
+
124
+ ### Translation file format
125
+
126
+ JS translations use JSON (JED format). Generated by WP-CLI:
127
+
128
+ ```bash
129
+ wp i18n make-json languages/ --no-purge
130
+ ```
131
+
132
+ Output files: `my-text-domain-{locale}-{md5hash}.json`
133
+
134
+ ## block.json translatable fields
135
+
136
+ These fields are automatically translated when `textdomain` is set:
137
+
138
+ ```json
139
+ {
140
+ "title": "My Block",
141
+ "description": "Block description here.",
142
+ "keywords": ["example", "demo"],
143
+ "styles": [
144
+ { "name": "default", "label": "Default" },
145
+ { "name": "fancy", "label": "Fancy" }
146
+ ],
147
+ "variations": [
148
+ {
149
+ "name": "wide",
150
+ "title": "Wide",
151
+ "description": "Wide layout variation."
152
+ }
153
+ ],
154
+ "textdomain": "my-text-domain"
155
+ }
156
+ ```
157
+
158
+ ## Common mistakes
159
+
160
+ ### Do NOT use template literals for translation keys
161
+
162
+ ```js
163
+ // WRONG — extraction tools cannot parse
164
+ const text = __(`Hello ${name}`, 'my-text-domain');
165
+
166
+ // CORRECT
167
+ const text = sprintf(__('Hello %s', 'my-text-domain'), name);
168
+ ```
169
+
170
+ ### Do NOT use dynamic text domains
171
+
172
+ ```js
173
+ // WRONG
174
+ const text = __(message, domain);
175
+
176
+ // CORRECT — literal string only
177
+ const text = __('Hello', 'my-text-domain');
178
+ ```
179
+
180
+ ### Do NOT concatenate inside translation calls
181
+
182
+ ```js
183
+ // WRONG
184
+ const text = __('Total: ' + count + ' items', 'my-text-domain');
185
+
186
+ // CORRECT
187
+ const text = sprintf(__('Total: %d items', 'my-text-domain'), count);
188
+ ```
189
+
190
+ ## Verification
191
+
192
+ ```bash
193
+ # Generate POT from JS files
194
+ wp i18n make-pot . languages/my-text-domain.pot --include="src/**/*.js,src/**/*.jsx"
195
+
196
+ # Generate JSON translation files
197
+ wp i18n make-json languages/ --no-purge
198
+
199
+ # Check for untranslated strings in JS
200
+ grep -rn "['\"]\`" --include="*.js" --include="*.jsx" src/ | grep -v "__\|_x\|_n\|sprintf"
201
+ ```
@@ -0,0 +1,219 @@
1
+ # Multilingual Setup
2
+
3
+ Use this file when configuring WordPress for multilingual content delivery.
4
+
5
+ ## Plugin-based solutions
6
+
7
+ ### WPML (commercial)
8
+
9
+ Most widely used. Supports posts, pages, custom post types, taxonomies, strings, menus, and widgets.
10
+
11
+ ```php
12
+ // Check if WPML is active
13
+ if (defined('ICL_SITEPRESS_VERSION')) {
14
+ // WPML-specific code
15
+ }
16
+
17
+ // Get current language
18
+ $current_lang = apply_filters('wpml_current_language', null);
19
+
20
+ // Get element translation
21
+ $translated_id = apply_filters('wpml_object_id', $post_id, 'post', true, 'it');
22
+
23
+ // Switch language in a template
24
+ do_action('wpml_switch_lang', 'it');
25
+ // ... output Italian content
26
+ do_action('wpml_switch_lang', null); // reset
27
+ ```
28
+
29
+ Theme/plugin compatibility header:
30
+ ```php
31
+ /**
32
+ * Plugin Name: My Plugin
33
+ * WPML Compatible: yes
34
+ */
35
+ ```
36
+
37
+ ### Polylang (free + pro)
38
+
39
+ ```php
40
+ // Check if Polylang is active
41
+ if (function_exists('pll_current_language')) {
42
+ $lang = pll_current_language();
43
+ }
44
+
45
+ // Get translated post ID
46
+ $translated_id = pll_get_post($post_id, 'it');
47
+
48
+ // Get translated term ID
49
+ $translated_term = pll_get_term($term_id, 'it');
50
+
51
+ // Register a string for translation
52
+ pll_register_string('my-string-name', 'Default text', 'My Plugin');
53
+
54
+ // Get translated string
55
+ $text = pll__('Default text');
56
+ // or echo
57
+ pll_e('Default text');
58
+ ```
59
+
60
+ ### TranslatePress (visual)
61
+
62
+ Frontend visual editing — translates directly on the page.
63
+
64
+ ```php
65
+ // Check if TranslatePress is active
66
+ if (class_exists('TRP_Translate_Press')) {
67
+ // TranslatePress-specific code
68
+ }
69
+ ```
70
+
71
+ ## URL structure options
72
+
73
+ | Strategy | Example | Pros | Cons |
74
+ |----------|---------|------|------|
75
+ | Subdirectories | `example.com/it/` | Simple, single domain | Shared hosting limits |
76
+ | Subdomains | `it.example.com` | Separate analytics | DNS setup required |
77
+ | Domains | `example.it` | Best for SEO by country | Multiple SSL certs |
78
+ | URL parameter | `example.com?lang=it` | Easiest setup | Worst for SEO |
79
+
80
+ Recommended: **subdirectories** for most projects.
81
+
82
+ ## Making plugins translation-compatible
83
+
84
+ ### String registration (WPML)
85
+
86
+ ```php
87
+ // Register admin strings
88
+ add_action('init', function() {
89
+ if (function_exists('icl_register_string')) {
90
+ icl_register_string('my-plugin', 'CTA Button Text', get_option('my_cta_text'));
91
+ }
92
+ });
93
+
94
+ // Retrieve translated string
95
+ function get_my_cta_text() {
96
+ $text = get_option('my_cta_text');
97
+ if (function_exists('icl_t')) {
98
+ return icl_t('my-plugin', 'CTA Button Text', $text);
99
+ }
100
+ return $text;
101
+ }
102
+ ```
103
+
104
+ ### wpml-config.xml
105
+
106
+ Required for WPML to know which custom fields and options to translate:
107
+
108
+ ```xml
109
+ <wpml-config>
110
+ <custom-fields>
111
+ <custom-field action="translate">subtitle</custom-field>
112
+ <custom-field action="copy">price</custom-field>
113
+ <custom-field action="ignore">internal_notes</custom-field>
114
+ </custom-fields>
115
+ <admin-texts>
116
+ <key name="my_plugin_options">
117
+ <key name="cta_text" />
118
+ <key name="footer_text" />
119
+ </key>
120
+ </admin-texts>
121
+ <custom-types>
122
+ <custom-type translate="1">product</custom-type>
123
+ </custom-types>
124
+ <taxonomies>
125
+ <taxonomy translate="1">product_category</taxonomy>
126
+ </taxonomies>
127
+ </wpml-config>
128
+ ```
129
+
130
+ Place at plugin or theme root. WPML reads it automatically.
131
+
132
+ ## Multilingual REST API
133
+
134
+ ### WPML
135
+
136
+ ```bash
137
+ # Get posts in Italian
138
+ curl "https://site.com/wp-json/wp/v2/posts?lang=it"
139
+
140
+ # Requires WPML REST API addon or custom filter
141
+ ```
142
+
143
+ ### Polylang
144
+
145
+ ```bash
146
+ # Polylang adds ?lang= support to REST API
147
+ curl "https://site.com/wp-json/wp/v2/posts?lang=it"
148
+ ```
149
+
150
+ ### Custom REST language filtering
151
+
152
+ ```php
153
+ add_filter('rest_post_query', function($args, $request) {
154
+ $lang = $request->get_param('lang');
155
+ if ($lang && function_exists('pll_current_language')) {
156
+ $args['lang'] = $lang;
157
+ }
158
+ return $args;
159
+ }, 10, 2);
160
+ ```
161
+
162
+ ## hreflang tags
163
+
164
+ Essential for SEO — tells search engines which language version to show:
165
+
166
+ ```php
167
+ add_action('wp_head', function() {
168
+ // WPML handles this automatically
169
+ // For custom implementations:
170
+ $languages = [
171
+ 'en' => 'https://example.com/page/',
172
+ 'it' => 'https://example.com/it/pagina/',
173
+ 'de' => 'https://example.com/de/seite/',
174
+ ];
175
+ foreach ($languages as $lang => $url) {
176
+ printf('<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
177
+ esc_attr($lang), esc_url($url));
178
+ }
179
+ // x-default for language selector/default page
180
+ printf('<link rel="alternate" hreflang="x-default" href="%s" />' . "\n",
181
+ esc_url($languages['en']));
182
+ });
183
+ ```
184
+
185
+ ## WooCommerce multilingual
186
+
187
+ ### WPML + WooCommerce Multilingual
188
+
189
+ - Synchronizes products, variations, attributes across languages
190
+ - Multi-currency support
191
+ - Translated emails and checkout
192
+
193
+ ```php
194
+ // Get translated product ID
195
+ $translated_product_id = apply_filters('wpml_object_id', $product_id, 'product', true, 'it');
196
+
197
+ // Get price in current currency
198
+ // Handled automatically by WooCommerce Multilingual
199
+ ```
200
+
201
+ ### Polylang + Hyyan WooCommerce Polylang Integration
202
+
203
+ Free alternative. Syncs stock, prices, and product data.
204
+
205
+ ## Verification
206
+
207
+ ```bash
208
+ # Check hreflang tags
209
+ curl -s https://site.com/ | grep -i "hreflang"
210
+
211
+ # Verify language switcher works
212
+ curl -s -o /dev/null -w "%{http_code}" https://site.com/it/
213
+
214
+ # Check WPML config file exists (for WPML-compatible plugins)
215
+ ls wpml-config.xml
216
+
217
+ # Verify translated content exists
218
+ wp post list --lang=it --post_type=page --fields=ID,post_title
219
+ ```