Build WordPress plugins from scratch: actions and filters, shortcodes, settings pages, custom post types, meta boxes, AJAX, REST endpoints, custom tables, cron and blocks.
Lessons
Internationalising a plugin means wrapping every user-facing string in a translation function with your text domain, so the plugin can be translated into any language — for PHP strings, JavaScript strings and block metadata alike.
| Function | Use |
|---|---|
__() | Return a translated string |
esc_html__() / esc_attr__() | Return, translated and escaped |
esc_html_e() / esc_attr_e() | Print, translated and escaped |
_x() | Disambiguate: "Book" (noun) vs "Book" (verb) |
_n() | Singular and plural forms |
<?php
printf(
/* translators: 1: number of books, 2: genre name */
esc_html( _n( '%1$s book in %2$s', '%1$s books in %2$s', $count, 'gdc-books' ) ),
esc_html( number_format_i18n( $count ) ),
esc_html( $genre_name )
);Watch out: The text domain must be a literal string — 'gdc-books' — never a variable or a constant. Translation tools read your code as text and cannot resolve a constant.For plugins hosted on WordPress.org, translations from translate.wordpress.org load automatically as long as the Text Domain header matches the plugin slug. To also ship your own .mo files, load them on init:
<?php
function gdc_books_load_textdomain() {
load_plugin_textdomain( 'gdc-books', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'init', 'gdc_books_load_textdomain' );Tip: Do not call translation functions before init — for example in a constant definition at the top of the main file. Since WordPress 6.7 that triggers a "translation loading was triggered too early" notice, because the user's language is not known yet.import { __, sprintf, _n } from '@wordpress/i18n';
const label = __( 'Add to favourites', 'gdc-books' );
const summary = sprintf(
/* translators: %d: number of books */
_n( '%d book saved', '%d books saved', count, 'gdc-books' ),
count
);Then tell WordPress where the JavaScript translations live, right after enqueuing the script:
<?php
wp_set_script_translations( 'gdc-books-favourites', 'gdc-books', GDC_BOOKS_PATH . 'languages' );Blocks registered from block.json with a textdomain field get this automatically.
# From the plugin folder, with WP-CLI installed
wp i18n make-pot . languages/gdc-books.pot --exclude=node_modules,vendor,blocks/book-list/src
# After translators deliver .po files, build the JS translation files
wp i18n make-json languages --no-purgegdc-books.pot and check that strings from PHP, JavaScript and block.json all appear.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.