wp-i18n-audit
Audits WordPress plugin or theme PHP code for internationalization (i18n) correctness — text-domain consistency, use of escaped translation helpers (esc_html__, esc_attr__, esc_html_e), correct placeholder helpers (sprintf with translator comments, _n for plurals, _x for context), no variable text-domains, no concatenation inside __() calls, presence of load_plugin_textdomain or load_theme_textdomain, and matching declared Text Domain in the plugin/theme header. Use before plugin/theme release, when reviewing contributor PRs, when adding new strings, when migrating to a new text domain, or when a translator reports issues with the .pot file.
Best use case
wp-i18n-audit is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Audits WordPress plugin or theme PHP code for internationalization (i18n) correctness — text-domain consistency, use of escaped translation helpers (esc_html__, esc_attr__, esc_html_e), correct placeholder helpers (sprintf with translator comments, _n for plurals, _x for context), no variable text-domains, no concatenation inside __() calls, presence of load_plugin_textdomain or load_theme_textdomain, and matching declared Text Domain in the plugin/theme header. Use before plugin/theme release, when reviewing contributor PRs, when adding new strings, when migrating to a new text domain, or when a translator reports issues with the .pot file.
Teams using wp-i18n-audit should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/wp-i18n-audit/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How wp-i18n-audit Compares
| Feature / Agent | wp-i18n-audit | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Audits WordPress plugin or theme PHP code for internationalization (i18n) correctness — text-domain consistency, use of escaped translation helpers (esc_html__, esc_attr__, esc_html_e), correct placeholder helpers (sprintf with translator comments, _n for plurals, _x for context), no variable text-domains, no concatenation inside __() calls, presence of load_plugin_textdomain or load_theme_textdomain, and matching declared Text Domain in the plugin/theme header. Use before plugin/theme release, when reviewing contributor PRs, when adding new strings, when migrating to a new text domain, or when a translator reports issues with the .pot file.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# WordPress i18n audit
A focused review of translation correctness in WP plugin or theme PHP. Catches the boring mistakes that cause `.pot` extraction to break, translators to ship wrong files, or strings to silently fail to translate at runtime.
## When to use this skill
Trigger when:
- The user is preparing a plugin or theme for release, especially for wp.org.
- A translator has reported missing or broken strings.
- The user is adding new user-facing strings.
- The user is migrating from one text-domain to another.
- The diff contains: `__(`, `_e(`, `_n(`, `_x(`, `_nx(`, `_ex(`, `esc_html__(`, `esc_html_e(`, `esc_attr__(`, `esc_attr_e(`, `translate(`, `load_plugin_textdomain`, `load_theme_textdomain`, `Text Domain:`.
## How to run the audit
Work through the checks below. For each finding, produce:
1. File and line.
2. The offending code (1–3 lines).
3. The fix.
4. Severity: **HIGH** (translation broken), **MEDIUM** (will break .pot extraction or one locale), **LOW** (best practice).
Do NOT silently rewrite. Produce a report; only edit on user request.
## Determine the canonical text-domain first
Before any other check, find the project's declared text-domain:
1. Open the main plugin file or `style.css` (theme).
2. Read the header — look for `Text Domain: <slug>`.
3. If absent, **derive the expected domain from the plugin folder
slug** — since WP 4.6 wp.org-distributed plugins auto-load
translations using the folder slug as the domain, and a missing
header is not by itself a runtime breakage. Severity for the
missing header alone:
- LOW (or no finding) when the strings consistently use the folder
slug as the domain — runtime works.
- MEDIUM when wp.org distribution is intended (the header is still
recommended for clarity and tooling).
- HIGH only when the header is missing AND the strings use a
different domain than the folder slug — runtime translation will
not work.
4. Use the derived/declared slug as the **expected domain** for every subsequent check.
If the codebase contains multiple plugins/themes, repeat per project — do NOT mix domains across folders.
## Audit checks
### 1. Text-domain consistency
Every translation call must pass the expected domain as the last argument.
```php
// WRONG — wrong domain
__( 'Save', 'some-other-plugin' )
// WRONG — missing domain (defaults to WP core, broken extraction)
__( 'Save' )
// RIGHT
__( 'Save', 'myplugin' )
```
Flag any call where the domain literal does not match the project domain. **HIGH** if the wrong domain is used (string lands in another project's `.pot`); **MEDIUM** if missing.
### 2. No variable text-domains
```php
// WRONG — wp i18n tools cannot extract this
$d = 'myplugin';
__( 'Save', $d );
__( 'Save', MY_TEXTDOMAIN );
```
Tools like `wp i18n make-pot` parse statically. The domain MUST be a string literal. Constants and variables are skipped. **HIGH**.
### 3. No variable strings
```php
// WRONG — extractor cannot follow
__( $message, 'myplugin' )
__( "Hello $name", 'myplugin' ) // double quotes with interpolation
__( 'Hello ' . $name, 'myplugin' ) // concatenation
```
The first argument must be a static string literal. For dynamic content use `sprintf` with placeholders (check 5). **HIGH** — string never appears in `.pot`.
### 4. Use the escaped helper at the point of output
If the translated string is echoed into HTML, escape AT translation time using the right helper:
```php
// WRONG
echo __( 'Save', 'myplugin' );
// RIGHT — HTML body
echo esc_html__( 'Save', 'myplugin' );
esc_html_e( 'Save', 'myplugin' );
// RIGHT — HTML attribute
echo '<button title="' . esc_attr__( 'Save', 'myplugin' ) . '">';
```
Map:
- `__()` → builds string only, no escape (use when passing to a function that escapes later, or in non-HTML contexts).
- `_e()` → echoes unescaped (rarely correct in HTML — flag as **MEDIUM** unless context is non-HTML or escape is applied around it).
- `esc_html__` / `esc_html_e` → for HTML body.
- `esc_attr__` / `esc_attr_e` → for HTML attributes.
- For URLs in `href`/`src`, translate with `__()` then wrap with `esc_url()`.
### 5. Placeholders: sprintf, not concatenation
```php
// WRONG — translators cannot reorder, can break grammar
echo __( 'Hello, ', 'myplugin' ) . $name . __( '!', 'myplugin' );
// RIGHT
printf(
/* translators: %s: user display name */
esc_html__( 'Hello, %s!', 'myplugin' ),
esc_html( $name )
);
```
Rules:
- Single placeholder: `%s` (string), `%d` (int), `%1$s` `%2$s` for ordered.
- Multiple placeholders MUST be numbered (`%1$s`, `%2$s`) so translators can reorder.
- Always add a `/* translators: %1$s: ..., %2$s: ... */` comment immediately above the call. wp i18n tools surface this in the `.pot`.
- The placeholder VALUE still needs escaping (`esc_html( $name )`) — the translation helper escapes the template, not the inserted value.
### 6. Plurals: `_n` and `_nx`
```php
// WRONG — English-centric, breaks in languages with multiple plural forms
echo $count . ' items';
printf( __( '%d items', 'myplugin' ), $count );
// RIGHT
printf(
/* translators: %s: number of items */
esc_html( _n( '%s item', '%s items', $count, 'myplugin' ) ),
number_format_i18n( $count )
);
```
Use `_n` for plurals, `_nx` when context is needed. Flag any `if ( $n === 1 ) ... else ...` pattern around translated strings — that's English plural logic, doesn't generalize.
### 7. Context: `_x` and `_ex`
When a single English word has multiple meanings (e.g. "Post" = noun vs. verb), use `_x` to disambiguate for translators:
```php
$label_noun = _x( 'Post', 'noun: a blog post', 'myplugin' );
$label_verb = _x( 'Post', 'verb: to publish', 'myplugin' );
```
Flag short, ambiguous strings (`'Post'`, `'View'`, `'Order'`, `'Address'`, `'Read'`) using `__` without context — **LOW** unless the project clearly mixes meanings, then **MEDIUM**.
### 8. `load_plugin_textdomain` / `load_theme_textdomain`
Plugins distributed via wp.org since WP 4.6 have translations
auto-loaded — an explicit `load_plugin_textdomain()` call is
**recommended for self-hosted .mo files** but not strictly required
for wp.org-distributed plugins. Do not treat its absence as a HIGH
finding without first establishing whether the plugin is shipped via
wp.org.
When you do call it, hook on `init` (recommended) — NOT
`plugins_loaded`. Since WP 6.7, calling `load_plugin_textdomain()`
before `init` triggers a `_doing_it_wrong` notice because
just-in-time translation loading runs at `init`. Themes hook on
`after_setup_theme`.
```php
add_action( 'init', function () {
load_plugin_textdomain(
'myplugin',
false,
dirname( plugin_basename( __FILE__ ) ) . '/languages/'
);
} );
```
Check:
- Domain string matches the expected domain (header or folder slug).
**HIGH** mismatch.
- Hooked on `init` or later — not `plugins_loaded`. **MEDIUM** if too
early (triggers `_doing_it_wrong` on WP 6.7+).
- Path points to a real directory in the package. **MEDIUM** if
missing on a self-hosted plugin; **LOW** for wp.org plugins where
auto-loading covers it.
### 9. Header consistency
Plugin header (main file) or theme header (style.css) must declare `Text Domain` and ideally `Domain Path`:
```
Plugin Name: My Plugin
Text Domain: myplugin
Domain Path: /languages
```
Severity of a missing `Text Domain` header depends on whether
strings still resolve at runtime — see the rule in *Determine the
canonical text-domain first* at the top of this skill. Missing
`Domain Path` is **LOW** if the directory is the default `/languages`.
### 10. JavaScript translations
If the plugin localizes strings to JS via `wp_set_script_translations`:
```php
wp_set_script_translations( 'myplugin-script', 'myplugin', plugin_dir_path( __FILE__ ) . 'languages' );
```
In JS:
```js
import { __, _n, sprintf } from '@wordpress/i18n';
const label = __( 'Save', 'myplugin' );
```
Flag mismatched domain between PHP and JS calls. Out of scope: deep JS audit (different skill).
## Severity guide
- **HIGH:** wrong / missing text-domain, variable text-domain, dynamic first argument to `__()`, missing `Text Domain` header.
- **MEDIUM:** unescaped `_e` in HTML, missing translator comment on `sprintf`, concatenation across translated strings, `load_plugin_textdomain` on the wrong hook, plural handled with English `if`.
- **LOW:** missing `Domain Path` header, ambiguous short strings without `_x` context, missing `load_plugin_textdomain` on a wp.org-distributed plugin.
## Report format
```
# i18n audit: <plugin/theme name>
Declared text-domain: <slug>
Header location: <file>:<line>
Date: <YYYY-MM-DD>
## HIGH
1. <file>:<line> — <issue>
<code>
Fix: <code>
## MEDIUM
...
## LOW
...
## Out of scope
- JS string audit (run separately if needed).
- .po / .mo file content (this skill only audits source code).
```
## Cross-references
- **`wp-security-audit`** — covers escape correctness in non-translation contexts; complementary, not redundant.
- For a release-readiness review, run `wp-security-audit`, `wp-security-secrets`, and this skill in sequence before submission.
## What this skill does NOT cover
- Quality of existing `.po` / `.mo` translations (linguistic review).
- Translation memory or glossary consistency.
- JavaScript-side i18n beyond a domain-match spot check.
- RTL / locale-specific CSS or layout.
- Date/number formatting beyond a `number_format_i18n` reminder.
## References
- WP Plugin i18n handbook: https://developer.wordpress.org/plugins/internationalization/
- WP Theme i18n handbook: https://developer.wordpress.org/themes/functionality/internationalization/
- `wp i18n make-pot`: https://developer.wordpress.org/cli/commands/i18n/make-pot/Related Skills
wp-security-audit
Audits WordPress plugin or theme PHP code for the most common security mistakes — missing nonce checks, capability checks, input sanitization, output escaping, unslashing, SQL preparation, AJAX nopriv exposure, file/path traversal, and unsafe redirects. Use when reviewing pull requests, before releasing a plugin, when the user asks "is this secure", or when handling code that touches $_GET / $_POST / $_REQUEST / $_COOKIE / $_FILES / $_SERVER, admin-ajax / admin-post, REST endpoints, options, user meta, custom DB queries, or file uploads.
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
web-artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
valyu
Search the live web and 36+ specialised data sources including SEC filings, PubMed, ChEMBL, clinical trials, FRED economic indicators, and patent databases. Use when current, authoritative, or paywalled data is required.
ui-ux-pro-max
AI-powered design intelligence with 67 UI styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 15+ tech stacks. Generates complete design systems for any product type with industry-specific reasoning rules.
theme-factory
Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.
slack-gif-creator
Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."
skill-creator
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
shannon
Autonomous AI security pen testing. Executes real exploits against web applications to find SQL injection, XSS, SSRF, authentication flaws, and IDOR vulnerabilities. Reports only confirmed, reproducible findings — no false positives.
remotion
Create programmatic videos using React and Remotion. Translate natural language descriptions into working Remotion components for product demos, release announcements, explainer videos, and animated content.
planetscale
Design schemas and write queries for PlanetScale serverless MySQL using branch-based workflows. Ensures index coverage, avoids foreign key anti-patterns, and treats every schema change as a reviewable, reversible deploy request.
mcp-builder
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).