WordPress.org

Make WordPress Core

Ticket #37997: l10n.php

File l10n.php, 38.5 KB (added by sharkomatic, 4 years ago)

l10n.php with performance improvements

Line 
1<?php
2/**
3 * Core Translation API
4 *
5 * @package WordPress
6 * @subpackage i18n
7 * @since 1.2.0
8 */
9
10/**
11 * Retrieves the current locale.
12 *
13 * If the locale is set, then it will filter the locale in the {@see 'locale'}
14 * filter hook and return the value.
15 *
16 * If the locale is not set already, then the WPLANG constant is used if it is
17 * defined. Then it is filtered through the {@see 'locale'} filter hook and
18 * the value for the locale global set and the locale is returned.
19 *
20 * The process to get the locale should only be done once, but the locale will
21 * always be filtered using the {@see 'locale'} hook.
22 *
23 * @since 1.5.0
24 *
25 * @global string $locale
26 * @global string $wp_local_package
27 *
28 * @return string The locale of the blog or from the {@see 'locale'} hook.
29 */
30function get_locale() {
31        global $locale, $wp_local_package;
32
33        if ( isset( $locale ) ) {
34                /**
35                 * Filters WordPress install's locale ID.
36                 *
37                 * @since 1.5.0
38                 *
39                 * @param string $locale The locale ID.
40                 */
41                return apply_filters( 'locale', $locale );
42        }
43
44        if ( isset( $wp_local_package ) ) {
45                $locale = $wp_local_package;
46        }
47
48        // WPLANG was defined in wp-config.
49        if ( defined( 'WPLANG' ) ) {
50                $locale = WPLANG;
51        }
52
53        // If multisite, check options.
54        if ( is_multisite() ) {
55                // Don't check blog option when installing.
56                if ( wp_installing() || ( false === $ms_locale = get_option( 'WPLANG' ) ) ) {
57                        $ms_locale = get_site_option( 'WPLANG' );
58                }
59
60                if ( $ms_locale !== false ) {
61                        $locale = $ms_locale;
62                }
63        } else {
64                $db_locale = get_option( 'WPLANG' );
65                if ( $db_locale !== false ) {
66                        $locale = $db_locale;
67                }
68        }
69
70        if ( empty( $locale ) ) {
71                $locale = 'en_US';
72        }
73
74        /** This filter is documented in wp-includes/l10n.php */
75        return apply_filters( 'locale', $locale );
76}
77
78/**
79 * Retrieve the translation of $text.
80 *
81 * If there is no translation, or the text domain isn't loaded, the original text is returned.
82 *
83 * *Note:* Don't use translate() directly, use __() or related functions.
84 *
85 * @since 2.2.0
86 *
87 * @param string $text   Text to translate.
88 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
89 *                       Default 'default'.
90 * @return string Translated text
91 */
92function translate( $text, $domain = 'default' ) {
93        $translations = get_translations_for_domain( $domain );
94        $translations = $translations->translate( $text );
95
96        /**
97         * Filters text with its translation.
98         *
99         * @since 2.0.11
100         *
101         * @param string $translations Translated text.
102         * @param string $text         Text to translate.
103         * @param string $domain       Text domain. Unique identifier for retrieving translated strings.
104         */
105        return apply_filters( 'gettext', $translations, $text, $domain );
106}
107
108/**
109 * Remove last item on a pipe-delimited string.
110 *
111 * Meant for removing the last item in a string, such as 'Role name|User role'. The original
112 * string will be returned if no pipe '|' characters are found in the string.
113 *
114 * @since 2.8.0
115 *
116 * @param string $string A pipe-delimited string.
117 * @return string Either $string or everything before the last pipe.
118 */
119function before_last_bar( $string ) {
120        $last_bar = strrpos( $string, '|' );
121        if ( false === $last_bar ) {
122                return $string;
123        } else {
124                return substr( $string, 0, $last_bar );
125        }
126}
127
128/**
129 * Retrieve the translation of $text in the context defined in $context.
130 *
131 * If there is no translation, or the text domain isn't loaded the original
132 * text is returned.
133 *
134 * *Note:* Don't use translate_with_gettext_context() directly, use _x() or related functions.
135 *
136 * @since 2.8.0
137 *
138 * @param string $text    Text to translate.
139 * @param string $context Context information for the translators.
140 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
141 *                        Default 'default'.
142 * @return string Translated text on success, original text on failure.
143 */
144function translate_with_gettext_context( $text, $context, $domain = 'default' ) {
145        $translations = get_translations_for_domain( $domain );
146        $translations = $translations->translate( $text, $context );
147        /**
148         * Filters text with its translation based on context information.
149         *
150         * @since 2.8.0
151         *
152         * @param string $translations Translated text.
153         * @param string $text         Text to translate.
154         * @param string $context      Context information for the translators.
155         * @param string $domain       Text domain. Unique identifier for retrieving translated strings.
156         */
157        return apply_filters( 'gettext_with_context', $translations, $text, $context, $domain );
158}
159
160/**
161 * Retrieve the translation of $text.
162 *
163 * If there is no translation, or the text domain isn't loaded, the original text is returned.
164 *
165 * @since 2.1.0
166 *
167 * @param string $text   Text to translate.
168 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
169 *                       Default 'default'.
170 * @return string Translated text.
171 */
172function __( $text, $domain = 'default' ) {
173        return translate( $text, $domain );
174}
175
176/**
177 * Retrieve the translation of $text and escapes it for safe use in an attribute.
178 *
179 * If there is no translation, or the text domain isn't loaded, the original text is returned.
180 *
181 * @since 2.8.0
182 *
183 * @param string $text   Text to translate.
184 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
185 *                       Default 'default'.
186 * @return string Translated text on success, original text on failure.
187 */
188function esc_attr__( $text, $domain = 'default' ) {
189        return esc_attr( translate( $text, $domain ) );
190}
191
192/**
193 * Retrieve the translation of $text and escapes it for safe use in HTML output.
194 *
195 * If there is no translation, or the text domain isn't loaded, the original text is returned.
196 *
197 * @since 2.8.0
198 *
199 * @param string $text   Text to translate.
200 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
201 *                       Default 'default'.
202 * @return string Translated text
203 */
204function esc_html__( $text, $domain = 'default' ) {
205        return esc_html( translate( $text, $domain ) );
206}
207
208/**
209 * Display translated text.
210 *
211 * @since 1.2.0
212 *
213 * @param string $text   Text to translate.
214 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
215 *                       Default 'default'.
216 */
217function _e( $text, $domain = 'default' ) {
218        echo translate( $text, $domain );
219}
220
221/**
222 * Display translated text that has been escaped for safe use in an attribute.
223 *
224 * @since 2.8.0
225 *
226 * @param string $text   Text to translate.
227 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
228 *                       Default 'default'.
229 */
230function esc_attr_e( $text, $domain = 'default' ) {
231        echo esc_attr( translate( $text, $domain ) );
232}
233
234/**
235 * Display translated text that has been escaped for safe use in HTML output.
236 *
237 * @since 2.8.0
238 *
239 * @param string $text   Text to translate.
240 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
241 *                       Default 'default'.
242 */
243function esc_html_e( $text, $domain = 'default' ) {
244        echo esc_html( translate( $text, $domain ) );
245}
246
247/**
248 * Retrieve translated string with gettext context.
249 *
250 * Quite a few times, there will be collisions with similar translatable text
251 * found in more than two places, but with different translated context.
252 *
253 * By including the context in the pot file, translators can translate the two
254 * strings differently.
255 *
256 * @since 2.8.0
257 *
258 * @param string $text    Text to translate.
259 * @param string $context Context information for the translators.
260 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
261 *                        Default 'default'.
262 * @return string Translated context string without pipe.
263 */
264function _x( $text, $context, $domain = 'default' ) {
265        return translate_with_gettext_context( $text, $context, $domain );
266}
267
268/**
269 * Display translated string with gettext context.
270 *
271 * @since 3.0.0
272 *
273 * @param string $text    Text to translate.
274 * @param string $context Context information for the translators.
275 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
276 *                        Default 'default'.
277 * @return string Translated context string without pipe.
278 */
279function _ex( $text, $context, $domain = 'default' ) {
280        echo _x( $text, $context, $domain );
281}
282
283/**
284 * Translate string with gettext context, and escapes it for safe use in an attribute.
285 *
286 * @since 2.8.0
287 *
288 * @param string $text    Text to translate.
289 * @param string $context Context information for the translators.
290 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
291 *                        Default 'default'.
292 * @return string Translated text
293 */
294function esc_attr_x( $text, $context, $domain = 'default' ) {
295        return esc_attr( translate_with_gettext_context( $text, $context, $domain ) );
296}
297
298/**
299 * Translate string with gettext context, and escapes it for safe use in HTML output.
300 *
301 * @since 2.9.0
302 *
303 * @param string $text    Text to translate.
304 * @param string $context Context information for the translators.
305 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
306 *                        Default 'default'.
307 * @return string Translated text.
308 */
309function esc_html_x( $text, $context, $domain = 'default' ) {
310        return esc_html( translate_with_gettext_context( $text, $context, $domain ) );
311}
312
313/**
314 * Translates and retrieves the singular or plural form based on the supplied number.
315 *
316 * Used when you want to use the appropriate form of a string based on whether a
317 * number is singular or plural.
318 *
319 * Example:
320 *
321 *     $people = sprintf( _n( '%s person', '%s people', $count, 'text-domain' ), number_format_i18n( $count ) );
322 *
323 * @since 2.8.0
324 *
325 * @param string $single The text to be used if the number is singular.
326 * @param string $plural The text to be used if the number is plural.
327 * @param int    $number The number to compare against to use either the singular or plural form.
328 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
329 *                       Default 'default'.
330 * @return string The translated singular or plural form.
331 */
332function _n( $single, $plural, $number, $domain = 'default' ) {
333        $translations = get_translations_for_domain( $domain );
334        $translation = $translations->translate_plural( $single, $plural, $number );
335
336        /**
337         * Filters the singular or plural form of a string.
338         *
339         * @since 2.2.0
340         *
341         * @param string $translation Translated text.
342         * @param string $single      The text to be used if the number is singular.
343         * @param string $plural      The text to be used if the number is plural.
344         * @param string $number      The number to compare against to use either the singular or plural form.
345         * @param string $domain      Text domain. Unique identifier for retrieving translated strings.
346         */
347        return apply_filters( 'ngettext', $translation, $single, $plural, $number, $domain );
348}
349
350/**
351 * Translates and retrieves the singular or plural form based on the supplied number, with gettext context.
352 *
353 * This is a hybrid of _n() and _x(). It supports context and plurals.
354 *
355 * Used when you want to use the appropriate form of a string with context based on whether a
356 * number is singular or plural.
357 *
358 * Example:
359 *
360 *     $people = sprintf( _n( '%s person', '%s people', $count, 'context', 'text-domain' ), number_format_i18n( $count ) );
361 *
362 * @since 2.8.0
363 *
364 * @param string $single  The text to be used if the number is singular.
365 * @param string $plural  The text to be used if the number is plural.
366 * @param int    $number  The number to compare against to use either the singular or plural form.
367 * @param string $context Context information for the translators.
368 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
369 *                        Default 'default'.
370 * @return string The translated singular or plural form.
371 */
372function _nx($single, $plural, $number, $context, $domain = 'default') {
373        $translations = get_translations_for_domain( $domain );
374        $translation = $translations->translate_plural( $single, $plural, $number, $context );
375
376        /**
377         * Filters the singular or plural form of a string with gettext context.
378         *
379         * @since 2.8.0
380         *
381         * @param string $translation Translated text.
382         * @param string $single      The text to be used if the number is singular.
383         * @param string $plural      The text to be used if the number is plural.
384         * @param string $number      The number to compare against to use either the singular or plural form.
385         * @param string $context     Context information for the translators.
386         * @param string $domain      Text domain. Unique identifier for retrieving translated strings.
387         */
388        return apply_filters( 'ngettext_with_context', $translation, $single, $plural, $number, $context, $domain );
389}
390
391/**
392 * Registers plural strings in POT file, but does not translate them.
393 *
394 * Used when you want to keep structures with translatable plural
395 * strings and use them later when the number is known.
396 *
397 * Example:
398 *
399 *     $messages = array(
400 *              'post' => _n_noop( '%s post', '%s posts', 'text-domain' ),
401 *              'page' => _n_noop( '%s pages', '%s pages', 'text-domain' ),
402 *     );
403 *     ...
404 *     $message = $messages[ $type ];
405 *     $usable_text = sprintf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
406 *
407 * @since 2.5.0
408 *
409 * @param string $singular Singular form to be localized.
410 * @param string $plural   Plural form to be localized.
411 * @param string $domain   Optional. Text domain. Unique identifier for retrieving translated strings.
412 *                         Default null.
413 * @return array {
414 *     Array of translation information for the strings.
415 *
416 *     @type string $0        Singular form to be localized. No longer used.
417 *     @type string $1        Plural form to be localized. No longer used.
418 *     @type string $singular Singular form to be localized.
419 *     @type string $plural   Plural form to be localized.
420 *     @type null   $context  Context information for the translators.
421 *     @type string $domain   Text domain.
422 * }
423 */
424function _n_noop( $singular, $plural, $domain = null ) {
425        return array( 0 => $singular, 1 => $plural, 'singular' => $singular, 'plural' => $plural, 'context' => null, 'domain' => $domain );
426}
427
428/**
429 * Registers plural strings with gettext context in POT file, but does not translate them.
430 *
431 * Used when you want to keep structures with translatable plural
432 * strings and use them later when the number is known.
433 *
434 * Example:
435 *
436 *     $messages = array(
437 *              'post' => _n_noop( '%s post', '%s posts', 'context', 'text-domain' ),
438 *              'page' => _n_noop( '%s pages', '%s pages', 'context', 'text-domain' ),
439 *     );
440 *     ...
441 *     $message = $messages[ $type ];
442 *     $usable_text = sprintf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
443 *
444 * @since 2.8.0
445 *
446 * @param string $singular Singular form to be localized.
447 * @param string $plural   Plural form to be localized.
448 * @param string $context  Context information for the translators.
449 * @param string $domain   Optional. Text domain. Unique identifier for retrieving translated strings.
450 *                         Default null.
451 * @return array {
452 *     Array of translation information for the strings.
453 *
454 *     @type string $0        Singular form to be localized. No longer used.
455 *     @type string $1        Plural form to be localized. No longer used.
456 *     @type string $2        Context information for the translators. No longer used.
457 *     @type string $singular Singular form to be localized.
458 *     @type string $plural   Plural form to be localized.
459 *     @type string $context  Context information for the translators.
460 *     @type string $domain   Text domain.
461 * }
462 */
463function _nx_noop( $singular, $plural, $context, $domain = null ) {
464        return array( 0 => $singular, 1 => $plural, 2 => $context, 'singular' => $singular, 'plural' => $plural, 'context' => $context, 'domain' => $domain );
465}
466
467/**
468 * Translates and retrieves the singular or plural form of a string that's been registered
469 * with _n_noop() or _nx_noop().
470 *
471 * Used when you want to use a translatable plural string once the number is known.
472 *
473 * Example:
474 *
475 *     $messages = array(
476 *              'post' => _n_noop( '%s post', '%s posts', 'text-domain' ),
477 *              'page' => _n_noop( '%s pages', '%s pages', 'text-domain' ),
478 *     );
479 *     ...
480 *     $message = $messages[ $type ];
481 *     $usable_text = sprintf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
482 *
483 * @since 3.1.0
484 *
485 * @param array  $nooped_plural Array with singular, plural, and context keys, usually the result of _n_noop() or _nx_noop().
486 * @param int    $count         Number of objects.
487 * @param string $domain        Optional. Text domain. Unique identifier for retrieving translated strings. If $nooped_plural contains
488 *                              a text domain passed to _n_noop() or _nx_noop(), it will override this value. Default 'default'.
489 * @return string Either $single or $plural translated text.
490 */
491function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) {
492        if ( $nooped_plural['domain'] )
493                $domain = $nooped_plural['domain'];
494
495        if ( $nooped_plural['context'] )
496                return _nx( $nooped_plural['singular'], $nooped_plural['plural'], $count, $nooped_plural['context'], $domain );
497        else
498                return _n( $nooped_plural['singular'], $nooped_plural['plural'], $count, $domain );
499}
500
501/**
502 * Load a .mo file into the text domain $domain.
503 *
504 * If the text domain already exists, the translations will be merged. If both
505 * sets have the same string, the translation from the original value will be taken.
506 *
507 * On success, the .mo file will be placed in the $l10n global by $domain
508 * and will be a MO object.
509 *
510 * @since 1.5.0
511 *
512 * @global array $l10n          An array of all currently loaded text domains.
513 * @global array $l10n_unloaded An array of all text domains that have been unloaded again.
514 *
515 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
516 * @param string $mofile Path to the .mo file.
517 * @return bool True on success, false on failure.
518 */
519function load_textdomain( $domain, $mofile ) {
520        global $l10n, $l10n_unloaded;
521
522        $l10n_unloaded = (array) $l10n_unloaded;
523
524        /**
525         * Filters whether to override the .mo file loading.
526         *
527         * @since 2.9.0
528         *
529         * @param bool   $override Whether to override the .mo file loading. Default false.
530         * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
531         * @param string $mofile   Path to the MO file.
532         */
533        $plugin_override = apply_filters( 'override_load_textdomain', false, $domain, $mofile );
534
535        if ( true == $plugin_override ) {
536                unset( $l10n_unloaded[ $domain ] );
537
538                return true;
539        }
540
541        /**
542         * Fires before the MO translation file is loaded.
543         *
544         * @since 2.9.0
545         *
546         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
547         * @param string $mofile Path to the .mo file.
548         */
549        do_action( 'load_textdomain', $domain, $mofile );
550
551        /**
552         * Filters MO file path for loading translations for a specific text domain.
553         *
554         * @since 2.9.0
555         *
556         * @param string $mofile Path to the MO file.
557         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
558         */
559        $mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain );
560
561        if ( !is_readable( $mofile ) ) return false;
562
563        $mo = new MO();
564        if ( !$mo->import_from_file( $mofile ) ) return false;
565
566        if ( isset( $l10n[$domain] ) )
567                $mo->merge_with( $l10n[$domain] );
568
569        unset( $l10n_unloaded[ $domain ] );
570
571        $l10n[$domain] = &$mo;
572
573        return true;
574}
575
576/**
577 * Unload translations for a text domain.
578 *
579 * @since 3.0.0
580 *
581 * @global array $l10n          An array of all currently loaded text domains.
582 * @global array $l10n_unloaded An array of all text domains that have been unloaded again.
583 *
584 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
585 * @return bool true
586 */
587function unload_textdomain( $domain ) {
588        global $l10n, $l10n_unloaded;
589
590        $l10n_unloaded = (array) $l10n_unloaded;
591
592        /**
593         * Filters whether to override the text domain unloading.
594         *
595         * @since 3.0.0
596         *
597         * @param bool   $override Whether to override the text domain unloading. Default false.
598         * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
599         */
600        $plugin_override = apply_filters( 'override_unload_textdomain', false, $domain );
601
602        if ( $plugin_override ) {
603                $l10n_unloaded[ $domain ] = true;
604
605                return true;
606        }
607
608        /**
609         * Fires before the text domain is unloaded.
610         *
611         * @since 3.0.0
612         *
613         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
614         */
615        do_action( 'unload_textdomain', $domain );
616
617        if ( isset( $l10n[$domain] ) ) {
618                unset( $l10n[$domain] );
619        }
620
621        $l10n_unloaded[ $domain ] = true;
622
623        return true;
624}
625
626/**
627 * Load default translated strings based on locale.
628 *
629 * Loads the .mo file in WP_LANG_DIR constant path from WordPress root.
630 * The translated (.mo) file is named based on the locale.
631 *
632 * @see load_textdomain()
633 *
634 * @since 1.5.0
635 *
636 * @param string $locale Optional. Locale to load. Default is the value of get_locale().
637 * @return bool Whether the textdomain was loaded.
638 */
639function load_default_textdomain( $locale = null ) {
640        if ( null === $locale ) {
641                $locale = get_locale();
642        }
643
644        // Unload previously loaded strings so we can switch translations.
645        unload_textdomain( 'default' );
646
647        $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo" );
648
649        if ( ( is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) && ! file_exists(  WP_LANG_DIR . "/admin-$locale.mo" ) ) {
650                load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo" );
651                return $return;
652        }
653
654        if ( is_admin() || wp_installing() || ( defined( 'WP_REPAIRING' ) && WP_REPAIRING ) ) {
655                load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" );
656        }
657
658        if ( is_network_admin() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) )
659                load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo" );
660
661        return $return;
662}
663
664/**
665 * Loads a plugin's translated strings.
666 *
667 * If the path is not given then it will be the root of the plugin directory.
668 *
669 * The .mo file should be named based on the text domain with a dash, and then the locale exactly.
670 *
671 * @since 1.5.0
672 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
673 *
674 * @param string $domain          Unique identifier for retrieving translated strings
675 * @param string $deprecated      Optional. Use the $plugin_rel_path parameter instead. Defaukt false.
676 * @param string $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides.
677 *                                Default false.
678 * @return bool True when textdomain is successfully loaded, false otherwise.
679 */
680function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
681        /**
682         * Filters a plugin's locale.
683         *
684         * @since 3.0.0
685         *
686         * @param string $locale The plugin's current locale.
687         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
688         */
689        $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
690
691        $mofile = $domain . '-' . $locale . '.mo';
692
693        // Try to load from the languages directory first.
694        if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
695                return true;
696        }
697
698        if ( false !== $plugin_rel_path ) {
699                $path = WP_PLUGIN_DIR . '/' . trim( $plugin_rel_path, '/' );
700        } elseif ( false !== $deprecated ) {
701                _deprecated_argument( __FUNCTION__, '2.7.0' );
702                $path = ABSPATH . trim( $deprecated, '/' );
703        } else {
704                $path = WP_PLUGIN_DIR;
705        }
706
707        return load_textdomain( $domain, $path . '/' . $mofile );
708}
709
710/**
711 * Load the translated strings for a plugin residing in the mu-plugins directory.
712 *
713 * @since 3.0.0
714 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
715 *
716 * @param string $domain             Text domain. Unique identifier for retrieving translated strings.
717 * @param string $mu_plugin_rel_path Optional. Relative to `WPMU_PLUGIN_DIR` directory in which the .mo
718 *                                   file resides. Default empty string.
719 * @return bool True when textdomain is successfully loaded, false otherwise.
720 */
721function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
722        /** This filter is documented in wp-includes/l10n.php */
723        $locale = apply_filters( 'plugin_locale', get_locale(), $domain );
724
725        $mofile = $domain . '-' . $locale . '.mo';
726
727        // Try to load from the languages directory first.
728        if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
729                return true;
730        }
731
732        $path = trailingslashit( WPMU_PLUGIN_DIR . '/' . ltrim( $mu_plugin_rel_path, '/' ) );
733
734        return load_textdomain( $domain, $path . '/' . $mofile );
735}
736
737/**
738 * Load the theme's translated strings.
739 *
740 * If the current locale exists as a .mo file in the theme's root directory, it
741 * will be included in the translated strings by the $domain.
742 *
743 * The .mo files must be named based on the locale exactly.
744 *
745 * @since 1.5.0
746 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
747 *
748 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
749 * @param string $path   Optional. Path to the directory containing the .mo file.
750 *                       Default false.
751 * @return bool True when textdomain is successfully loaded, false otherwise.
752 */
753function load_theme_textdomain( $domain, $path = false ) {
754        /**
755         * Filters a theme's locale.
756         *
757         * @since 3.0.0
758         *
759         * @param string $locale The theme's current locale.
760         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
761         */
762        $locale = apply_filters( 'theme_locale', get_locale(), $domain );
763
764        $mofile = $domain . '-' . $locale . '.mo';
765
766        // Try to load from the languages directory first.
767        if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ) ) {
768                return true;
769        }
770
771        if ( ! $path ) {
772                $path = get_template_directory();
773        }
774
775        return load_textdomain( $domain, $path . '/' . $locale . '.mo' );
776}
777
778/**
779 * Load the child themes translated strings.
780 *
781 * If the current locale exists as a .mo file in the child themes
782 * root directory, it will be included in the translated strings by the $domain.
783 *
784 * The .mo files must be named based on the locale exactly.
785 *
786 * @since 2.9.0
787 *
788 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
789 * @param string $path   Optional. Path to the directory containing the .mo file.
790 *                       Default false.
791 * @return bool True when the theme textdomain is successfully loaded, false otherwise.
792 */
793function load_child_theme_textdomain( $domain, $path = false ) {
794        if ( ! $path )
795                $path = get_stylesheet_directory();
796        return load_theme_textdomain( $domain, $path );
797}
798
799/**
800 * Loads plugin and theme textdomains just-in-time.
801 *
802 * When a textdomain is encountered for the first time, we try to load
803 * the translation file from `wp-content/languages`, removing the need
804 * to call load_plugin_texdomain() or load_theme_texdomain().
805 *
806 * Holds a cached list of available .mo files to improve performance.
807 *
808 * @since 4.6.0
809 * @access private
810 *
811 * @see get_translations_for_domain()
812 *
813 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
814 * @return bool True when the textdomain is successfully loaded, false otherwise.
815 */
816function _load_textdomain_just_in_time( $domain ) {
817
818        static $cached_mofiles = null;
819
820        // Short-circuit if domain is 'default' which is reserved for core.
821        if ( 'default' === $domain || is_textdomain_unloaded( $domain ) ) {
822                return false;
823        }
824
825        if ( null === $cached_mofiles ) {
826                $cached_mofiles = array();
827
828                $locations = array(
829                        WP_LANG_DIR . '/plugins',
830                        WP_LANG_DIR . '/themes',
831                );
832
833                foreach ( $locations as $location ) {
834                        foreach ( get_available_languages( $location ) as $file ) {
835                                $cached_mofiles[] = "{$location}/{$file}.mo";
836                        }
837                }
838        }
839
840        $locale = get_locale();
841        $mofile = "{$domain}-{$locale}.mo";
842
843        if ( in_array( WP_LANG_DIR . '/plugins/' . $mofile, $cached_mofiles ) ) {
844                return load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile );
845        }
846
847        if ( in_array( WP_LANG_DIR . '/themes/' . $mofile, $cached_mofiles ) ) {
848                return load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile );
849        }
850
851        // if we've gotten this far, there are no translations for this domain
852        // so unload it to avoid further overhead
853        unload_textdomain( $domain );
854        return false;
855}
856
857/**
858 * Return the Translations instance for a text domain.
859 *
860 * If there isn't one, returns empty Translations instance.
861 *
862 * @since 2.8.0
863 *
864 * @global array $l10n
865 *
866 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
867 * @return NOOP_Translations A Translations instance.
868 */
869function get_translations_for_domain( $domain ) {
870        global $l10n;
871
872        if ( !is_textdomain_unloaded( $domain ) && ( isset( $l10n[ $domain ] ) || ( _load_textdomain_just_in_time( $domain ) && isset( $l10n[ $domain ] ) ) ) ) {
873                return $l10n[ $domain ];
874        }
875
876        static $noop_translations = null;
877        if ( null === $noop_translations ) {
878                $noop_translations = new NOOP_Translations;
879        }
880
881        return $noop_translations;
882}
883
884/**
885 * Whether there are translations for the text domain.
886 *
887 * @since 3.0.0
888 *
889 * @global array $l10n
890 *
891 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
892 * @return bool Whether there are translations.
893 */
894function is_textdomain_loaded( $domain ) {
895        global $l10n;
896        return isset( $l10n[ $domain ] );
897}
898
899/**
900 * Whether a textdomain has been unloaded.
901 *
902 * @since steph
903 *
904 * @global array $l10n_unloaded
905 *
906 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
907 * @return bool Whether domain has been unloaded.
908 */
909function is_textdomain_unloaded( $domain ) {
910        global $l10n_unloaded;
911        return isset( $l10n_unloaded[ $domain ] );
912}
913
914/**
915 * Translates role name.
916 *
917 * Since the role names are in the database and not in the source there
918 * are dummy gettext calls to get them into the POT file and this function
919 * properly translates them back.
920 *
921 * The before_last_bar() call is needed, because older installs keep the roles
922 * using the old context format: 'Role name|User role' and just skipping the
923 * content after the last bar is easier than fixing them in the DB. New installs
924 * won't suffer from that problem.
925 *
926 * @since 2.8.0
927 *
928 * @param string $name The role name.
929 * @return string Translated role name on success, original name on failure.
930 */
931function translate_user_role( $name ) {
932        return translate_with_gettext_context( before_last_bar($name), 'User role' );
933}
934
935/**
936 * Get all available languages based on the presence of *.mo files in a given directory.
937 *
938 * The default directory is WP_LANG_DIR.
939 *
940 * @since 3.0.0
941 *
942 * @param string $dir A directory to search for language files.
943 *                    Default WP_LANG_DIR.
944 * @return array An array of language codes or an empty array if no languages are present. Language codes are formed by stripping the .mo extension from the language file names.
945 */
946function get_available_languages( $dir = null ) {
947        $languages = array();
948
949        $lang_files = glob( ( is_null( $dir) ? WP_LANG_DIR : $dir ) . '/*.mo' );
950        if ( $lang_files ) {
951                foreach ( $lang_files as $lang_file ) {
952                        $lang_file = basename( $lang_file, '.mo' );
953                        if ( 0 !== strpos( $lang_file, 'continents-cities' ) && 0 !== strpos( $lang_file, 'ms-' ) &&
954                                0 !== strpos( $lang_file, 'admin-' ) ) {
955                                $languages[] = $lang_file;
956                        }
957                }
958        }
959
960        return $languages;
961}
962
963/**
964 * Get installed translations.
965 *
966 * Looks in the wp-content/languages directory for translations of
967 * plugins or themes.
968 *
969 * @since 3.7.0
970 *
971 * @param string $type What to search for. Accepts 'plugins', 'themes', 'core'.
972 * @return array Array of language data.
973 */
974function wp_get_installed_translations( $type ) {
975        if ( $type !== 'themes' && $type !== 'plugins' && $type !== 'core' )
976                return array();
977
978        $dir = 'core' === $type ? '' : "/$type";
979
980        if ( ! is_dir( WP_LANG_DIR ) )
981                return array();
982
983        if ( $dir && ! is_dir( WP_LANG_DIR . $dir ) )
984                return array();
985
986        $files = scandir( WP_LANG_DIR . $dir );
987        if ( ! $files )
988                return array();
989
990        $language_data = array();
991
992        foreach ( $files as $file ) {
993                if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "$dir/$file" ) ) {
994                        continue;
995                }
996                if ( substr( $file, -3 ) !== '.po' ) {
997                        continue;
998                }
999                if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
1000                        continue;
1001                }
1002                if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) )  {
1003                        continue;
1004                }
1005
1006                list( , $textdomain, $language ) = $match;
1007                if ( '' === $textdomain ) {
1008                        $textdomain = 'default';
1009                }
1010                $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "$dir/$file" );
1011        }
1012        return $language_data;
1013}
1014
1015/**
1016 * Extract headers from a PO file.
1017 *
1018 * @since 3.7.0
1019 *
1020 * @param string $po_file Path to PO file.
1021 * @return array PO file headers.
1022 */
1023function wp_get_pomo_file_data( $po_file ) {
1024        $headers = get_file_data( $po_file, array(
1025                'POT-Creation-Date'  => '"POT-Creation-Date',
1026                'PO-Revision-Date'   => '"PO-Revision-Date',
1027                'Project-Id-Version' => '"Project-Id-Version',
1028                'X-Generator'        => '"X-Generator',
1029        ) );
1030        foreach ( $headers as $header => $value ) {
1031                // Remove possible contextual '\n' and closing double quote.
1032                $headers[ $header ] = preg_replace( '~(\\\n)?"$~', '', $value );
1033        }
1034        return $headers;
1035}
1036
1037/**
1038 * Language selector.
1039 *
1040 * @since 4.0.0
1041 * @since 4.3.0 Introduced the `echo` argument.
1042 *
1043 * @see get_available_languages()
1044 * @see wp_get_available_translations()
1045 *
1046 * @param string|array $args {
1047 *     Optional. Array or string of arguments for outputting the language selector.
1048 *
1049 *     @type string   $id                           ID attribute of the select element. Default empty.
1050 *     @type string   $name                         Name attribute of the select element. Default empty.
1051 *     @type array    $languages                    List of installed languages, contain only the locales.
1052 *                                                  Default empty array.
1053 *     @type array    $translations                 List of available translations. Default result of
1054 *                                                  wp_get_available_translations().
1055 *     @type string   $selected                     Language which should be selected. Default empty.
1056 *     @type bool|int $echo                         Whether to echo the generated markup. Accepts 0, 1, or their
1057 *                                                  boolean equivalents. Default 1.
1058 *     @type bool     $show_available_translations  Whether to show available translations. Default true.
1059 * }
1060 * @return string HTML content
1061 */
1062function wp_dropdown_languages( $args = array() ) {
1063
1064        $args = wp_parse_args( $args, array(
1065                'id'           => '',
1066                'name'         => '',
1067                'languages'    => array(),
1068                'translations' => array(),
1069                'selected'     => '',
1070                'echo'         => 1,
1071                'show_available_translations' => true,
1072        ) );
1073
1074        $translations = $args['translations'];
1075        if ( empty( $translations ) ) {
1076                require_once( ABSPATH . 'wp-admin/includes/translation-install.php' );
1077                $translations = wp_get_available_translations();
1078        }
1079
1080        /*
1081         * $args['languages'] should only contain the locales. Find the locale in
1082         * $translations to get the native name. Fall back to locale.
1083         */
1084        $languages = array();
1085        foreach ( $args['languages'] as $locale ) {
1086                if ( isset( $translations[ $locale ] ) ) {
1087                        $translation = $translations[ $locale ];
1088                        $languages[] = array(
1089                                'language'    => $translation['language'],
1090                                'native_name' => $translation['native_name'],
1091                                'lang'        => current( $translation['iso'] ),
1092                        );
1093
1094                        // Remove installed language from available translations.
1095                        unset( $translations[ $locale ] );
1096                } else {
1097                        $languages[] = array(
1098                                'language'    => $locale,
1099                                'native_name' => $locale,
1100                                'lang'        => '',
1101                        );
1102                }
1103        }
1104
1105        $translations_available = ( ! empty( $translations ) && $args['show_available_translations'] );
1106
1107        $output = sprintf( '<select name="%s" id="%s">', esc_attr( $args['name'] ), esc_attr( $args['id'] ) );
1108
1109        // Holds the HTML markup.
1110        $structure = array();
1111
1112        // List installed languages.
1113        if ( $translations_available ) {
1114                $structure[] = '<optgroup label="' . esc_attr_x( 'Installed', 'translations' ) . '">';
1115        }
1116        $structure[] = '<option value="" lang="en" data-installed="1">English (United States)</option>';
1117        foreach ( $languages as $language ) {
1118                $structure[] = sprintf(
1119                        '<option value="%s" lang="%s"%s data-installed="1">%s</option>',
1120                        esc_attr( $language['language'] ),
1121                        esc_attr( $language['lang'] ),
1122                        selected( $language['language'], $args['selected'], false ),
1123                        esc_html( $language['native_name'] )
1124                );
1125        }
1126        if ( $translations_available ) {
1127                $structure[] = '</optgroup>';
1128        }
1129
1130        // List available translations.
1131        if ( $translations_available ) {
1132                $structure[] = '<optgroup label="' . esc_attr_x( 'Available', 'translations' ) . '">';
1133                foreach ( $translations as $translation ) {
1134                        $structure[] = sprintf(
1135                                '<option value="%s" lang="%s"%s>%s</option>',
1136                                esc_attr( $translation['language'] ),
1137                                esc_attr( current( $translation['iso'] ) ),
1138                                selected( $translation['language'], $args['selected'], false ),
1139                                esc_html( $translation['native_name'] )
1140                        );
1141                }
1142                $structure[] = '</optgroup>';
1143        }
1144
1145        $output .= join( "\n", $structure );
1146
1147        $output .= '</select>';
1148
1149        if ( $args['echo'] ) {
1150                echo $output;
1151        }
1152
1153        return $output;
1154}