How to Build Localized Theme Text Domains and Translations Without Breaking Site Responsiveness
Understanding WordPress Text Domains
In WordPress theme and plugin development, internationalization (i18n) and localization (l10n) are crucial for reaching a global audience. The cornerstone of this process is the “text domain.” A text domain is a unique identifier for your theme or plugin, used by WordPress to load the correct translation files. For themes, this text domain should be unique and typically matches the theme’s slug (the directory name).
When you use translation functions like __(), _e(), or _n() in your theme’s PHP files, you must pass this text domain as the last argument. This tells WordPress which set of translations to look for. For example, if your theme’s directory is named my-awesome-theme, its text domain should also be my-awesome-theme.
Defining Your Theme’s Text Domain
The primary place to declare your theme’s text domain is within its style.css file. This is a WordPress requirement for themes to be recognized and properly processed. The text domain is specified in the theme header comments.
/* Theme Name: My Awesome Theme Theme URI: https://example.com/my-awesome-theme/ Author: Your Name Author URI: https://example.com/ Description: A brief description of your awesome theme. Version: 1.0.0 Text Domain: my-awesome-theme Domain Path: /languages Requires at least: 5.0 Tested up to: 6.4 Requires PHP: 7.4 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Tags: custom-background, custom-logo, featured-images, theme-options, translation-ready */
The Text Domain line is critical. The Domain Path line specifies the directory where translation files (.po and .mo files) will be stored. Conventionally, this is a languages folder within your theme’s root directory.
Implementing Translation Functions
Now, let’s integrate translation functions into your theme’s PHP templates and functions. Every translatable string should be wrapped in one of WordPress’s internationalization functions. The most common ones are:
__(): Translates a string and returns it._e(): Translates a string and echoes it directly to the output._n(): Translates a singular or plural string based on a number.
Remember to always include your theme’s text domain as the final argument.
Example: Translating a Header Title
Consider a simple header in your header.php file:
<?php /** * The header for our theme * * This is the template that displays all of the section and * everything up until the main content * * @link https://developer.wordpress.org/themes/basics/template-files/#template-partials * * @package My_Awesome_Theme */ ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="<?php bloginfo( 'charset' ); ?>"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="profile" href="https://gmpg.org/xfn/11"> <?php wp_head(); ?> </head> <body <?php body_class(); ?>> <?php wp_body_open(); ?> <div id="page" class="site"> <a class="skip-link screen-reader-text" href="#content"><?php esc_html_e( 'Skip to content', 'my-awesome-theme' ); ?></a> <header id="masthead" class="site-header"> <div class="site-branding"> <?php the_custom_logo(); if ( is_front_page() && is_home() ) : ?> <h1 class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></h1> <?php elseif ( is_front_page() || is_home() ) : ?> <h2 class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></h2> <?php else : ?> <p class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></p> <?php endif; $description = get_bloginfo( 'description', 'display' ); if ( $description || is_customize_preview() ) : ?> <p class="site-description"><?php echo $description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p> <?php endif; ?> </div><!-- .site-branding --> <nav id="site-navigation" class="main-navigation"> <button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false"><?php esc_html_e( 'Primary Menu', 'my-awesome-theme' ); ?></button> <?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_id' => 'primary-menu', ) ); ?> </nav><!-- #site-navigation --> </header><!-- #masthead --> <div id="content" class="site-content"> <?php // Content will be displayed here by other template files ?> ?>
In this snippet, 'Skip to content' and 'Primary Menu' are wrapped in esc_html_e(), which is a safe way to echo translated strings that are intended for HTML output. It automatically escapes HTML entities.
Example: Translating Plural Strings
For strings that can be singular or plural, use _n(). This is common for displaying counts, like the number of posts found.
<?php
// In a template file like archive.php or search.php
$count = $wp_query->post_count; // Or any other way to get the count
if ( $count === 1 ) {
printf( _n( '%d post found', '%d posts found', $count, 'my-awesome-theme' ), $count );
} else {
printf( _n( '%d post found', '%d posts found', $count, 'my-awesome-theme' ), $count );
}
?>
Here, _n() takes the singular string, the plural string, the number to determine singularity/plurality, and the text domain. printf() is used to insert the count into the translated string.
Generating Translation Files
Once your theme is populated with translatable strings, you need to generate the actual translation files. This involves two steps:
1. Creating a .pot File
A .pot (Portable Object Template) file is a template containing all the translatable strings from your theme. It serves as the source for translators.
The most common and recommended way to generate a .pot file is by using WP-CLI (WordPress Command Line Interface). If you don’t have WP-CLI installed, you’ll need to set it up first.
# Navigate to your theme's directory cd wp-content/themes/my-awesome-theme # Generate the .pot file wp i18n make-pot . --slug=my-awesome-theme --package="My Awesome Theme" --headers="PO-Revision-Date: YEAR-MO-DA,00:00+0000 Last-Translator: Your Name <[email protected]> Language-Team: Your Language Team Name <[email protected]>"
This command:
wp i18n make-pot .: Tells WP-CLI to scan the current directory for translatable strings and create a POT file.--slug=my-awesome-theme: Specifies the text domain for the POT file.--package="My Awesome Theme": Sets the package name in the POT file header.--headers="...": Allows you to add custom headers like the last translator and language team.
This will create a my-awesome-theme.pot file in your theme’s root directory. You should move this file to the languages folder as specified in your style.css.
2. Creating .po and .mo Files
Translators use the .pot file to create language-specific translation files:
.po(Portable Object): This is a human-readable text file containing the original strings and their translations..mo(Machine Object): This is a compiled binary file that WordPress uses for actual translations.
You can use translation tools like Poedit (a desktop application) or online platforms like Transifex or Weblate. For local development, Poedit is excellent.
To create a translation for, say, French (fr_FR):
- Open
my-awesome-theme.potin Poedit. - Start translating strings.
- Save the translation. Poedit will generate
fr_FR.poandfr_FR.mofiles. - Place these files in your theme’s
languagesdirectory (e.g.,wp-content/themes/my-awesome-theme/languages/fr_FR.poandwp-content/themes/my-awesome-theme/languages/fr_FR.mo).
Ensuring Site Responsiveness
The process of internationalization and localization itself does not inherently break site responsiveness. Responsiveness is primarily controlled by CSS (media queries, flexible layouts like Flexbox and Grid) and the HTML structure. However, there are a few considerations:
Viewport Meta Tag
Ensure your theme includes the viewport meta tag in the <head> section. This is crucial for responsive design to work correctly on mobile devices. It tells the browser how to control the page’s dimensions and scaling.
<meta name="viewport" content="width=device-width, initial-scale=1">
This line is already present in the header.php example provided earlier.
CSS and Text Expansion
The main potential issue is that translated strings can be significantly longer or shorter than their English counterparts. This can cause layout issues if your CSS isn’t flexible enough.
For example, a German translation might be much longer than the English original. If your CSS has fixed widths or rigid layouts, this expansion can break the design.
Best Practices for Responsive CSS with Translations:
- Use relative units: Prefer
%,em,rem,vw, andvhover fixed units likepxwhere appropriate. - Employ Flexbox and CSS Grid: These layout modules are inherently flexible and adapt well to content changes.
- Avoid fixed widths: If you must use fixed widths, ensure they are generous enough to accommodate longer translations or use
min-widthproperties. - Test thoroughly: After translating, test your site on various devices and screen sizes, paying close attention to areas where text might expand or contract significantly.
- Use CSS
word-wraporoverflow-wrap: For very long words or strings that cannot be broken, these properties can help prevent them from breaking the layout.
/* Example of flexible styling */
.site-title {
font-size: 2rem; /* Relative to root font size */
margin-bottom: 1em; /* Relative to font size */
word-wrap: break-word; /* Allow long words to break */
overflow-wrap: break-word; /* Standardized version */
}
.site-header {
display: flex;
flex-wrap: wrap; /* Allow items to wrap on smaller screens */
justify-content: space-between;
align-items: center;
padding: 1em;
}
.site-branding {
flex-basis: 100%; /* Take full width on small screens */
text-align: center;
margin-bottom: 1em;
}
@media (min-width: 768px) {
.site-branding {
flex-basis: auto; /* Allow side-by-side on larger screens */
text-align: left;
margin-bottom: 0;
}
.site-navigation {
flex-basis: auto;
}
}
By adopting these CSS practices, your theme’s layout will be more resilient to the variations in text length that come with localization, ensuring that your site remains responsive across all languages.
Advanced Considerations: Including Translations in Child Themes
If you are developing a child theme, you should generally not copy the entire parent theme’s translation files. Instead, you should:
- Declare the parent theme’s text domain in your child theme’s
style.cssif you are overriding parent theme templates and need to load parent theme translations. - If your child theme introduces its own translatable strings, it needs its own text domain and its own translation files.
To load translations from a parent theme within a child theme, you can use the load_child_theme_textdomain hook. However, for loading parent theme translations, you typically rely on WordPress’s default loading mechanism, which checks the parent theme’s languages directory if the child theme doesn’t have its own.
// In your child theme's functions.php
function my_child_theme_setup() {
// Load child theme textdomain
load_child_theme_textdomain( 'my-child-theme-slug', get_stylesheet_directory() . '/languages' );
// If you need to load parent theme translations for overridden templates,
// WordPress usually handles this automatically if the parent theme's
// text domain is correctly declared and its languages are in the parent's languages folder.
// Explicitly loading parent theme translations is less common and can be complex.
// The primary goal is to ensure your child theme's strings are translatable.
}
add_action( 'after_setup_theme', 'my_child_theme_setup' );
For child themes, the key is to ensure that any new strings introduced in the child theme are properly marked with the child theme’s text domain and that the child theme has its own languages folder and translation files.