claude-plugin-wordpress-manager 1.4.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.claude-plugin/plugin.json +7 -3
  2. package/CHANGELOG.md +111 -0
  3. package/README.md +10 -3
  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/GUIDE.md +68 -15
  13. package/docs/guides/INDEX.md +46 -0
  14. package/docs/guides/wp-blog.md +590 -0
  15. package/docs/guides/wp-design-system.md +976 -0
  16. package/docs/guides/wp-ecommerce.md +786 -0
  17. package/docs/guides/wp-landing-page.md +762 -0
  18. package/docs/guides/wp-portfolio.md +713 -0
  19. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  20. package/docs/plans/2026-02-27-local-dev-tools-assessment.md +332 -0
  21. package/docs/plans/2026-02-27-local-env-design.md +179 -0
  22. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  23. package/package.json +7 -3
  24. package/skills/wordpress-router/SKILL.md +25 -5
  25. package/skills/wordpress-router/references/decision-tree.md +59 -3
  26. package/skills/wp-accessibility/SKILL.md +170 -0
  27. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  28. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  29. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  30. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  31. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  32. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  33. package/skills/wp-audit/SKILL.md +4 -0
  34. package/skills/wp-block-development/SKILL.md +5 -0
  35. package/skills/wp-block-themes/SKILL.md +4 -0
  36. package/skills/wp-deploy/SKILL.md +12 -0
  37. package/skills/wp-e2e-testing/SKILL.md +186 -0
  38. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  39. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  40. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  41. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  42. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  43. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  44. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  45. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  46. package/skills/wp-headless/SKILL.md +168 -0
  47. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  48. package/skills/wp-headless/references/cors-config.md +245 -0
  49. package/skills/wp-headless/references/frontend-integration.md +331 -0
  50. package/skills/wp-headless/references/headless-auth.md +286 -0
  51. package/skills/wp-headless/references/webhooks.md +277 -0
  52. package/skills/wp-headless/references/wpgraphql.md +331 -0
  53. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  54. package/skills/wp-i18n/SKILL.md +170 -0
  55. package/skills/wp-i18n/references/js-i18n.md +201 -0
  56. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  57. package/skills/wp-i18n/references/php-i18n.md +196 -0
  58. package/skills/wp-i18n/references/rtl-support.md +206 -0
  59. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  60. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  61. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  62. package/skills/wp-interactivity-api/SKILL.md +4 -0
  63. package/skills/wp-local-env/SKILL.md +233 -0
  64. package/skills/wp-local-env/references/localwp-adapter.md +156 -0
  65. package/skills/wp-local-env/references/mcp-adapter-setup.md +153 -0
  66. package/skills/wp-local-env/references/studio-adapter.md +127 -0
  67. package/skills/wp-local-env/references/wpenv-adapter.md +121 -0
  68. package/skills/wp-local-env/scripts/detect_local_env.mjs +404 -0
  69. package/skills/wp-playground/SKILL.md +13 -1
  70. package/skills/wp-plugin-development/SKILL.md +6 -0
  71. package/skills/wp-rest-api/SKILL.md +4 -0
  72. package/skills/wp-security/SKILL.md +179 -0
  73. package/skills/wp-security/references/api-restriction.md +147 -0
  74. package/skills/wp-security/references/authentication-hardening.md +105 -0
  75. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  76. package/skills/wp-security/references/http-headers.md +105 -0
  77. package/skills/wp-security/references/incident-response.md +144 -0
  78. package/skills/wp-security/references/user-capabilities.md +115 -0
  79. package/skills/wp-security/references/wp-config-security.md +129 -0
  80. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
  81. package/skills/wp-wpcli-and-ops/SKILL.md +6 -0
@@ -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
+ ```
@@ -0,0 +1,196 @@
1
+ # PHP Internationalization
2
+
3
+ Use this file when internationalizing PHP code in WordPress plugins and themes.
4
+
5
+ ## Core translation functions
6
+
7
+ ### Simple strings
8
+
9
+ ```php
10
+ // Return translated string
11
+ $text = __('Hello World', 'my-text-domain');
12
+
13
+ // Echo translated string
14
+ _e('Hello World', 'my-text-domain');
15
+ ```
16
+
17
+ ### Strings with context
18
+
19
+ ```php
20
+ // "Post" as a verb vs noun
21
+ $verb = _x('Post', 'verb', 'my-text-domain');
22
+ $noun = _x('Post', 'noun', 'my-text-domain');
23
+
24
+ // Echo version
25
+ _ex('Post', 'verb', 'my-text-domain');
26
+ ```
27
+
28
+ ### Strings with variables
29
+
30
+ ```php
31
+ // Single placeholder
32
+ $text = sprintf(
33
+ /* translators: %s: user display name */
34
+ __('Welcome, %s!', 'my-text-domain'),
35
+ $user->display_name
36
+ );
37
+
38
+ // Multiple placeholders (use numbered)
39
+ $text = sprintf(
40
+ /* translators: 1: product name, 2: price */
41
+ __('%1$s costs %2$s', 'my-text-domain'),
42
+ $product_name,
43
+ $price
44
+ );
45
+ ```
46
+
47
+ ### Plurals
48
+
49
+ ```php
50
+ $text = sprintf(
51
+ /* translators: %d: number of items */
52
+ _n('%d item', '%d items', $count, 'my-text-domain'),
53
+ $count
54
+ );
55
+
56
+ // With context
57
+ $text = sprintf(
58
+ _nx('%d post', '%d posts', $count, 'blog posts', 'my-text-domain'),
59
+ $count
60
+ );
61
+ ```
62
+
63
+ ## Escaping functions
64
+
65
+ Always use escaping variants when outputting to HTML:
66
+
67
+ ```php
68
+ // Escape for HTML content
69
+ echo esc_html__('Safe text', 'my-text-domain');
70
+ esc_html_e('Safe text', 'my-text-domain');
71
+
72
+ // Escape for HTML attributes
73
+ echo esc_attr__('attribute value', 'my-text-domain');
74
+ esc_attr_e('attribute value', 'my-text-domain');
75
+
76
+ // With context
77
+ echo esc_html_x('Post', 'verb', 'my-text-domain');
78
+ ```
79
+
80
+ ### When to use which
81
+
82
+ | Context | Function |
83
+ |---------|----------|
84
+ | HTML content | `esc_html__()` / `esc_html_e()` |
85
+ | HTML attributes | `esc_attr__()` / `esc_attr_e()` |
86
+ | URLs | `esc_url()` around `__()` |
87
+ | JavaScript strings | `esc_js()` around `__()` |
88
+ | Already-safe internal use | `__()` / `_e()` |
89
+
90
+ ## Text domain loading
91
+
92
+ ### Plugins
93
+
94
+ ```php
95
+ add_action('init', function() {
96
+ load_plugin_textdomain(
97
+ 'my-text-domain',
98
+ false,
99
+ dirname(plugin_basename(__FILE__)) . '/languages'
100
+ );
101
+ });
102
+ ```
103
+
104
+ ### Themes
105
+
106
+ ```php
107
+ add_action('after_setup_theme', function() {
108
+ load_theme_textdomain(
109
+ 'my-text-domain',
110
+ get_template_directory() . '/languages'
111
+ );
112
+ });
113
+
114
+ // Child theme
115
+ add_action('after_setup_theme', function() {
116
+ load_child_theme_textdomain(
117
+ 'my-child-domain',
118
+ get_stylesheet_directory() . '/languages'
119
+ );
120
+ });
121
+ ```
122
+
123
+ ### WordPress 6.7+ (automatic loading)
124
+
125
+ Since WordPress 6.7, translation files placed in `wp-content/languages/plugins/` or `wp-content/languages/themes/` are loaded automatically. Manual `load_plugin_textdomain()` is still recommended as a fallback.
126
+
127
+ ## Common mistakes
128
+
129
+ ### Do NOT concatenate translatable strings
130
+
131
+ ```php
132
+ // WRONG — translators cannot reorder
133
+ __('There are ' . $count . ' items', 'my-text-domain');
134
+
135
+ // CORRECT
136
+ sprintf(__('There are %d items', 'my-text-domain'), $count);
137
+ ```
138
+
139
+ ### Do NOT use variables as text domain
140
+
141
+ ```php
142
+ // WRONG — tools cannot extract
143
+ __('Hello', $domain);
144
+
145
+ // CORRECT — literal string only
146
+ __('Hello', 'my-text-domain');
147
+ ```
148
+
149
+ ### Do NOT translate HTML
150
+
151
+ ```php
152
+ // WRONG — HTML in translatable string
153
+ __('<strong>Warning:</strong> This is dangerous', 'my-text-domain');
154
+
155
+ // CORRECT — separate HTML from text
156
+ '<strong>' . esc_html__('Warning:', 'my-text-domain') . '</strong> '
157
+ . esc_html__('This is dangerous', 'my-text-domain');
158
+ ```
159
+
160
+ ### Do NOT split sentences
161
+
162
+ ```php
163
+ // WRONG — sentence split across calls
164
+ __('Click ', 'my-text-domain') . '<a>' . __('here', 'my-text-domain') . '</a>';
165
+
166
+ // CORRECT — full sentence with placeholder
167
+ sprintf(
168
+ /* translators: %s: link HTML */
169
+ __('Click %s for details', 'my-text-domain'),
170
+ '<a href="...">' . esc_html__('here', 'my-text-domain') . '</a>'
171
+ );
172
+ ```
173
+
174
+ ## Translator comments
175
+
176
+ Add context for translators with `/* translators: */` comments:
177
+
178
+ ```php
179
+ /* translators: %s: date in ISO 8601 format */
180
+ sprintf(__('Published on %s', 'my-text-domain'), $date);
181
+
182
+ /* translators: 1: opening link tag, 2: closing link tag */
183
+ sprintf(__('Read the %1$sfull article%2$s', 'my-text-domain'), '<a href="...">', '</a>');
184
+ ```
185
+
186
+ These comments must appear on the line immediately before the translation function call.
187
+
188
+ ## Verification
189
+
190
+ ```bash
191
+ # Check for missing text domains
192
+ wp i18n make-pot . languages/my-text-domain.pot --slug=my-text-domain
193
+
194
+ # Audit for untranslated strings
195
+ grep -rn "echo \"\|echo '" --include="*.php" | grep -v "__\|_e\|esc_"
196
+ ```
@@ -0,0 +1,206 @@
1
+ # RTL (Right-to-Left) Support
2
+
3
+ Use this file when adding right-to-left language support to WordPress themes and plugins.
4
+
5
+ ## RTL languages
6
+
7
+ Arabic (`ar`), Hebrew (`he`), Persian/Farsi (`fa`), Urdu (`ur`), Pashto (`ps`), Sindhi (`sd`), Kurdish (Sorani) (`ckb`), Uyghur (`ug`), Divehi (`dv`).
8
+
9
+ ## How WordPress handles RTL
10
+
11
+ WordPress automatically:
12
+ 1. Sets `dir="rtl"` on `<html>` when the locale is RTL
13
+ 2. Adds `class="rtl"` to `<body>`
14
+ 3. Loads `*-rtl.css` stylesheets (if they exist) instead of `*.css`
15
+
16
+ Check direction in PHP:
17
+ ```php
18
+ if (is_rtl()) {
19
+ // RTL-specific logic
20
+ }
21
+ ```
22
+
23
+ Check in JavaScript:
24
+ ```js
25
+ const isRtl = document.documentElement.dir === 'rtl';
26
+ // or
27
+ const isRtl = document.body.classList.contains('rtl');
28
+ ```
29
+
30
+ ## CSS approach: Logical properties (recommended)
31
+
32
+ Modern CSS logical properties handle both LTR and RTL automatically:
33
+
34
+ ```css
35
+ /* AVOID physical properties */
36
+ .card {
37
+ margin-left: 20px; /* LTR only */
38
+ padding-right: 10px; /* LTR only */
39
+ text-align: left; /* LTR only */
40
+ float: left; /* LTR only */
41
+ border-left: 1px solid; /* LTR only */
42
+ }
43
+
44
+ /* USE logical properties */
45
+ .card {
46
+ margin-inline-start: 20px; /* left in LTR, right in RTL */
47
+ padding-inline-end: 10px; /* right in LTR, left in RTL */
48
+ text-align: start; /* left in LTR, right in RTL */
49
+ float: inline-start; /* left in LTR, right in RTL */
50
+ border-inline-start: 1px solid; /* left in LTR, right in RTL */
51
+ }
52
+ ```
53
+
54
+ ### Logical property mapping
55
+
56
+ | Physical | Logical | LTR | RTL |
57
+ |----------|---------|-----|-----|
58
+ | `left` | `inline-start` | left | right |
59
+ | `right` | `inline-end` | right | left |
60
+ | `margin-left` | `margin-inline-start` | margin-left | margin-right |
61
+ | `margin-right` | `margin-inline-end` | margin-right | margin-left |
62
+ | `padding-left` | `padding-inline-start` | padding-left | padding-right |
63
+ | `padding-right` | `padding-inline-end` | padding-right | padding-left |
64
+ | `border-left` | `border-inline-start` | border-left | border-right |
65
+ | `text-align: left` | `text-align: start` | left | right |
66
+ | `float: left` | `float: inline-start` | left | right |
67
+
68
+ ### Shorthand
69
+
70
+ ```css
71
+ /* Physical: top right bottom left */
72
+ margin: 10px 20px 10px 0;
73
+
74
+ /* Logical */
75
+ margin-block: 10px; /* top and bottom */
76
+ margin-inline: 0 20px; /* start and end */
77
+ ```
78
+
79
+ ## CSS approach: RTL stylesheets (legacy)
80
+
81
+ ### Automatic RTL generation
82
+
83
+ ```bash
84
+ # Generate RTL stylesheets with rtlcss
85
+ npx rtlcss style.css style-rtl.css
86
+
87
+ # Watch mode
88
+ npx rtlcss -w src/style.css dist/style-rtl.css
89
+ ```
90
+
91
+ ### rtlcss directives
92
+
93
+ Control flipping with comments:
94
+
95
+ ```css
96
+ /* rtl:ignore — skip this rule */
97
+ .icon-arrow {
98
+ /* rtl:ignore */
99
+ transform: rotate(45deg);
100
+ }
101
+
102
+ /* rtl:remove — remove this rule in RTL */
103
+ .ltr-only {
104
+ /* rtl:remove */
105
+ float: left;
106
+ }
107
+
108
+ /* rtl:raw — insert raw CSS in RTL */
109
+ .custom {
110
+ /* rtl:raw:
111
+ float: right;
112
+ */
113
+ }
114
+ ```
115
+
116
+ ### Enqueuing RTL stylesheets
117
+
118
+ WordPress handles this automatically when you enqueue properly:
119
+
120
+ ```php
121
+ wp_enqueue_style('my-style', plugins_url('css/style.css', __FILE__));
122
+ // WordPress will automatically load css/style-rtl.css in RTL contexts
123
+ // IF the RTL file exists in the same directory
124
+ ```
125
+
126
+ For custom paths:
127
+ ```php
128
+ wp_style_add_data('my-style', 'rtl', 'replace');
129
+ // WordPress will load style-rtl.css instead of style.css
130
+ ```
131
+
132
+ Or add an independent RTL stylesheet:
133
+ ```php
134
+ wp_style_add_data('my-style', 'rtl', plugin_dir_url(__FILE__) . 'css/custom-rtl.css');
135
+ ```
136
+
137
+ ## Block editor RTL support
138
+
139
+ ### Block styles
140
+
141
+ ```css
142
+ /* Use logical properties in block stylesheets */
143
+ .wp-block-my-plugin-card {
144
+ padding-inline-start: 1rem;
145
+ border-inline-start: 3px solid var(--wp--preset--color--primary);
146
+ }
147
+ ```
148
+
149
+ ### useBlockProps and direction
150
+
151
+ ```js
152
+ import { useBlockProps } from '@wordpress/block-editor';
153
+
154
+ export default function Edit() {
155
+ const blockProps = useBlockProps();
156
+ // blockProps automatically inherits the document direction
157
+ return <div {...blockProps}>Content</div>;
158
+ }
159
+ ```
160
+
161
+ ## Icons and directional elements
162
+
163
+ Flip directional icons in RTL:
164
+
165
+ ```css
166
+ /* Arrows, chevrons, navigation icons */
167
+ .rtl .icon-arrow-right {
168
+ transform: scaleX(-1);
169
+ }
170
+
171
+ /* Or with logical approach */
172
+ [dir="rtl"] .icon-next {
173
+ transform: scaleX(-1);
174
+ }
175
+ ```
176
+
177
+ Do NOT flip:
178
+ - Clocks (always clockwise)
179
+ - Media playback controls (play/pause universal)
180
+ - Checkmarks
181
+ - Phone icons
182
+ - Logos and brand marks
183
+
184
+ ## Testing RTL
185
+
186
+ ```bash
187
+ # Switch site to Arabic
188
+ wp site switch-language ar
189
+
190
+ # Switch back to English
191
+ wp site switch-language en_US
192
+ ```
193
+
194
+ Quick browser test: add `dir="rtl"` to `<html>` in DevTools.
195
+
196
+ WordPress admin: Settings → General → Site Language → Arabic (or any RTL language).
197
+
198
+ ## Verification checklist
199
+
200
+ - [ ] All CSS uses logical properties (or RTL stylesheets exist)
201
+ - [ ] No hardcoded `left`/`right` in positioning
202
+ - [ ] Directional icons flip correctly
203
+ - [ ] Text alignment uses `start`/`end` not `left`/`right`
204
+ - [ ] Flexbox/Grid layout respects direction
205
+ - [ ] RTL stylesheets are properly enqueued with `wp_style_add_data`
206
+ - [ ] Tested with at least one RTL locale (Arabic recommended)