Classic theme i18n textdomain
Skill Lonsdale201/wp-agent-skills/theme-development/classic-theme-i18n-textdomain
A community-maintained collection of agent skills for WordPress plugin and theme development.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
7.0 KB, 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