$npx -y skills add mralaminahamed/wp-dev-skills --skill wp-i18n-workflowUse when managing the full translation workflow for a WordPress plugin — registering text domain with load_plugin_textdomain, wrapping strings with __(), _e(), esc_html__(), esc_attr__(), _n(), _x(), _ex(), _nx(), generating POT with wp i18n make-pot, compiling .po to .mo (wp i18
| 1 | # WordPress Plugin i18n Workflow |
| 2 | |
| 3 | > **Model note:** Fully mechanical — POT generation, PO/MO compilation, and script registration are tool invocations. `haiku` handles all steps; no cross-file reasoning needed. |
| 4 | |
| 5 | Full translation pipeline for WordPress plugins: POT generation, PO/MO compilation, JavaScript translations, translate.wordpress.org GlotPress, and language pack distribution. Covers the coding conventions and the tooling workflow. |
| 6 | |
| 7 | ## When to use |
| 8 | |
| 9 | - "Generate a POT file for my plugin", "update translation strings". |
| 10 | - "Set up JavaScript translations", "translate strings in React/block editor". |
| 11 | - "Submit to translate.wordpress.org", "set up language packs". |
| 12 | - "Why aren't my translations loading?", "debug missing .mo file". |
| 13 | - "Add translator comments", "handle plurals and context strings". |
| 14 | |
| 15 | **Not for:** PHPCS i18n sniff violations — use `wp-coding-standards`. Checking i18n completeness in an audit — use `wp-plugin-audit` Dimension B. |
| 16 | |
| 17 | ## Method |
| 18 | |
| 19 | ### 1. PHP i18n conventions |
| 20 | |
| 21 | All translatable strings must use the plugin's **text domain** consistently. The text domain must match the `Text Domain:` header and the `load_plugin_textdomain()` call. |
| 22 | |
| 23 | ```php |
| 24 | // Basic translation |
| 25 | __( 'Settings', 'my-plugin' ) |
| 26 | _e( 'Save Changes', 'my-plugin' ) // echo version |
| 27 | |
| 28 | // With HTML context (escape + translate combined) |
| 29 | esc_html__( 'Error message', 'my-plugin' ) |
| 30 | esc_attr__( 'Tooltip text', 'my-plugin' ) |
| 31 | |
| 32 | // Plurals |
| 33 | _n( '%d item', '%d items', $count, 'my-plugin' ) |
| 34 | sprintf( _n( '%d item', '%d items', $count, 'my-plugin' ), $count ) |
| 35 | |
| 36 | // Context strings (disambiguation for translators) |
| 37 | _x( 'Post', 'noun: a blog post', 'my-plugin' ) |
| 38 | _ex( 'Draft', 'verb: save as draft', 'my-plugin' ) |
| 39 | |
| 40 | // Plural with context |
| 41 | _nx( '%d reply', '%d replies', $count, 'comment count', 'my-plugin' ) |
| 42 | ``` |
| 43 | |
| 44 | **Translator comments** — required for strings with placeholders: |
| 45 | ```php |
| 46 | /* translators: %s: plugin version number */ |
| 47 | sprintf( __( 'Version %s', 'my-plugin' ), MY_PLUGIN_VERSION ) |
| 48 | |
| 49 | /* translators: 1: post title, 2: author name */ |
| 50 | sprintf( __( '"%1$s" by %2$s', 'my-plugin' ), $title, $author ) |
| 51 | ``` |
| 52 | Comment must be on the line immediately before the function call and start with `translators:`. |
| 53 | |
| 54 | ### 2. Load text domain |
| 55 | |
| 56 | ```php |
| 57 | add_action( 'init', function() { |
| 58 | load_plugin_textdomain( |
| 59 | 'my-plugin', |
| 60 | false, |
| 61 | dirname( plugin_basename( __FILE__ ) ) . '/languages/' |
| 62 | ); |
| 63 | } ); |
| 64 | ``` |
| 65 | |
| 66 | For WP.org plugins, language packs are auto-loaded from `translate.wordpress.org` — `load_plugin_textdomain()` only needed for bundled `.mo` files or local development. |
| 67 | |
| 68 | ### 3. Generate POT file |
| 69 | |
| 70 | ```bash |
| 71 | # WP-CLI (preferred) |
| 72 | wp i18n make-pot . languages/my-plugin.pot \ |
| 73 | --domain=my-plugin \ |
| 74 | --exclude=vendor,node_modules,tests,build \ |
| 75 | --headers='{"Project-Id-Version":"My Plugin 1.0.0","Report-Msgid-Bugs-To":"https://github.com/my-org/my-plugin/issues"}' |
| 76 | |
| 77 | # Update existing POT (merges new strings, marks removed as obsolete) |
| 78 | wp i18n make-pot . languages/my-plugin.pot --domain=my-plugin |
| 79 | ``` |
| 80 | |
| 81 | Commit `languages/my-plugin.pot` to git. Translators use this as the source. |
| 82 | |
| 83 | ### 4. Compile PO → MO |
| 84 | |
| 85 | `.po` files are human-editable; `.mo` are compiled binary files loaded by PHP. |
| 86 | |
| 87 | ```bash |
| 88 | # Single file |
| 89 | wp i18n make-mo languages/my-plugin-fr_FR.po |
| 90 | |
| 91 | # All PO files in the directory |
| 92 | wp i18n make-mo languages/ |
| 93 | |
| 94 | # Using msgfmt (gettext tools) |
| 95 | msgfmt languages/my-plugin-fr_FR.po -o languages/my-plugin-fr_FR.mo |
| 96 | ``` |
| 97 | |
| 98 | File naming convention: `{text-domain}-{locale}.po` / `.mo` |
| 99 | Examples: `my-plugin-fr_FR.mo`, `my-plugin-de_DE.mo`, `my-plugin-pt_BR.mo` |
| 100 | |
| 101 | ### 5. JavaScript translations |
| 102 | |
| 103 | **Block editor / React components** — use `@wordpress/i18n`: |
| 104 | |
| 105 | ```js |
| 106 | import { __, _n, _x, sprint |