Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

I18N Command Laravel Package

wp-cli/i18n-command

Provides WP-CLI commands for WordPress internationalization: generate POT files, build JSON translations, update PO files, and check translation status from the command line. Part of the WP-CLI toolkit for automating i18n workflows.

Deep Wiki
Context7

Getting Started

wp-cli/i18n-command adds a wp i18n namespace to WP-CLI. It is pure PHP — no xgettext, msgfmt, or msgmerge binaries required — so it works identically on macOS, Linux, and Windows CI runners.

Install it once, globally or per-project:

# Global (recommended for local dev)
wp package install wp-cli/i18n-command

# Or pinned per project so CI gets the same version
composer require --dev wp-cli/i18n-command
vendor/bin/wp i18n --help

Look at wp help i18n first, then wp help i18n make-pot. The five subcommands you will actually use are make-pot, make-json, make-mo, make-php, and update-po.

The first use case is almost always generating a POT file for a plugin or theme. Run it from the project root:

wp i18n make-pot . languages/my-plugin.pot

make-pot reads Plugin Name / Theme Name from the main file to infer the slug and text domain, scans PHP (including _n, _x, _nx, _n_noop, esc_html__, etc.) and JavaScript (__, _x, _n, sprintf-style placeholders), and writes a POT with source references. Commit that POT — it is the contract with your translators and with GlotPress/translate.wordpress.org.

Implementation Patterns

Wire the whole chain into a build script, not into manual steps. A typical composer.json script block:

"scripts": {
  "i18n": [
    "@pot",
    "@json"
  ],
  "pot": "wp i18n make-pot . languages/my-plugin.pot --domain=my-plugin --exclude=node_modules,vendor,tests",
  "json": "wp i18n make-json languages --no-purge --pretty-print"
}

Generate POT in CI and fail on drift. make-pot exits non-zero when the string audit finds placeholder mismatches or malformed calls, which makes it a useful lint step on its own:

wp i18n make-pot . languages/my-plugin.pot
git diff --exit-code languages/my-plugin.pot

This catches the common case where a developer edits a string and forgets to regenerate the POT.

Use --merge to preserve translator work. When regenerating a POT in a project that keeps translations in-repo, --merge=languages/my-plugin.pot carries over existing translator comments and obsolete entries instead of clobbering them. Combined with wp i18n update-po languages/my-plugin.pot languages/, you get a msgmerge-like workflow fully in PHP:

wp i18n make-pot . /tmp/new.pot --merge=languages/my-plugin.pot
wp i18n update-po /tmp/new.pot languages/

Ship compiled artifacts at release time, not in git. .mo and .l10n.php files are build outputs:

wp i18n make-mo languages/ languages/
wp i18n make-php languages/ languages/   # WP 6.5+ performs better with these

WordPress 6.5+ prefers {domain}-{locale}.l10n.php over .mo, and it loads measurably faster because it is an included array rather than a binary parse. Generate both during a release build so older installs still work.

For block/JS strings, the JSON filename is the integration. Register the script with an explicit path so load_script_textdomain() can find the files:

wp_enqueue_script( 'my-plugin-editor', plugins_url( 'build/editor.js', __FILE__ ), [], '1.0', true );
wp_set_script_translations( 'my-plugin-editor', 'my-plugin', plugin_dir_path( __FILE__ ) . 'languages' );

The md5() hash in my-plugin-de_DE-<hash>.json is derived from the JS file's path relative to the plugin (or theme) directory — this is why the source argument and working directory matter, and why make-json is usually run from the plugin root against the languages folder.

Use the classes directly for custom tooling. The package ships WP_CLI\I18n\PotGenerator, PhpCodeExtractor, and JsCodeExtractor. If you have a non-standard source layout, a custom extractor for a template language, or want POT generation inside a PHPUnit test, calling PotGenerator::generate() from a WP-CLI command of your own is far cleaner than shelling out.

Gotchas and Tips

  • Text domain mismatches silently drop strings. If a call uses a domain other than --domain/the inferred slug, it is skipped. Use --ignore-domain when your codebase legitimately mixes domains (e.g. a mu-plugin loading shared code), and use --domain explicitly rather than relying on header inference.
  • make-json silently produces nothing when the PO has no JS references. If the POT was generated with --skip-js, or the .po was created by a tool that dropped #: comments, there is nothing to map. Check that grep -c '\.js:' languages/my-plugin.pot is non-zero.
  • The JSON hash is the number one support question. A file named my-plugin-de_DE-abc123.json that never loads almost always means the hash was computed from a different relative path than WordPress computes at runtime. Run make-json from the plugin root and verify by dumping md5( $relative_path ) against the filename. Symlinked plugin directories break this too.
  • --purge / --no-purge defaults have changed between releases. Always check wp help i18n make-json for the version you pinned, and pass the flag explicitly in scripts rather than relying on the default.
  • Audit failures block POT generation. %s vs %d mismatches, %1$s vs %s inconsistencies between singular and plural, and unbalanced HTML in strings will abort with a non-zero exit. That is the desired behavior in CI; use --skip-audit only for one-off local generations, never in a release pipeline.
  • References churn the diff. Every line-number shift rewrites the POT. Use --file-comment to control the header and accept references as noise, or drop them from the committed POT in a post-processing step. Reviewers should not be reading POT diffs line by line.
  • vendor/ and node_modules/ are excluded by default but not everything is. Custom build directories, .min.js bundles, and copied third-party libraries will be scanned and pollute your POT. Add --exclude= proactively; --debug shows exactly which files were parsed and is the fastest way to diagnose "why is this string missing."
  • --headers replaces --location. Pass a JSON file or inline JSON string for Report-Msgid-Bugs-To, PO-Revision-Date, and friends. The old --location flag is deprecated.
  • Install location collisions. If the package is both globally installed and a project Composer dependency, you can end up running different versions from wp and vendor/bin/wp. Pick one — usually the project-pinned version in CI — and wp package update the global copy when you hit a bug that is already fixed upstream.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect