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.
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +97 -0
- package/README.md +27 -13
- package/agents/wp-accessibility-auditor.md +206 -0
- package/agents/wp-content-strategist.md +18 -0
- package/agents/wp-deployment-engineer.md +34 -2
- package/agents/wp-performance-optimizer.md +12 -0
- package/agents/wp-security-auditor.md +20 -0
- package/agents/wp-security-hardener.md +266 -0
- package/agents/wp-site-manager.md +14 -0
- package/agents/wp-test-engineer.md +207 -0
- package/docs/guides/INDEX.md +46 -0
- package/docs/guides/wp-blog.md +590 -0
- package/docs/guides/wp-design-system.md +976 -0
- package/docs/guides/wp-ecommerce.md +786 -0
- package/docs/guides/wp-landing-page.md +762 -0
- package/docs/guides/wp-portfolio.md +713 -0
- package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
- package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
- package/package.json +2 -2
- package/skills/wordpress-router/references/decision-tree.md +12 -2
- package/skills/wp-accessibility/SKILL.md +170 -0
- package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
- package/skills/wp-accessibility/references/a11y-testing.md +222 -0
- package/skills/wp-accessibility/references/block-a11y.md +247 -0
- package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
- package/skills/wp-accessibility/references/media-a11y.md +254 -0
- package/skills/wp-accessibility/references/theme-a11y.md +309 -0
- package/skills/wp-audit/SKILL.md +4 -0
- package/skills/wp-block-development/SKILL.md +5 -0
- package/skills/wp-block-themes/SKILL.md +4 -0
- package/skills/wp-e2e-testing/SKILL.md +186 -0
- package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
- package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
- package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
- package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
- package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
- package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
- package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
- package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
- package/skills/wp-headless/SKILL.md +168 -0
- package/skills/wp-headless/references/api-layer-choice.md +160 -0
- package/skills/wp-headless/references/cors-config.md +245 -0
- package/skills/wp-headless/references/frontend-integration.md +331 -0
- package/skills/wp-headless/references/headless-auth.md +286 -0
- package/skills/wp-headless/references/webhooks.md +277 -0
- package/skills/wp-headless/references/wpgraphql.md +331 -0
- package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
- package/skills/wp-i18n/SKILL.md +170 -0
- package/skills/wp-i18n/references/js-i18n.md +201 -0
- package/skills/wp-i18n/references/multilingual-setup.md +219 -0
- package/skills/wp-i18n/references/php-i18n.md +196 -0
- package/skills/wp-i18n/references/rtl-support.md +206 -0
- package/skills/wp-i18n/references/translation-workflow.md +178 -0
- package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
- package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
- package/skills/wp-interactivity-api/SKILL.md +4 -0
- package/skills/wp-plugin-development/SKILL.md +6 -0
- package/skills/wp-rest-api/SKILL.md +4 -0
- package/skills/wp-security/SKILL.md +179 -0
- package/skills/wp-security/references/api-restriction.md +147 -0
- package/skills/wp-security/references/authentication-hardening.md +105 -0
- package/skills/wp-security/references/filesystem-hardening.md +105 -0
- package/skills/wp-security/references/http-headers.md +105 -0
- package/skills/wp-security/references/incident-response.md +144 -0
- package/skills/wp-security/references/user-capabilities.md +115 -0
- package/skills/wp-security/references/wp-config-security.md +129 -0
- 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
|
+
```
|