| 1 |
<?php |
| 2 |
/** |
| 3 |
* @package Polylang |
| 4 |
*/ |
| 5 |
|
| 6 |
/** |
| 7 |
* It is best practice that plugins do nothing before plugins_loaded is fired |
| 8 |
* so it is what Polylang intends to do |
| 9 |
* but some plugins load their text domain as soon as loaded, thus before plugins_loaded is fired |
| 10 |
* this class differs text domain loading until the language is defined |
| 11 |
* either in a plugins_loaded action or in a wp action ( when the language is set from content on frontend ) |
| 12 |
* |
| 13 |
* @since 1.2 |
| 14 |
*/ |
| 15 |
class PLL_OLT_Manager { |
| 16 |
/** |
| 17 |
* Singleton instance |
| 18 |
* |
| 19 |
* @var PLL_OLT_Manager |
| 20 |
*/ |
| 21 |
protected static $instance; |
| 22 |
|
| 23 |
/** |
| 24 |
* Stores the default site locale before it is modified. |
| 25 |
* |
| 26 |
* @var string |
| 27 |
*/ |
| 28 |
protected $default_locale; |
| 29 |
|
| 30 |
/** |
| 31 |
* Stores all loaded text domains and mo files. |
| 32 |
* |
| 33 |
* @var string[][] |
| 34 |
*/ |
| 35 |
protected $list_textdomains = array(); |
| 36 |
|
| 37 |
/** |
| 38 |
* Stores post types an taxonomies labels to translate. |
| 39 |
* |
| 40 |
* @var string[][] |
| 41 |
*/ |
| 42 |
public $labels = array(); |
| 43 |
|
| 44 |
/** |
| 45 |
* Constructor: setups relevant filters |
| 46 |
* |
| 47 |
* @since 1.2 |
| 48 |
*/ |
| 49 |
public function __construct() { |
| 50 |
// Allows Polylang to be the first plugin loaded ;-) |
| 51 |
add_filter( 'pre_update_option_active_plugins', array( $this, 'make_polylang_first' ) ); |
| 52 |
add_filter( 'pre_update_option_active_sitewide_plugins', array( $this, 'make_polylang_first' ) ); |
| 53 |
|
| 54 |
// Overriding load text domain only on front since WP 4.7. |
| 55 |
if ( is_admin() && ! Polylang::is_ajax_on_front() ) { |
| 56 |
return; |
| 57 |
} |
| 58 |
|
| 59 |
// Saves the default locale before we start any language manipulation |
| 60 |
$this->default_locale = get_locale(); |
| 61 |
|
| 62 |
// Filters for text domain management |
| 63 |
add_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ), 10, 2 ); |
| 64 |
add_filter( 'gettext', array( $this, 'gettext' ), 10, 3 ); |
| 65 |
add_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ), 10, 4 ); |
| 66 |
|
| 67 |
// Loads text domains |
| 68 |
add_action( 'pll_language_defined', array( $this, 'load_textdomains' ), 2 ); // After PLL_Frontend::pll_language_defined |
| 69 |
add_action( 'pll_no_language_defined', array( $this, 'load_textdomains' ) ); |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Access to the single instance of the class |
| 74 |
* |
| 75 |
* @since 1.7 |
| 76 |
* |
| 77 |
* @return object |
| 78 |
*/ |
| 79 |
public static function instance() { |
| 80 |
if ( empty( self::$instance ) ) { |
| 81 |
self::$instance = new self(); |
| 82 |
} |
| 83 |
|
| 84 |
return self::$instance; |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Loads text domains |
| 89 |
* |
| 90 |
* @since 0.1 |
| 91 |
* |
| 92 |
* @return void |
| 93 |
*/ |
| 94 |
public function load_textdomains() { |
| 95 |
// Our load_textdomain_mofile filter has done its job. let's remove it before calling load_textdomain |
| 96 |
remove_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ), 10, 2 ); |
| 97 |
remove_filter( 'gettext', array( $this, 'gettext' ), 10, 3 ); |
| 98 |
remove_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ), 10, 4 ); |
| 99 |
$new_locale = get_locale(); |
| 100 |
|
| 101 |
// Don't try to save time for en_US as some users have theme written in another language |
| 102 |
// Now we can load all overridden text domains with the right language |
| 103 |
if ( ! empty( $this->list_textdomains ) ) { |
| 104 |
/* |
| 105 |
* FIXME: Backward compatibility with WP < 5.6 |
| 106 |
* From WP 4.7 to 5.5, we need to reset the internal cache of _get_path_to_translation when switching from any locale to en_US. |
| 107 |
* See WP_Locale_Switcher::change_locale() |
| 108 |
*/ |
| 109 |
if ( ! class_exists( 'WP_Textdomain_Registry' ) && function_exists( '_get_path_to_translation' ) ) { |
| 110 |
_get_path_to_translation( '', true ); |
| 111 |
} |
| 112 |
|
| 113 |
foreach ( $this->list_textdomains as $textdomain ) { |
| 114 |
// Since WP 4.6, plugins translations are first loaded from wp-content/languages |
| 115 |
if ( ! load_textdomain( $textdomain['domain'], str_replace( "{$this->default_locale}.mo", "$new_locale.mo", $textdomain['mo'] ) ) ) { |
| 116 |
// Since WP 3.5 themes may store languages files in /wp-content/languages/themes |
| 117 |
if ( ! load_textdomain( $textdomain['domain'], WP_LANG_DIR . "/themes/{$textdomain['domain']}-$new_locale.mo" ) ) { |
| 118 |
// Since WP 3.7 plugins may store languages files in /wp-content/languages/plugins |
| 119 |
load_textdomain( $textdomain['domain'], WP_LANG_DIR . "/plugins/{$textdomain['domain']}-$new_locale.mo" ); |
| 120 |
} |
| 121 |
} |
| 122 |
} |
| 123 |
} |
| 124 |
|
| 125 |
// First remove taxonomies and post_types labels that we don't need to translate |
| 126 |
$taxonomies = get_taxonomies( array( '_pll' => true ) ); |
| 127 |
$post_types = get_post_types( array( '_pll' => true ) ); |
| 128 |
|
| 129 |
// We don't need to translate core taxonomies and post types labels when setting the language from the url |
| 130 |
// As they will be translated when registered the second time |
| 131 |
if ( ! did_action( 'setup_theme' ) ) { |
| 132 |
$taxonomies = array_merge( get_taxonomies( array( '_builtin' => true ) ), $taxonomies ); |
| 133 |
$post_types = array_merge( get_post_types( array( '_builtin' => true ) ), $post_types ); |
| 134 |
} |
| 135 |
|
| 136 |
// Translate labels of post types and taxonomies |
| 137 |
foreach ( array_diff_key( $GLOBALS['wp_taxonomies'], array_flip( $taxonomies ) ) as $tax ) { |
| 138 |
$this->translate_labels( $tax ); |
| 139 |
} |
| 140 |
foreach ( array_diff_key( $GLOBALS['wp_post_types'], array_flip( $post_types ) ) as $pt ) { |
| 141 |
$this->translate_labels( $pt ); |
| 142 |
} |
| 143 |
|
| 144 |
// Act only if the language has not been set early ( before default textdomain loading and $wp_locale creation ) |
| 145 |
if ( did_action( 'after_setup_theme' ) ) { |
| 146 |
// Reinitializes wp_locale for weekdays and months |
| 147 |
unset( $GLOBALS['wp_locale'] ); |
| 148 |
$GLOBALS['wp_locale'] = new WP_Locale(); |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Fires after the post types and taxonomies labels have been translated |
| 153 |
* This allows plugins to translate text the same way we do for post types and taxonomies labels |
| 154 |
* |
| 155 |
* @since 1.2 |
| 156 |
* |
| 157 |
* @param array $labels list of strings to trnaslate |
| 158 |
*/ |
| 159 |
do_action_ref_array( 'pll_translate_labels', array( &$this->labels ) ); |
| 160 |
|
| 161 |
// Free memory |
| 162 |
unset( $this->default_locale, $this->list_textdomains, $this->labels ); |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Saves all text domains in a table for later usage. |
| 167 |
* It replaces the 'override_load_textdomain' filter previously used. |
| 168 |
* |
| 169 |
* @since 2.0.4 |
| 170 |
* |
| 171 |
* @param string $mofile The translation file name. |
| 172 |
* @param string $domain The text domain name. |
| 173 |
* @return string |
| 174 |
*/ |
| 175 |
public function load_textdomain_mofile( $mofile, $domain ) { |
| 176 |
// On multisite, 2 files are sharing the same domain so we need to distinguish them. |
| 177 |
if ( 'default' === $domain && false !== strpos( $mofile, '/ms-' ) ) { |
| 178 |
$this->list_textdomains['ms-default'] = array( 'mo' => $mofile, 'domain' => $domain ); |
| 179 |
} else { |
| 180 |
$this->list_textdomains[ $domain ] = array( 'mo' => $mofile, 'domain' => $domain ); |
| 181 |
} |
| 182 |
return ''; // Hack to prevent WP loading text domains as we will load them all later. |
| 183 |
} |
| 184 |
|
| 185 |
/** |
| 186 |
* Saves post types and taxonomies labels for a later usage |
| 187 |
* |
| 188 |
* @since 0.9 |
| 189 |
* |
| 190 |
* @param string $translation not used |
| 191 |
* @param string $text string to translate |
| 192 |
* @param string $domain text domain |
| 193 |
* @return string unmodified $translation |
| 194 |
*/ |
| 195 |
public function gettext( $translation, $text, $domain ) { |
| 196 |
if ( is_string( $text ) ) { // Avoid a warning with some buggy plugins which pass an array |
| 197 |
$this->labels[ $text ] = array( 'domain' => $domain ); |
| 198 |
} |
| 199 |
return $translation; |
| 200 |
} |
| 201 |
|
| 202 |
/** |
| 203 |
* Saves post types and taxonomies labels for a later usage |
| 204 |
* |
| 205 |
* @since 0.9 |
| 206 |
* |
| 207 |
* @param string $translation not used |
| 208 |
* @param string $text string to translate |
| 209 |
* @param string $context some comment to describe the context of string to translate |
| 210 |
* @param string $domain text domain |
| 211 |
* @return string unmodified $translation |
| 212 |
*/ |
| 213 |
public function gettext_with_context( $translation, $text, $context, $domain ) { |
| 214 |
$this->labels[ $text ] = array( 'domain' => $domain, 'context' => $context ); |
| 215 |
return $translation; |
| 216 |
} |
| 217 |
|
| 218 |
/** |
| 219 |
* Translates post types and taxonomies labels once the language is known. |
| 220 |
* |
| 221 |
* @since 0.9 |
| 222 |
* |
| 223 |
* @param WP_Post_Type|WP_Taxonomy $type Either a post type or a taxonomy. |
| 224 |
* @return void |
| 225 |
*/ |
| 226 |
public function translate_labels( $type ) { |
| 227 |
// Use static array to avoid translating several times the same ( default ) labels |
| 228 |
static $translated = array(); |
| 229 |
|
| 230 |
foreach ( $type->labels as $key => $label ) { |
| 231 |
if ( is_string( $label ) && isset( $this->labels[ $label ] ) ) { |
| 232 |
if ( empty( $translated[ $label ] ) ) { |
| 233 |
// PHPCS:disable WordPress.WP.I18n |
| 234 |
$type->labels->$key = $translated[ $label ] = isset( $this->labels[ $label ]['context'] ) ? |
| 235 |
_x( $label, $this->labels[ $label ]['context'], $this->labels[ $label ]['domain'] ) : |
| 236 |
__( $label, $this->labels[ $label ]['domain'] ); |
| 237 |
// PHPCS:enable |
| 238 |
} |
| 239 |
else { |
| 240 |
$type->labels->$key = $translated[ $label ]; |
| 241 |
} |
| 242 |
} |
| 243 |
} |
| 244 |
} |
| 245 |
|
| 246 |
/** |
| 247 |
* Allows Polylang to be the first plugin loaded ;-). |
| 248 |
* |
| 249 |
* @since 1.2 |
| 250 |
* |
| 251 |
* @param string[] $plugins List of active plugins. |
| 252 |
* @return string[] List of active plugins. |
| 253 |
*/ |
| 254 |
public function make_polylang_first( $plugins ) { |
| 255 |
if ( $key = array_search( POLYLANG_BASENAME, $plugins ) ) { |
| 256 |
unset( $plugins[ $key ] ); |
| 257 |
array_unshift( $plugins, POLYLANG_BASENAME ); |
| 258 |
} |
| 259 |
return $plugins; |
| 260 |
} |
| 261 |
} |
| 262 |
|