Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.
Lessons
A translation-ready theme wraps every visible string in a WordPress translation function with its text domain, so the theme can be shown in any language without changing its code.
Around half of all WordPress sites are not in English. Internationalisation — "i18n" — is not optional for a theme you plan to share.
<?php
function gdc_starter_setup() {
load_theme_textdomain( 'gdc-starter', get_template_directory() . '/languages' );
// ...other setup.
}
add_action( 'after_setup_theme', 'gdc_starter_setup' );Themes hosted on WordPress.org get translations automatically from translate.wordpress.org, but loading the folder lets you ship translations with the theme too.
| Function | Use for |
|---|---|
__( $text, $domain ) | Returns a translated string. |
esc_html__() / esc_attr__() | Returns translated and escaped — prefer these. |
esc_html_e() / esc_attr_e() | Prints translated and escaped. |
_x( $text, $context, $domain ) | Same word, different meaning: "Post" (verb) versus "Post" (noun). |
_n( $single, $plural, $number, $domain ) | Plurals. |
Watch out: The text domain must be a literal string, never a variable or constant. Translation tools scan your code as text and cannot follow GDC_DOMAIN to its value.<?php
// Wrong: word order is fixed in English. Many languages put the name first.
echo esc_html__( 'Posted by ', 'gdc-starter' ) . esc_html( get_the_author() );
// Right: the whole sentence is one translatable string with a placeholder.
printf(
/* translators: %s: post author name */
esc_html__( 'Posted by %s', 'gdc-starter' ),
esc_html( get_the_author() )
);The /* translators: */ comment directly above appears in translation tools, telling the translator what %s will become. With two or more placeholders, number them — %1$s, %2$s — so translators can reorder them.
<?php
$count = (int) get_comments_number();
printf(
/* translators: %s: number of comments */
esc_html( _n( '%s comment', '%s comments', $count, 'gdc-starter' ) ),
esc_html( number_format_i18n( $count ) )
);Never write if ( 1 === $count ) yourself. Some languages have three or more plural forms — Polish uses different words for 2 and 5 — and _n() handles all of them.
Translators work from a .pot template listing every string. Generate it with WP-CLI from the theme folder:
wp i18n make-pot . languages/gdc-starter.pot --domain=gdc-starterTranslators open the .pot in a tool such as Poedit, translate it, and produce a .po and compiled .mo file named after the locale — gdc-starter-fr_FR.mo for French — which go in languages/.
Arabic and Hebrew read right to left. Add an rtl.css file and WordPress loads it automatically after style.css when the site language is RTL. Better still, write CSS with logical properties — margin-inline-start instead of margin-left — and most of the layout flips by itself.
gdc-starter domain.printf() and a translators comment..pot file and open it to check every string appears.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.