Internationalization
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages. |
Internationalization (i18n) is the developer’s part of translating software: writing code so that every user-facing
string can be replaced by a translation, and dates, numbers and text direction follow the reader’s language.
Localization (l10n) is the translators' part: producing the translation files for one locale (es_ES, pt_BR,
ar). This page, for plugin and theme developers, applies both to the lumen-catalog plugin of the Lumen Press
site, translated into Spanish for a few strings. Running a site in another language is covered in
Settings Screens and
Updates and Auto-Updates; child theme translation is in
Child Themes.
This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the
version to run in production), PHP 7.4 minimum and 8.3+ recommended, WP-CLI 2.12.0, @wordpress/scripts 37.0.0 with
@wordpress/i18n 7.0.0, and WordPress Coding Standards (WPCS) 3.4.1, and
checked on 2026-10-28. WordPress 7.2 is in beta and due in December 2026; nothing on this page depends on it.
Core i18n translates the strings written in code (labels, messages, e-mails, block settings), not the content editors write. A multilingual site, with one copy of each post per language, needs a multilingual plugin or a Multisite network with one site per language; both rely on internationalized code.
Two locales are active on every request. The site locale (get_locale()) comes from Settings > General > Site
Language, stored in the WPLANG option (empty for en_US). The user locale (get_user_locale(), since 4.7) comes
from the Language field of the user’s profile, stored as the locale user meta, and falls back to the site locale.
determine_locale() (5.0) decides which one a request uses: the user locale in the admin, REST requests from the editor
and AJAX, the site locale on the front end, and the installer’s choice during installation.
The text domain
Every translatable string names a text domain, the identifier of the translation catalogue it belongs to. Core uses
the default domain; a plugin or theme uses its own, which must equal its slug (the folder name): lumen-catalog
for the plugin, lumen and lumen-child for the themes. WordPress.org builds language packs per slug, and the
just-in-time loader looks for files named <domain>-<locale>, so a domain that differs from the slug is never
translated. The domain is passed as a string literal in every call, never through a variable or constant, because the
extraction tools read the source code without running it.
The main file declares it with two headers of the canonical header shown in
Plugin Fundamentals. Text Domain: lumen-catalog is
optional since 4.6 when it equals the slug, but wp i18n make-pot, Plugin Check and WPCS read it, so it is kept.
Domain Path: /languages names the folder of the bundled translation files; without it, the plugin folder is used.
WordPress looks for a domain’s files in this order and loads the first locale match: wp-content/languages/plugins/
(language packs from translate.wordpress.org), wp-content/languages/themes/, then the path registered for the domain
(the Domain Path folder). A language pack therefore wins over a bundled file with the same name.
The gettext functions
The functions take the original English string, look it up in the domain’s loaded catalogue and return the
translation, or the original when there is none. Those whose name contains _e echo instead of returning.
| Function | Use |
|---|---|
|
Returns the translation. The basic function. |
|
Echoes it. Prefer the escaping variants below for output. |
|
Returns (or echoes) it, with a context that tells two identical English strings apart. |
|
Returns the plural form that matches |
|
|
|
Register a plural pair now and translate it later, when the number is known. |
|
Translate and escape for HTML text. |
|
Translate and escape for an HTML attribute value. |
|
Formats a number with the locale’s thousands separator and decimal point. |
|
Formats a date in the site time zone, with translated month and day names. Replaces |
|
Whether a translation exists, without loading the domain. |
Placeholders and translator comments
Variable parts go in placeholders filled by sprintf() or printf(), never by concatenation: 'Published on ' . $date
gives translators two fragments, and many languages need the date elsewhere in the sentence. A string with more than one
placeholder uses numbered ones (%1$s, %2$s) so a translation can reorder them. A comment starting with
translators: on the line directly above the call (or inside it, above the string) explains each placeholder; it is
copied into the POT file and shown to translators next to the string. HTML stays out of strings where possible; when a
link sits inside a sentence, the translation keeps the tag and the URL is inserted through a placeholder.
Plurals and context
_n() is not an if ( 1 === $n ). Each language defines its own plural rule in the Plural-Forms header of its
translation file: Spanish has two forms (n != 1), Japanese one, Arabic six. The singular string still carries the
placeholder, because some languages use the "singular" form for 21, 31 and so on. When the number is unknown at the
point where the strings are declared (a post status label, a list registered on init), _n_noop() stores the pair
and translate_nooped_plural( $noop, $count, 'lumen-catalog' ) picks the form later.
_x() adds a context to identical English strings that translate differently: the catalogue has a sort "Order" and
an "Order" button, Orden and Pedir in Spanish, each a separate catalogue entry. Context also carries hints such as
post type general name, used by core’s own labels.
Escaping translations
A translation is third-party input: a translator, or a compromised language pack, can put markup or script into it.
Translated text is therefore escaped like any other output, late and for its context, with the combined functions
(esc_html__(), esc_attr__(), esc_html_x()) or with esc_html( __() ); the catalogue’s search form prints
its label with esc_html_e( 'Search the catalogue', 'lumen-catalog' ) and its placeholder attribute with
esc_attr__( 'Title, author or ISBN', 'lumen-catalog' ). When a translation must contain HTML, wp_kses() keeps
only the expected tags and the URL is escaped separately with esc_url() (see
Security for Developers).
The catalogue strings
src/Text.php of lumen-catalog holds the user-facing strings of the catalogue and shows each case: a plural with a
formatted number, reorderable placeholders, two contexts, a translated date and a sentence with a link. The class is loaded by the plugin’s Composer autoloader (see
Plugin Fundamentals):
<?php
/**
* User-facing catalogue strings.
*
* @package Lumen\Catalog
*/
namespace Lumen\Catalog;
defined( 'ABSPATH' ) || exit;
final class Text {
public static function stock( int $copies ): string {
return sprintf(
/* translators: %s: number of copies in stock. */
_n( '%s copy in stock', '%s copies in stock', $copies, 'lumen-catalog' ),
number_format_i18n( $copies )
);
}
/** "Title by Author", with numbered placeholders so translators can reorder them. */
public static function byline( string $title, string $author ): string {
return sprintf(
/* translators: 1: book title, 2: author name. */
__( '%1$s by %2$s', 'lumen-catalog' ),
$title,
$author
);
}
public static function order_labels(): array {
return array(
'sort' => _x( 'Order', 'sort order of the list', 'lumen-catalog' ),
'buy' => _x( 'Order', 'button: buy this book', 'lumen-catalog' ),
);
}
public static function published_on( int $timestamp ): string {
return sprintf(
/* translators: %s: publication date. */
__( 'Published on %s', 'lumen-catalog' ),
wp_date( get_option( 'date_format' ), $timestamp )
);
}
/** Translated HTML: allow only the link, insert the URL separately. */
public static function submissions_notice(): string {
return sprintf(
wp_kses(
/* translators: %s: URL of the submissions page. */
__( 'Read the <a href="%s">submission guidelines</a> before sending a manuscript.', 'lumen-catalog' ),
array( 'a' => array( 'href' => array() ) )
),
esc_url( home_url( '/submissions/' ) )
);
}
}
Post type labels are translated the same way, inside the init callback that registers lp_book
(_x( 'Books', 'post type general name', 'lumen-catalog' ) for name, __( 'Add New Book', 'lumen-catalog' )
for add_new_item; the full registration is in Custom Post Types and Taxonomies). The
rewrite slug stays books in every language: a translated slug changes the URLs whenever the site language changes,
and the rewrite rules are only flushed on activation.
Mistakes the tools catch
Five common mistakes, in a throwaway file Bad.php outside the plugin:
<?php
function lumen_catalog_bad_examples( string $title, int $count ): void {
echo __( 'Added %s to your wishlist', 'lumen-catalog' );
echo __( $title, 'lumen-catalog' );
echo sprintf( __( '%s and %s', 'lumen-catalog' ), $title, $title );
echo __( 'Books', 'lumen' );
echo _n( 'One book', '%d books', $count, 'lumen-catalog' );
}
The WordPress.WP.I18n sniff of WPCS 3.4.1, run as
vendor/bin/phpcs --standard=WordPress --sniffs=WordPress.WP.I18n --runtime-set text_domain lumen-catalog Bad.php,
reported seven errors on five lines: MissingTranslatorsComment (lines 3, 5 and 7), NonSingularStringLiteralText
("The $text parameter must be a single text string literal. Found: $title", line 4), UnorderedPlaceholdersText
(line 5, fixable automatically), TextDomainMismatch ("Expected 'lumen-catalog' but got 'lumen'", line 6) and
MissingSingularPlaceholder (line 7).
__( $title ) can never work: the extractor cannot know the value, so no translator ever sees it. The real
Text.php and Plugin.php passed the same sniff plus WordPress.Security.EscapeOutput with no reports. The
text_domain property belongs in the plugin’s phpcs.xml.dist, next to the prefixes shown in
Plugin Fundamentals.
Loading translations
How a plugin’s catalogue gets loaded changed several times, which is why older code is full of calls that are no longer needed:
| Version | Change |
|---|---|
Before 4.6 |
Every plugin called |
4.6 |
Just-in-time loading: the first translation call for a domain loads its file from |
6.5 |
Performant translations: |
6.7 |
|
6.8 |
Just-in-time loading extended to every plugin and theme, including those not on WordPress.org: while loading plugins, WordPress registers the |
Since lumen-catalog declares Requires at least: 6.9, it calls no loading function at all: its Domain Path
header is enough, and the first __( ..., 'lumen-catalog' ) after init loads
languages/lumen-catalog-es_ES.l10n.php. A plugin that still supports WordPress older than 6.8 and ships its own files
keeps a call, on init:
add_action(
'init',
static function () {
load_plugin_textdomain( 'lumen-catalog', false, dirname( plugin_basename( LUMEN_CATALOG_FILE ) ) . '/languages' );
}
);
Must-use plugins use load_muplugin_textdomain(), themes load_theme_textdomain().
The "triggered too early" notice
Translations loaded before the current user and locale are known come out in the wrong language (the site locale instead
of the user’s profile language) and bypass any plugin that filters the locale or the file paths. Since 6.7, WordPress
reports it. To reproduce it, a test copy of lumen-catalog.php gets one extra line at the end of the main file, which
runs while WordPress is still including plugins:
// Too early: runs while the plugin file is included.
define( 'LUMEN_CATALOG_LABEL', __( 'Search Books', 'lumen-catalog' ) );
With WP_DEBUG enabled, any request (here wp eval 'echo LUMEN_CATALOG_LABEL, "\n";') prints:
Notice: Function _load_textdomain_just_in_time was called <strong>incorrectly</strong>. Translation loading for the
<code>lumen-catalog</code> domain was triggered too early. This is usually an indicator for some code in the plugin or
theme running too early. Translations should be loaded at the <code>init</code> action or later. Please see
<a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/">Debugging in WordPress</a> for
more information. (This message was added in version 6.7.0.) in /var/www/html/wp-includes/functions.php on line 6260
Buscar libros
The string was still translated, in the site locale, so the notice is easy to dismiss. On WordPress 7.1.3 it
appeared even with the site in English, because since 6.8 every active plugin with a Text Domain header has a
registered path, and the check runs before looking for a file. The usual causes are:
-
__()at the top level of a file, in adefine(), a class constant or a property default, or in a constructor called while the plugin is included. -
Registering post types, taxonomies, blocks or settings on
plugins_loadedinstead ofinit. -
get_plugin_data( $file )with the default$translate = true; passfalsefor the third argument when only the version is needed. -
load_plugin_textdomain()itself onplugins_loadedis harmless since 6.7, but any translation call in the same callback is not.
The fix is to move the code into a callback on init (or later), or to translate at the point of output.
Translation files
| File | Role |
|---|---|
|
Template: every source string, with references, translator comments and flags, and empty translations. Generated from the code; never translated directly. |
|
The Spanish translation, edited by translators (in Poedit, on translate.wordpress.org or by hand). Not loaded by WordPress. |
|
Binary compiled from the PO. Loaded when no PHP file exists. |
|
PHP array compiled from the PO (6.5+). Preferred over the MO: faster and cacheable by OPcache. |
|
JavaScript strings of one script, in Jed format, named after the MD5 of the script’s relative path. |
Generating the POT
wp i18n make-pot scans PHP, JavaScript, Blade, block.json and theme.json files and the plugin header. It
defaults the domain to the slug and silently skips strings of other domains. Run from the plugin folder (in the cli
container of the stack in
Database Schema, with the plugin folder mounted):
wp i18n make-pot . languages/lumen-catalog.pot --exclude=src/admin --headers='{"Report-Msgid-Bugs-To":"https://lumenpress.example/support/"}'
Plugin file detected.
Success: POT file successfully generated.
node_modules, vendor and .git are excluded by default. --exclude=src/admin leaves out the JavaScript sources,
so that the JavaScript strings are referenced from the built file that WordPress actually loads (see
JavaScript translations). The entry for the script’s plural, with its translator comment and format flag:
#. translators: %d: number of books listed on this screen.
#: build/admin/catalog-screen.js:2
#, js-format
msgid "%d book on this page"
msgid_plural "%d books on this page"
msgstr[0] ""
msgstr[1] ""
The audit built into make-pot warns about placeholders without a translator comment and unordered placeholders
(--skip-audit turns it off); with Bad.php copied into the plugin for a run, it printed
Warning: The string "%s and %s" contains placeholders but has no "translators:" comment to clarify their meaning.
followed by Warning: Multiple placeholders should be ordered. It does not see __( $title ) or the wrong domain,
which is why WPCS runs as well.
A Spanish translation
The PO file starts as a copy of the POT with the language headers filled in; the Plural-Forms line must match the
language. Translators then fill msgstr:
"Language-Team: Spanish (Spain)\n"
"Language: es_ES\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
#: src/Text.php:33
msgctxt "sort order of the list"
msgid "Order"
msgstr "Orden"
#: src/Text.php:34
msgctxt "button: buy this book"
msgid "Order"
msgstr "Pedir"
#. translators: %s: number of copies in stock.
#: src/Text.php:16
#, php-format
msgid "%s copy in stock"
msgid_plural "%s copies in stock"
msgstr[0] "%s ejemplar en stock"
msgstr[1] "%s ejemplares en stock"
Source: Plugin Handbook: Localization
When the code changes, wp i18n update-po languages/lumen-catalog.pot languages/ merges new strings into existing PO
files. The three compiled formats come from the PO:
cd languages
wp i18n make-mo lumen-catalog-es_ES.po
wp i18n make-php lumen-catalog-es_ES.po
wp i18n make-json lumen-catalog-es_ES.po --no-purge
ls -1
Each command printed Success: Created 1 file., leaving:
lumen-catalog-es_ES-ef813ae35541dd4fb413a45f5ec676cf.json
lumen-catalog-es_ES.l10n.php
lumen-catalog-es_ES.mo
lumen-catalog-es_ES.po
lumen-catalog.pot
make-json removes the JavaScript strings from the PO by default; --no-purge keeps the PO complete, so it can be
compiled again. The .l10n.php file is a plain return [ ... ] of the headers and messages, with context and string
joined by an invisible \x04 byte and plural forms by \0. With the MO file removed, the strings were still
translated from the PHP file alone; a site that needs MO files only returns mo from translation_file_format.
Language packs from translate.wordpress.org
A WordPress.org plugin is translated by the community on translate.wordpress.org, and WordPress downloads each
language pack into wp-content/languages/plugins/ with the other updates (translations update automatically by
default; wp language plugin install <slug> <locale> installs one by hand). lumen-catalog is private, so it ships its
own languages/ folder; a language pack for the same domain would take precedence.
Switching the locale
The site language is changed on Settings > General > Site Language (which installs the core language pack when the user may install languages) or with WP-CLI:
wp language core install es_ES
wp site switch-language es_ES
The commands printed Success: Installed 1 of 1 languages. and Success: Language activated., and the WPLANG
option then held es_ES. wp site switch-language replaces the deprecated wp language core activate. Code switches temporarily with
switch_to_locale() and goes back with restore_previous_locale(); loaded domains are reloaded for the new locale. A demo script,
demo.php in the plugin folder, prints the catalogue strings in the site locale and in English:
<?php
// demo.php, run with: wp eval-file demo.php
use Lumen\Catalog\Text;
printf( "Locale: %s\n", determine_locale() );
echo Text::stock( 1 ), ' | ', Text::stock( 1250 ), "\n";
echo Text::byline( 'La casa del faro', 'Inés Durán' ), "\n";
echo implode( ' / ', Text::order_labels() ), "\n";
echo Text::published_on( mktime( 12, 0, 0, 3, 14, 2026 ) ), "\n";
echo Text::submissions_notice(), "\n";
switch_to_locale( 'en_US' );
printf( "Locale: %s\n", determine_locale() );
echo Text::stock( 1250 ), ' | ', Text::published_on( mktime( 12, 0, 0, 3, 14, 2026 ) ), "\n";
restore_previous_locale();
Locale: es_ES
1 ejemplar en stock | 1.250 ejemplares en stock
La casa del faro, de Inés Durán
Orden / Pedir
Publicado el marzo 14, 2026
Lee las <a href="https://lumenpress.test/submissions/">normas de envío</a> antes de enviar un manuscrito.
Locale: en_US
1,250 copies in stock | Published on March 14, 2026
number_format_i18n() used the Spanish thousands separator, and wp_date() translated the month. The odd
"marzo 14, 2026" comes from the date_format option, F j, Y, which the installer stored in English; it is not
translated when the site language changes later. A site installed in Spanish gets core’s translated default,
j \d\e F \d\e Y ("14 de marzo de 2026"); an existing site changes it on Settings > General.
Messages in the recipient’s language
Code that writes to one user (e-mails, notifications) uses that user’s locale with switch_to_user_locale() (6.2),
which also makes determine_locale() and get_user_locale() follow. Core already does this for its own e-mails, and
since 6.7 also for messages to the admin_email address when it belongs to a user. With the site in Spanish and
maria.editor set to English in her profile (wp user meta update maria.editor locale en_US):
<?php
// user-locale-demo.php, run with: wp eval-file user-locale-demo.php
use Lumen\Catalog\Text;
$editor = get_user_by( 'login', 'maria.editor' );
switch_to_user_locale( $editor->ID );
$subject = sprintf(
/* translators: %s: book title. */
__( 'New book awaiting review: %s', 'lumen-catalog' ),
'La casa del faro'
);
echo $subject, ' | ', Text::stock( 3 ), "\n";
restore_previous_locale();
New book awaiting review: La casa del faro | 3 copies in stock
JavaScript translations
JavaScript uses the same functions from the @wordpress/i18n package: __(), _x(), _n(), _nx(),
sprintf() and isRTL(). @wordpress/scripts treats @wordpress/* imports as externals: the build reads them from
the global wp.i18n and lists wp-i18n in the generated .asset.php file, so the package is never bundled. The admin
script of the Books list screen, src/admin/catalog-screen.js:
// Adds a stock summary under the Books list screen heading.
import { __, _n, sprintf, isRTL } from '@wordpress/i18n';
import domReady from '@wordpress/dom-ready';
import './catalog-screen.css';
domReady( () => {
const heading = document.querySelector( '.wrap .wp-heading-inline' );
if ( ! heading ) {
return;
}
const rows = document.querySelectorAll(
'#the-list tr.type-lp_book'
).length;
const summary = document.createElement( 'p' );
summary.className = 'lp-catalog-summary';
summary.textContent = sprintf(
/* translators: %d: number of books listed on this screen. */
_n(
'%d book on this page',
'%d books on this page',
rows,
'lumen-catalog'
),
rows
);
summary.title = isRTL()
? __( 'Right-to-left layout', 'lumen-catalog' )
: __( 'Left-to-right layout', 'lumen-catalog' );
heading.after( summary );
} );
package.json pins @wordpress/scripts 37.0.0 as a development dependency (with @wordpress/i18n 7.0.0 and
@wordpress/dom-ready 5.0.0 for the editor’s import resolution) and defines
"build": "wp-scripts build src/admin/catalog-screen.js --output-path=build/admin".
npm run build produced catalog-screen.js, catalog-screen.css, catalog-screen-rtl.css and
catalog-screen.asset.php (dependencies wp-dom-ready and wp-i18n), and npm run lint:js reported nothing. The
minifier keeps the __, _n calls and the translator comment, which is what lets make-pot read the built file.
The script is enqueued on the Books list only (edit.php with the lp_book screen), from a callback on
admin_enqueue_scripts, and wp_set_script_translations() (5.0) tells WordPress to print the script’s translations
before it. The relevant lines of Plugin::enqueue_admin():
$asset = require plugin_dir_path( LUMEN_CATALOG_FILE ) . 'build/admin/catalog-screen.asset.php';
wp_enqueue_script(
'lumen-catalog-screen',
plugins_url( 'build/admin/catalog-screen.js', LUMEN_CATALOG_FILE ),
$asset['dependencies'],
$asset['version'],
array( 'in_footer' => true )
);
wp_set_script_translations( 'lumen-catalog-screen', 'lumen-catalog', plugin_dir_path( LUMEN_CATALOG_FILE ) . 'languages' );
wp_enqueue_style( 'lumen-catalog-admin', plugins_url( 'build/admin/catalog-screen.css', LUMEN_CATALOG_FILE ), array(), $asset['version'] );
wp_style_add_data( 'lumen-catalog-admin', 'rtl', 'replace' );
load_script_textdomain() looks for
<domain>-<locale>-<handle>.json first, then <domain>-<locale>-<md5>.json, where the hash is the MD5 of the script’s
path relative to the plugin folder. ef813ae35541dd4fb413a45f5ec676cf is the MD5 of build/admin/catalog-screen.js:
had the POT referenced src/admin/catalog-screen.js, make-json would have produced a file for the source path, and
the screen would have stayed in English with no error. A build that bundles several sources into one file maps them with
make-json --use-map=build/map.json. On the Spanish site,
wp_scripts()->print_translations( 'lumen-catalog-screen', false ) returned the inline script WordPress prints before
the file: a call to wp.i18n.setLocaleData() for the lumen-catalog domain with the JSON content, including
"%d book on this page":["%d libro en esta p\u00e1gina","%d libros en esta p\u00e1gina"].
Blocks need no call: when block.json has "textdomain": "lumen-catalog", register_block_type() calls
wp_set_script_translations() for the block’s editor and view scripts that depend on wp-i18n, and translates the
title, description, keywords and style labels of block.json on the server; make-pot extracts those fields
too. Script modules, used by Interactivity API view scripts, gained wp_set_script_module_translations() in 7.0 for
modules with their own domain. Block metadata and registration are covered in
block.json and Registration.
Right-to-left languages
Arabic, Hebrew, Persian and Urdu are written right to left. is_rtl() returns true when the active locale’s core
translation of the string ltr (context text direction) is rtl, so it depends on the core language pack, not on
the plugin. In an RTL locale WordPress sets dir="rtl" on <html> (language_attributes()), loads the -rtl variant
of core’s styles, and isRTL() returns true in JavaScript.
A plugin stylesheet needs its own RTL variant. @wordpress/scripts generates it with RTLCSS
(catalog-screen-rtl.css swaps left and right):
/* src/admin/catalog-screen.css */
.lp-catalog-summary {
margin-left: 0;
padding-left: 12px;
border-left: 4px solid #b3261e;
}
/* build/admin/catalog-screen-rtl.css, generated */
.lp-catalog-summary{border-right:4px solid #b3261e;margin-right:0;padding-right:12px}
wp_style_add_data( $handle, 'rtl', 'replace' ) makes WordPress load <file>-rtl.css instead of <file>.css when
is_rtl() is true; the value true loads both. Block styles registered from block.json get this automatically when
the -rtl.css file exists, and classic themes may ship an rtl.css that locale_stylesheet() adds. CSS logical
properties (margin-inline-start, padding-inline-end, border-inline-start) avoid most of the swapping and suit new
block themes. With the Arabic core pack installed (wp language core install ar) and switch_to_locale( 'ar' ),
is_rtl() returned true and the handle printed a lumen-catalog-admin-rtl-css tag for catalog-screen-rtl.css;
under es_ES it printed catalog-screen.css.
Pitfalls and corrections to older books
| Pitfall or outdated advice | Instead |
|---|---|
|
Not needed for WordPress.org plugins since 4.6, nor for any plugin with |
Translating strings at include time, in constants or on |
The 6.7 notice; strings come out in the site locale. Translate inside |
A text domain that differs from the slug, or set in a variable or constant |
Use the slug as a literal string in every call; otherwise neither language packs nor the extractor find the strings. |
|
Plural forms depend on the language; keep the placeholder in both forms. |
Echoing translations unescaped ( |
|
Shipping only |
Add |
JavaScript strings passed from PHP with |
|
WordPress All-in-One For Dummies covers no development. Pro WordPress (written around 6.2) and Ultimate WordPress Handbook (6.4 to 6.6) predate the 6.7 notice and the 6.8 loader, so any translation-loading code modelled on them follows the earlier rules in the left column.