A block theme is internationalized when every user-facing string has a stable text domain, is wrapped in the correct WordPress translation API for its execution context, and is shipped with extractable translation files. In practice, that means coordinating style.css metadata, PHP templates and pattern files, editor-side JavaScript, WP-CLI extraction, and locale testing. Set those pieces up before release and a theme-directory language pack can supply translations automatically; for custom files, the paths, handles, and filenames must match WordPress’s conventions exactly.
Antihost offers managed WordPress hosting with free migration, one-click staging and a 14-day free trial.
What internationalization means in a block theme
Internationalization (often written “i18n,” with 18 letters between the first i and the final n) prepares a theme for translation. Translation itself is the later act of supplying a locale such as French, German, or Japanese. A block theme needs both because its visible interface is split across several places:
As an Amazon Associate I earn from qualifying purchases.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →- Theme metadata: the slug and text-domain declaration identify the strings as belonging to your theme.
- Block templates and template parts: these are mostly block markup, but a theme can also render dynamic or authored text through PHP.
- Patterns: reusable designs may contain headings, calls to action, and other copy that must be made translatable.
- Editor JavaScript: custom blocks, inspector controls, notices, and other editor UI run in a JavaScript context and use the
@wordpress/i18npackage. - Translation files: PHP commonly consumes MO files, while JavaScript consumes generated JED 1.x JSON files.
The goal is not to translate every value in a theme. CSS class names, block names, URLs, machine-readable attributes, and internal identifiers should remain stable. Translate anything a site owner or visitor can read, including default headings, labels, button text, descriptions, accessibility text, and messages shown by editor scripts.
Choose and declare one text domain
Start with a unique, lowercase, kebab-case text domain that matches the theme slug. If the directory is northwind, use northwind; if the slug is northwind-magazine, use northwind-magazine. WordPress’s theme guidance requires the text domain to match the theme slug for reliable translation handling. Do not vary it between PHP, JavaScript, and metadata.
#1 Best Overall
Declare it in the theme’s style.css header:
/*
Theme Name: Northwind Magazine
Theme URI: https://example.com/northwind-magazine
Description: A block theme for editorial sites.
Version: 1.0.0
Author: Example Studio
Text Domain: northwind-magazine
*/
If you keep language files somewhere other than the normal theme language location, also declare a Domain Path. The value is a path relative to the theme directory, commonly:
Domain Path: /languages
The domain is case-sensitive in practice because it is passed through APIs and used in filenames. A typo such as northwind_magazine in a JavaScript call will create a different catalog from northwind-magazine in PHP.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Internationalize PHP-rendered text
Every user-facing PHP string should be wrapped in a WordPress i18n function. Select the function according to whether the string is returned or echoed, whether it needs context, and whether it changes with a count.
| Need | Function | Example |
|---|---|---|
| Return a singular string | __() |
$label = __( 'Read more', 'northwind-magazine' ); |
| Echo a singular string | _e() |
_e( 'Skip to content', 'northwind-magazine' ); |
| Return a string with translator context | _x() |
$verb = _x( 'Archive', 'verb', 'northwind-magazine' ); |
| Echo with context | _ex() |
_ex( 'Post', 'noun', 'northwind-magazine' ); |
| Choose singular or plural | _n() |
$text = _n( '%s comment', '%s comments', $count, 'northwind-magazine' ); |
| Plural with context | _nx() |
$text = _nx( '%s result', '%s results', $count, 'search result count', 'northwind-magazine' ); |
Use escaping variants at the output boundary. For plain text in an HTML element, esc_html__() returns a translated and HTML-escaped string; esc_html_e() echoes it. For an attribute, use esc_attr__() or esc_attr_e(). If a translated value is intentionally allowed to contain safe markup, use the appropriate escaping and sanitization strategy rather than printing it raw.
<h2><?php esc_html_e( 'Latest stories', 'northwind-magazine' ); ?></h2>
<a href="<?php echo esc_url( $url ); ?>"
aria-label="<?php echo esc_attr__( 'Read the full story', 'northwind-magazine' ); ?>">
<?php esc_html_e( 'Read more', 'northwind-magazine' ); ?>
</a>
Do not assemble sentences by concatenating separately translated fragments. Word order and grammar differ across languages. Prefer a complete translatable sentence and a placeholder:
Rank #2
- Used Book in Good Condition
$message = sprintf(
/* translators: %s is the author's display name. */
__( 'Written by %s', 'northwind-magazine' ),
$author_name
);
The translator comment is not displayed to visitors; it explains a placeholder or technical constraint in the catalog. Keep placeholders meaningful and pass escaped values when they are inserted into HTML.
Recommended Free Tools
Translate block-theme templates and theme settings safely
Site Editor templates are normally stored as HTML containing block comments. A literal string in a static template is not automatically a PHP translation call. Treat authored copy in those files as a design decision: if it must be translated independently of imported content, put the copy in a location where WordPress can invoke an i18n function, usually a PHP-rendered pattern or another PHP component.
Do not translate machine values in theme.json, block names, style variation identifiers, or CSS selectors. User-facing labels that WordPress explicitly exposes for translation should use the mechanism documented for that setting; otherwise, keep the value stable and provide a translatable PHP or JavaScript label alongside it. This separation prevents a translated label from breaking a block’s serialization or style lookup.
Internationalize editor-side JavaScript
Code running in the editor is a separate execution context from PHP. Import translation functions from @wordpress/i18n, pass the same text domain, and ensure the script declares wp-i18n as a dependency.
import { __, _x, _n } from '@wordpress/i18n';
const title = __( 'Featured stories', 'northwind-magazine' );
const alignment = _x( 'Wide', 'layout option', 'northwind-magazine' );
const summary = _n(
'%d story selected',
'%d stories selected',
selectedCount,
'northwind-magazine'
);
If a build process produces a bundled file, the resulting registered WordPress script still needs the i18n dependency. In PHP, register or enqueue the script with wp-i18n among its dependencies, then call wp_set_script_translations() for that registered handle before the script runs:
wp_register_script(
'northwind-editor',
get_theme_file_uri( 'build/editor.js' ),
array( 'wp-i18n', 'wp-blocks', 'wp-element' ),
'1.0.0',
true
);
wp_set_script_translations(
'northwind-editor',
'northwind-magazine'
);
wp_enqueue_script( 'northwind-editor' );
The optional third argument to wp_set_script_translations() is a path to a directory containing custom JSON files. Use it when the files are not in WordPress’s standard language-pack location. The handle passed to the function must be the same handle used during registration; otherwise WordPress cannot associate the catalog with the script.
Make pattern-authored copy translatable
Patterns are a primary place where block themes ship visible copy: hero headlines, card titles, newsletter prompts, and calls to action. A static pattern file made solely of block markup cannot call PHP translation functions. When that copy needs translation, use a PHP pattern file so the returned markup can be assembled with i18n calls.
<?php
/**
* Title: Featured stories
* Slug: northwind-magazine/featured-stories
* Categories: featured
*/
$title = esc_html__( 'Featured stories', 'northwind-magazine' );
$button = esc_html__( 'View all stories', 'northwind-magazine' );
return '<!-- wp:group {"layout":{"type":"constrained"}} -->'
. '<div class="wp-block-group">'
. '<!-- wp:heading --><h2>' . $title . '</h2><!-- /wp:heading -->'
. '<!-- wp:buttons --><div class="wp-block-buttons">'
. '<!-- wp:button --><div class="wp-block-button"><a class="wp-block-button__link">'
. $button
. '</a></div><!-- /wp:button -->'
. '</div><!-- /wp:buttons -->'
. '</div><!-- /wp:group -->';
Keep the block comment structure valid and escape values for their destination. If the copy is intended to become editable post content after insertion, document that behavior: a translation supplied at pattern-render time does not automatically rewrite content a user has already saved in the editor.
Extract strings and create translation files with WP-CLI
Extraction creates a POT template containing the translatable source strings. From the project directory, run:
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchwp i18n make-pot ./ languages/your-theme.pot
Replace languages/your-theme.pot with the actual destination in your theme. Review the generated catalog for the expected text domain, translator comments, contexts, and plural entries. Missing strings usually mean the source was hardcoded, used the wrong domain, or was written in a construct the extractor does not recognize.
Rank #4
After translators provide PO files, convert them to the JED 1.x JSON format consumed by JavaScript:
wp i18n make-json languages/
You can pass a more specific source or destination path when your project layout requires it. The important distinction is that make-pot extracts source strings, while make-json converts translated PO data for JavaScript loading; neither command translates text by itself.
JavaScript JSON filenames follow WordPress’s documented patterns, including domain-locale-handle.json and domain-locale-path-hash.json. The exact name depends on whether WordPress identifies the script by its registered handle or by the script path. Do not rename generated files casually: a correct catalog in the wrong filename is equivalent to a missing catalog.
Choose where translation files live
| Delivery model | What you ship | How loading works | Typical risk |
|---|---|---|---|
| Theme-directory language pack | Theme code with a matching slug and text domain | WordPress can download and load language packs into its language directories when the theme is distributed through the WordPress.org directory. | A mismatched slug or domain prevents the expected catalog from being selected. |
| Self-shipped PHP files | MO/PO files in the theme’s language directory | Use the documented theme language directory and loading functions; declare Domain Path when it is custom. |
The file path, locale, or domain does not match the call. |
| Self-shipped JavaScript files | JED 1.x JSON files generated by wp i18n make-json |
Call wp_set_script_translations(); pass the optional custom directory when files are outside the standard location. |
The handle, path hash, filename, or wp-i18n dependency is wrong. |
For a custom theme language directory, a PHP theme can load its domain explicitly:
add_action( 'after_setup_theme', function () {
load_theme_textdomain(
'northwind-magazine',
get_theme_file_path( 'languages' )
);
} );
Use the directory that actually contains the files, and keep locale filenames in the format expected by WordPress. A language pack installed under wp-content/languages is a different delivery path from a catalog bundled inside the theme; do not assume one location will be searched as if it were the other.
Best Value
A complete implementation workflow
- Record the slug and domain. Confirm the theme directory name, set the matching lowercase kebab-case
Text Domain, and addDomain Pathif using a custom language directory. - Inventory visible strings. Search PHP, pattern files, JavaScript, block attributes, editor controls, notices, labels, accessibility text, and default content. Mark each item as singular, plural, or context-sensitive.
- Wrap PHP strings. Use
__(),_e(),_x(),_n(), or the appropriate escaping variant. Add translator comments for placeholders and avoid sentence fragments. - Refactor patterns where necessary. Move authored copy from static markup into a PHP pattern when it needs a translation function. Preserve valid block markup and escape inserted values.
- Wrap JavaScript strings. Import from
@wordpress/i18n, pass the identical domain, and addwp-i18nto the registered script’s dependencies. - Extract a POT file. Run
wp i18n make-potagainst the theme root and inspect the catalog for omissions, context, and plural forms. - Generate JavaScript JSON. After PO translations exist, run
wp i18n make-jsonand keep the generated naming convention unchanged. - Wire runtime loading. Use automatic language-pack delivery where applicable. For custom files, load the theme text domain and pass the custom JSON directory to
wp_set_script_translations()when needed. - Test a real locale. Change the site’s language, open the front end and Site Editor, insert the relevant patterns, and exercise every custom editor control and message.
- Repeat after edits. Re-run extraction whenever strings, contexts, plural rules, handles, or paths change; stale catalogs can make a correct source change appear untranslated.
Test before shipping
Testing only the default English site proves that the fallback string renders; it does not prove that a catalog is found. Create a test site with a target locale selected under the WordPress language setting, then check both visitor-facing and editor-facing surfaces.
- Open a page using every template part and pattern containing authored copy.
- Insert a pattern after switching the Site Editor locale and verify its heading, button, and description.
- Open custom block controls, notices, placeholders, tooltips, and accessibility labels in the editor.
- Exercise zero, one, and multiple values for each pluralized message. Languages differ in plural rules, so testing only a count of one is insufficient.
- Check strings with context, such as a word used as both a noun and a verb.
- Inspect the browser’s network and console output if JavaScript remains in the source language; a 404 for a JSON catalog or a missing dependency points to loading rather than translation quality.
- Test a clean installation that receives a directory language pack separately from a development installation using bundled files.
Troubleshoot untranslated strings
The PHP string never appears in the catalog
Confirm it is genuinely user-facing and wrapped in a recognized i18n call. Check that the domain argument exactly matches the theme slug and that extraction was run from the directory containing the source file. Dynamic string construction, such as concatenating fragments, can prevent useful extraction and produces poor translations even when it is extracted.
Free tools Windows power users keep installed
One-click scans. No signup required.
The catalog exists but PHP still shows English
Verify the site’s locale, the catalog’s locale filename, and the location from which the theme text domain is loaded. If the theme is not using automatic directory language packs, call load_theme_textdomain() with the real language directory and ensure the domain is identical to the one in the file header and function calls.
PHP is translated but editor JavaScript is not
Check all four links in the chain: the JavaScript import, the domain argument, the registered script’s wp-i18n dependency, and the wp_set_script_translations() handle. Then check the generated JSON filename. A catalog generated for a different handle or path hash will not be selected for the script that runs.
A translated pattern appears in the editor but not after saving
Determine whether the pattern’s copy is rendered dynamically or was inserted into post content. Once a user inserts and saves a pattern, its text is content in that post; changing a PHP translation later does not rewrite already-saved content. For dynamic labels, keep the translation call in the PHP-rendered component.
Placeholders or markup break after translation
Use a complete sentence with named translator comments, escape values at the output boundary, and avoid allowing arbitrary HTML inside a translated string. Context and comments tell translators what a placeholder represents without forcing them to infer it from code.
Common design decisions and trade-offs
| Decision | Use this when | Trade-off |
|---|---|---|
| Static block markup | The text is intended as starter content that users edit after insertion. | It is simple, but it has no PHP translation hook and saved copies do not update when translations change. |
| PHP pattern | Authored pattern copy must be generated through WordPress i18n functions. | It adds PHP and escaping work, but supports extraction and runtime translation. |
| Singular function | The message never changes with a number. | Simple, but unsuitable for count-dependent grammar. |
| Plural function | A count changes the message. | Requires both forms and testing with relevant counts and locales. |
| Context function | The same source word has different meanings. | Adds catalog entries, but prevents translators from guessing the intended meaning. |
| Directory language packs | The theme is distributed through a channel that supplies WordPress language packs. | Minimal shipping work, but it depends on correct slug metadata and pack availability. |
| Self-shipped files | The theme controls its own translation delivery or is distributed privately. | Predictable packaging, but you must maintain paths, filenames, loading calls, and updates. |
Release checklist
- Identity: the directory slug,
Text Domain, PHP calls, JavaScript calls, and catalog headers all agree. - Coverage: no visitor-facing or editor-facing string remains hardcoded without a deliberate reason.
- Correct API: PHP uses WordPress PHP i18n functions; JavaScript imports from
@wordpress/i18n. - Safety: translated text is escaped for HTML, attributes, URLs, or other destinations.
- Catalogs: POT extraction includes contexts and plurals; PO data has been converted to JED JSON for scripts.
- Loading: custom language directories, script handles, dependencies, and JSON names match the files shipped.
- Verification: a non-default locale has been tested on both the front end and in the Site Editor, including plural counts and pattern insertion.
Bottom line
Internationalizing a block theme is a coordinated build step, not a single setting. Match the text domain to the slug, wrap PHP and JavaScript strings with their respective APIs, move translatable pattern copy into PHP when static markup cannot provide a hook, generate POT and JED JSON files with WP-CLI, and test a real locale before release. With those pieces aligned, WordPress can load the right language pack or custom catalog without changing the theme’s block structure.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

