Is it enough to just create a child-theme – let’s say technically without adding anything else but the bare minimum style.css – to have the translation of the parent-theme being used automatically for the child-theme as well?
Basically, the answer is NO, … but… there’s an option:
Add a mu-plugin.
This (MU-)Plugin does several things:
- It hooks into
after_setup_theme
on a priority of 20 – assuming that the parent textdomain/i18n .mo file is loaded correctly at the default priority on the correct hook. - Then it retrieves an
instanceof
theWP_Theme
– in this case the child theme. - Then it checks if there actually is a child theme in use.
- If this was true, then it will just load the textdomain from the parent file.
It’s actually quite easy, as the core class does a lot of checks for us: It retrieves another instance of WP_Theme
for the parent theme. Then it checks if the TextDomain
header is set, using: $current_theme->get( 'TextDomain' );
. The point therefore is there comes one convention into the game: This plugin will only work, if the parent theme got a Text Domain
and(!) a Domain Path
header set.
<?php
/**
* Plugin Name: (#113391) Parent Theme i18n Autoloader
* Description: Load Twenty12 Child theme translation files automagically from Parent
*/
add_action( 'after_setup_theme', 'wpse113391_parent_theme_i18n_autoloader', 20 );
function wpse113391_parent_theme_i18n_autoloader()
{
$current_theme = wp_get_theme();
if ( is_child_theme() )
$current_theme->parent()->load_textdomain();
}
Now here comes the problem: The default/standard Twenty* themes delivered by core do not(!) have the Domain Path
header entry. And this is something that we have to fix instantly, as load_theme_textdomain()
else searches for the translation file not in the Parent theme folder, but
- first in the child theme folder:
get_stylesheet_directory().WP_Theme::get( 'DomainPath' )
, which means that (A) theDomain Path
needs to be set and it needs to be prefixed with a slash:/
. - then in the child theme folder: `get_stylesheet_directory().’/languages’.
- and last in the in
WP_LANGUAGE_DIR.'/themes'
directory.
Note: I guess that’s just a bug that will never be fixed for “backwards compatibility”, which – in other words – means that there is a bug, but there may be devs already working around it. 😛
Then there’s another problem. The WP_Theme
class method load_textdomain()
internally passes a $path
to load_theme_textdomain()
. And this parameter is $this->get_stylesheet_directory()
. And this method returns $this->theme_root . "https://wordpress.stackexchange.com/" . $this->stylesheet
. So the function would actually work quite good, but it’s messing it up with simply calling an internal replacement for get_stylesheet_directory()
(which would have been filterable). One might now think
“Hey! The class
implements ArrayAccess
! So simply set the missing array key ofDomain Path
!”
Wrong. All the class properties are marked private
and inaccessible.
Then you might think
“Why not simply
extend
theWP_Theme
class and define aset()
Method so we can manually set the missing header entries?”
Wrong. The class itself is final
and not extensible.
Result: We’re left with what load_theme_textdomain()
– the last function in the chain of calls – offers us. Now we got a larger plugin that intercepts the load_theme_textdomain()
call to load the correct file. To not disturb other i18n file loads, it instantly removes the callback from the filter to keep your environment tidy.
<?php
/**
* Plugin Name: (#113391) Parent Theme i18n Autoloader
* Description: Load Twenty12 Child theme translation files automagically from Parent
*/
add_action( 'muplugins_loaded', array( 'WPSE113391Parenti18nLoader', 'getInstance' ) );
class WPSE113391Parenti18nLoader
{
public static $instance = null;
private $theme = null;
public static function getInstance()
{
null === self::$instance AND self::$instance = new self;
return self::$instance;
}
public function __construct()
{
add_action( 'after_setup_theme', array( $this, 'i18nAutoloader' ), 20 );
}
public function setTheme( $theme )
{
return $this->theme = $theme;
}
public function getTheme()
{
return $this->theme;
}
public function i18nAutoloader()
{
if ( ! is_child_theme() )
return;
$current_theme = wp_get_theme();
if ( '' === $current_theme->parent()->get( 'DomainPath' ) )
{
$this->setTheme( $current_theme->parent() );
add_filter( 'override_load_textdomain', array( $this, 'overrideI18nLoader' ), 10, 3 );
}
$current_theme->parent()->load_textdomain();
}
public function overrideI18nLoader( $activate, $domain, $mofile )
{
// Don't intercept anything else: Self removing
remove_filter( current_filter(), __FUNCTION__ );
// Rebuild the internals of WP_Theme::get_stylesheet_directory() and load_theme_textdomain()
$theme = $this->getTheme();
$path = trailingslashit( $theme->get_theme_root() ).$theme->get_template();
$locale = apply_filters( 'theme_locale', get_locale(), $domain );
load_textdomain( $domain, "{$path}/{$locale}.mo" );
// Return true to abort further attempts
return true;
}
}