Classic theme i18n textdomain
Skill Lonsdale201/wp-agent-skills/theme-development/classic-theme-i18n-textdomain
Build or audit internationalization in classic WordPress themes on WP 7.0. Covers `style.css` `Text Domain` and `Domain Path`, slug-matching domains, `load_theme_textdomain()` and `load_child_theme_textdomain()` on `after_setup_theme`, WP 6.7+ early translation warnings, escaped translation functions, `_x`, `_n`, translator comments, placeholders, JavaScript translation setup, and common mistakes such as variable text domains, string concatenation, missing domains, raw translated output, or wrongly named theme `.mo` files.From its SKILL.md
npx -y skills add Lonsdale201/wp-agent-skills --skill classic-theme-i18n-textdomainAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.0 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Classic Theme Internationalization and Text Domain
Use this when adding or reviewing translation readiness in a classic PHP theme: text domain headers, PHP strings, plural strings, context, translator comments, local .mo files, and JavaScript translations.
When to Use This Skill
- Creating or reviewing a theme
style.cssheader. - Adding visible text to templates,
functions.php, Customizer controls, menus, widgets, or comments. - Loading bundled theme translations.
- Fixing WP 6.7+ "translations loaded too early" notices.
- Auditing text domains before release.
Text Domain Header
Set the text domain in style.css.
/*
Theme Name: My Theme
Text Domain: my-theme
Domain Path: /languages
*/
Rules:
- The text domain should match the theme slug.
- Use lowercase kebab-case, not underscores.
- Use the same literal domain in every translation call.
Domain Pathis relative to the theme root and starts with/.- Use
/languagesunless the project has a clear reason for another directory.
Loading Translations
For themes distributed through WordPress.org language packs, WordPress can load translations from wp-content/languages/themes/.
If the theme bundles its own translations, register the path on after_setup_theme.
add_action( 'after_setup_theme', 'mytheme_load_textdomain' );
function mytheme_load_textdomain() {
load_theme_textdomain(
'my-theme',
get_template_directory() . '/languages'
);
}
For a child theme:
add_action( 'after_setup_theme', 'mytheme_child_load_textdomain' );
function mytheme_child_load_textdomain() {
load_child_theme_textdomain(
'my-theme',
get_stylesheet_directory() . '/languages'
);
}
Rules:
- Load theme translations no earlier than
after_setup_theme. - Do not translate strings at file load time before
after_setup_theme. - WP 6.7+ warns when just-in-time translation loading is triggered too early.
- Theme-bundled
.mofiles are named by locale, for examplede_DE.mo. - Language-pack
.mofiles underwp-content/languages/themes/are namedmy-theme-de_DE.mo.
Escaped Translation Functions
Prefer translate-and-escape helpers at output time.
esc_html_e( 'Read more', 'my-theme' );
printf(
'<a href="%1$s">%2$s</a>',
esc_url( get_permalink() ),
esc_html__( 'Continue reading', 'my-theme' )
);
Use by context:
| Output context | Function |
|---|---|
| HTML text | esc_html__() / esc_html_e() |
| Attribute | esc_attr__() / esc_attr_e() |
| URL | Translate label separately; escape URL with esc_url() |
| Controlled inline HTML | wp_kses() after translation |
Rules:
- Do not echo
__()directly into HTML unless it is escaped afterward. - Do not use
esc_html__()for attribute values; useesc_attr__(). - Keep URLs out of translatable strings when possible.
Context, Plurals, and Placeholders
Use context when the same English word has different meanings.
echo esc_html_x( 'Post', 'noun: blog post', 'my-theme' );
echo esc_html_x( 'Post', 'verb: submit form', 'my-theme' );
Use plural functions for counts.
$count = get_comments_number();
printf(
esc_html(
_n(
'%s comment',
'%s comments',
$count,
'my-theme'
)
),
esc_html( number_format_i18n( $count ) )
);
Use numbered placeholders when translators may reorder words.
printf(
/* translators: 1: post title, 2: author name. */
esc_html__( '%1$s by %2$s', 'my-theme' ),
esc_html( get_the_title() ),
esc_html( get_the_author() )
);
Rules:
- Add translator comments immediately before strings with placeholders.
- Do not concatenate sentence fragments.
- Do not translate dynamic values such as post titles, usernames, or option values.
- Use
number_format_i18n()for numbers shown to users.
JavaScript Strings
For WordPress-registered scripts that use @wordpress/i18n, set script translations.
wp_enqueue_script(
'mytheme-navigation',
get_theme_file_uri( 'assets/js/navigation.js' ),
array( 'wp-i18n' ),
mytheme_asset_version( 'assets/js/navigation.js' ),
array( 'in_footer' => true )
);
wp_set_script_translations(
'mytheme-navigation',
'my-theme',
get_theme_file_path( 'languages' )
);
Rules:
- Register script translations after registering/enqueueing the script handle.
- JavaScript translations need the same text domain.
- Do not pass already-translated PHP strings into JS just to avoid JS i18n.
Text Domain Audit
Search patterns:
rg "__\\(|_e\\(|_x\\(|_n\\(|esc_html__|esc_attr__|esc_html_e|esc_attr_e" .
Check:
- Every theme string has the literal theme text domain.
- No
$text_domainvariable is used in translation calls. - No plugin text domain is used in theme-owned strings.
- No missing second argument.
- No hardcoded visible English strings remain in templates.
Review Checklist
style.csshasText Domainmatching the theme slug.Domain Pathmatches bundled translation location.- Translation loading runs on
after_setup_themewhen needed. - No translation calls run too early at file load time.
- Output uses escaped translation functions by context.
- Plural strings use
_n()or related helpers. - Ambiguous strings use
_x()/esc_html_x(). - Placeholders are numbered and documented with translator comments.
- JavaScript translation setup uses
wp_set_script_translations()when needed.
Common Mistakes
- Using underscores in the text domain.
- Using a variable text domain, which extraction tools cannot reliably parse.
- Concatenating translatable sentence fragments.
- Echoing raw
__()output. - Translating dynamic user/content values.
- Naming bundled theme files
my-theme-de_DE.moinside the theme directory instead ofde_DE.mo.
References
- Official documentation: https://developer.wordpress.org/themes/advanced-topics/internationalization/
- Official documentation: https://developer.wordpress.org/themes/classic-themes/functionality/internationalization/
- Official documentation: https://developer.wordpress.org/reference/functions/load_theme_textdomain/
- Verified source paths:
wp-includes/l10n.phpwp-includes/class-wp-theme.phpwp-includes/link-template.phpwp-content/themes/storefront/style.csswp-content/themes/storefront/inc/class-storefront.phpwp-content/themes/generatepress/style.css
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.