Make WordPress Core

Ticket #41305: 41305.implementation

File 41305.implementation, 14.6 KB (added by schlessera, 9 years ago)

Initial implementation. This sets everything up, but does not any benefits yet because it is not being consumed by any other code.

Line 
1diff --git src/wp-includes/l10n.php src/wp-includes/l10n.php
2index a81470ff7d..917e402ec8 100644
3--- src/wp-includes/l10n.php
4+++ src/wp-includes/l10n.php
5@@ -203,6 +203,24 @@ function __( $text, $domain = 'default' ) {
6 }
7
8 /**
9+ * Lazily retrieves the translation of $text.
10+ *
11+ * If there is no translation, or the text domain isn't loaded, the original
12+ * text is returned.
13+ *
14+ * @since 4.9.0
15+ *
16+ * @param string $text Text to translate.
17+ * @param string $domain Optional. Text domain. Unique identifier for
18+ * retrieving translated strings. Default 'default'.
19+ *
20+ * @return WP_String_Proxy Proxy object representing a string.
21+ */
22+function _l( $text, $domain = 'default' ) {
23+ return new WP_Translation_Proxy( $text, $domain );
24+}
25+
26+/**
27 * Retrieve the translation of $text and escapes it for safe use in an attribute.
28 *
29 * If there is no translation, or the text domain isn't loaded, the original text is returned.
30@@ -219,6 +237,27 @@ function esc_attr__( $text, $domain = 'default' ) {
31 }
32
33 /**
34+ * Lazily retrieve the translation of $text and escape it for safe use in an
35+ * attribute.
36+ *
37+ * If there is no translation, or the text domain isn't loaded, the original
38+ * text is returned.
39+ *
40+ * @since 4.9.0
41+ *
42+ * @param string $text Text to translate.
43+ * @param string $domain Optional. Text domain. Unique identifier for
44+ * retrieving translated strings. Default 'default'.
45+ *
46+ * @return WP_String_Proxy Proxy object representing a string.
47+ */
48+function esc_attr_l( $text, $domain = 'default' ) {
49+ return new WP_Escape_Attribute_Proxy(
50+ new WP_Translation_Proxy( $text, $domain )
51+ );
52+}
53+
54+/**
55 * Retrieve the translation of $text and escapes it for safe use in HTML output.
56 *
57 * If there is no translation, or the text domain isn't loaded, the original text
58@@ -236,6 +275,27 @@ function esc_html__( $text, $domain = 'default' ) {
59 }
60
61 /**
62+ * Lazily retrieve the translation of $text and escape it for safe use in HTML
63+ * output.
64+ *
65+ * If there is no translation, or the text domain isn't loaded, the original
66+ * text is escaped and returned..
67+ *
68+ * @since 4.9.0
69+ *
70+ * @param string $text Text to translate.
71+ * @param string $domain Optional. Text domain. Unique identifier for
72+ * retrieving translated strings. Default 'default'.
73+ *
74+ * @return WP_String_Proxy Proxy object representing a string.
75+ */
76+function esc_html_l( $text, $domain = 'default' ) {
77+ return new WP_Escape_HTML_Proxy(
78+ new WP_Translation_Proxy( $text, $domain )
79+ );
80+}
81+
82+/**
83 * Display translated text.
84 *
85 * @since 1.2.0
86@@ -296,6 +356,28 @@ function _x( $text, $context, $domain = 'default' ) {
87 }
88
89 /**
90+ * Lazily retrieve translated string with gettext context.
91+ *
92+ * Quite a few times, there will be collisions with similar translatable text
93+ * found in more than two places, but with different translated context.
94+ *
95+ * By including the context in the pot file, translators can translate the two
96+ * strings differently.
97+ *
98+ * @since 4.9.0
99+ *
100+ * @param string $text Text to translate.
101+ * @param string $context Context information for the translators.
102+ * @param string $domain Optional. Text domain. Unique identifier for
103+ * retrieving translated strings. Default 'default'.
104+ *
105+ * @return WP_String_Proxy Proxy object representing a string.
106+ */
107+function _lx( $text, $context, $domain = 'default' ) {
108+ return new WP_Contextual_Translation_Proxy( $text, $context, $domain );
109+}
110+
111+/**
112 * Display translated string with gettext context.
113 *
114 * @since 3.0.0
115@@ -326,6 +408,25 @@ function esc_attr_x( $text, $context, $domain = 'default' ) {
116 }
117
118 /**
119+ * Lazily translate string with gettext context, and escape it for safe use in
120+ * an attribute.
121+ *
122+ * @since 4.9.0
123+ *
124+ * @param string $text Text to translate.
125+ * @param string $context Context information for the translators.
126+ * @param string $domain Optional. Text domain. Unique identifier for
127+ * retrieving translated strings. Default 'default'.
128+ *
129+ * @return WP_String_Proxy Proxy object representing a string.
130+ */
131+function esc_attr_lx( $text, $context, $domain = 'default' ) {
132+ return new WP_Escape_Attribute_Proxy(
133+ new WP_Contextual_Translation_Proxy( $text, $context, $domain )
134+ );
135+}
136+
137+/**
138 * Translate string with gettext context, and escapes it for safe use in HTML output.
139 *
140 * @since 2.9.0
141@@ -341,6 +442,25 @@ function esc_html_x( $text, $context, $domain = 'default' ) {
142 }
143
144 /**
145+ * Lazily translate string with gettext context, and escape it for safe use in
146+ * HTML output.
147+ *
148+ * @since 4.9.0
149+ *
150+ * @param string $text Text to translate.
151+ * @param string $context Context information for the translators.
152+ * @param string $domain Optional. Text domain. Unique identifier for
153+ * retrieving translated strings. Default 'default'.
154+ *
155+ * @return WP_String_Proxy Proxy object representing a string.
156+ */
157+function esc_html_lx( $text, $context, $domain = 'default' ) {
158+ return new WP_Escape_HTML_Proxy(
159+ new WP_Contextual_Translation_Proxy( $text, $context, $domain )
160+ );
161+}
162+
163+/**
164 * Translates and retrieves the singular or plural form based on the supplied number.
165 *
166 * Used when you want to use the appropriate form of a string based on whether a
167diff --git src/wp-includes/l10n/class-wp-contextual_translation-proxy.php src/wp-includes/l10n/class-wp-contextual_translation-proxy.php
168new file mode 100644
169index 0000000000..832caf4839
170--- /dev/null
171+++ src/wp-includes/l10n/class-wp-contextual_translation-proxy.php
172@@ -0,0 +1,45 @@
173+<?php
174+
175+/**
176+ * L10n: WP_Contextual_Translation_Proxy class.
177+ *
178+ * @package WordPress
179+ * @subpackage L10n
180+ * @since 4.9.0
181+ */
182+final class WP_Contextual_Translation_Proxy extends WP_String_Proxy {
183+
184+ private $text;
185+ private $context;
186+ private $domain;
187+
188+ /**
189+ * Instantiate a WP_Translation_Proxy object.
190+ *
191+ * @since 4.9.0
192+ *
193+ * @param string $text Text to translate.
194+ * @param string $context Context of the test to translate.
195+ * @param string $domain Optional. Text domain to use for the translation.
196+ */
197+ public function __construct( $text, $context, $domain = 'default' ) {
198+ $this->text = $text;
199+ $this->context = $context;
200+ $this->domain = $domain;
201+ }
202+
203+ /**
204+ * Get the result of evaluating the string proxy object.
205+ *
206+ * @since 4.9.0
207+ *
208+ * @return string
209+ */
210+ protected function get_result() {
211+ return translate_with_gettext_context(
212+ $this->text,
213+ $this->context,
214+ $this->domain
215+ );
216+ }
217+}
218diff --git src/wp-includes/l10n/class-wp-escape-attribute-proxy.php src/wp-includes/l10n/class-wp-escape-attribute-proxy.php
219new file mode 100644
220index 0000000000..cbcea2ee0b
221--- /dev/null
222+++ src/wp-includes/l10n/class-wp-escape-attribute-proxy.php
223@@ -0,0 +1,36 @@
224+<?php
225+
226+/**
227+ * L10n: WP_Escape_Attribute_Proxy class.
228+ *
229+ * @package WordPress
230+ * @subpackage L10n
231+ * @since 4.9.0
232+ */
233+final class WP_Escape_Attribute_Proxy extends WP_String_Proxy {
234+
235+ private $value;
236+
237+ /**
238+ * Instantiate a WP_Translation_Proxy object.
239+ *
240+ * @since 4.9.0
241+ *
242+ * @param mixed $value Value to be escaped. Needs to be castable to a
243+ * string.
244+ */
245+ public function __construct( $value ) {
246+ $this->value = $value;
247+ }
248+
249+ /**
250+ * Get the result of evaluating the string proxy object.
251+ *
252+ * @since 4.9.0
253+ *
254+ * @return string
255+ */
256+ protected function get_result() {
257+ return esc_attr( (string) $this->value );
258+ }
259+}
260diff --git src/wp-includes/l10n/class-wp-escape-html-proxy.php src/wp-includes/l10n/class-wp-escape-html-proxy.php
261new file mode 100644
262index 0000000000..97e73999f2
263--- /dev/null
264+++ src/wp-includes/l10n/class-wp-escape-html-proxy.php
265@@ -0,0 +1,36 @@
266+<?php
267+
268+/**
269+ * L10n: WP_Escape_HTML_Proxy class.
270+ *
271+ * @package WordPress
272+ * @subpackage L10n
273+ * @since 4.9.0
274+ */
275+final class WP_Escape_HTML_Proxy extends WP_String_Proxy {
276+
277+ private $value;
278+
279+ /**
280+ * Instantiate a WP_Translation_Proxy object.
281+ *
282+ * @since 4.9.0
283+ *
284+ * @param mixed $value Value to be escaped. Needs to be castable to a
285+ * string.
286+ */
287+ public function __construct( $value ) {
288+ $this->value = $value;
289+ }
290+
291+ /**
292+ * Get the result of evaluating the string proxy object.
293+ *
294+ * @since 4.9.0
295+ *
296+ * @return string
297+ */
298+ protected function get_result() {
299+ return esc_html( (string) $this->value );
300+ }
301+}
302diff --git src/wp-includes/l10n/class-wp-string-proxy.php src/wp-includes/l10n/class-wp-string-proxy.php
303new file mode 100644
304index 0000000000..3795c0b9a7
305--- /dev/null
306+++ src/wp-includes/l10n/class-wp-string-proxy.php
307@@ -0,0 +1,130 @@
308+<?php
309+
310+/**
311+ * L10n: WP_String_Proxy class.
312+ *
313+ * Implements `JsonSerializable` interface so that it gets converted to a
314+ * translated string on `json_encode()`.
315+ *
316+ * Implements the `ArrayAccess` interface so that you can directly access
317+ * individual characters in the translated string
318+ *
319+ * Uses magic `__get()` to make sure the result is only generated once.
320+ *
321+ * @package WordPress
322+ * @subpackage L10n
323+ * @since 4.9.0
324+ */
325+abstract class WP_String_Proxy implements JsonSerializable, ArrayAccess {
326+
327+ /**
328+ * Return the string representation of the proxy object.
329+ *
330+ * @since 4.9.0
331+ *
332+ * @return string
333+ */
334+ public function __toString() {
335+ return $this->result;
336+ }
337+
338+ /**
339+ * Return the JSON representation of the proxy object.
340+ *
341+ * @since 4.9.0
342+ *
343+ * @return string
344+ */
345+ public function jsonSerialize() {
346+ return $this->result;
347+ }
348+
349+ /**
350+ * Lazily evaluate the `result` property the first time it is being
351+ * requested.
352+ *
353+ * The property is then set to the resulting value, so that the
354+ * `__get()` magic method will not be called again.
355+ *
356+ * @since 4.9.0
357+ *
358+ * @param string $property Property that was requested.
359+ *
360+ * @return string
361+ */
362+ public function __get( $property ) {
363+ if ( 'result' === $property ) {
364+ $this->result = $this->get_result();
365+
366+ return $this->result;
367+ }
368+
369+ $message = sprintf(
370+ 'Undefined property: %s::$%s',
371+ get_class(),
372+ $property
373+ );
374+
375+ trigger_error( $message, E_USER_NOTICE );
376+
377+ return null;
378+ }
379+
380+ /**
381+ * Check whether an offset into the array exists.
382+ *
383+ * @since 4.9.0
384+ *
385+ * @param mixed $offset Offset to check for.
386+ *
387+ * @return bool
388+ */
389+ public function offsetExists( $offset ) {
390+ return mb_strlen( $this->result ) > $offset;
391+ }
392+
393+ /**
394+ * Retrieve a specific offset into the array.
395+ *
396+ * @since 4.9.0
397+ *
398+ * @param mixed $offset The offset to retrieve.
399+ *
400+ * @return mixed
401+ */
402+ public function offsetGet( $offset ) {
403+ return $this->result[ $offset ];
404+ }
405+
406+ /**
407+ * Set a specific offset in the array.
408+ *
409+ * @since 4.9.0
410+ *
411+ * @param mixed $offset The offset to assign the value to.
412+ * @param mixed $value The value to set the offset to.
413+ */
414+ public function offsetSet( $offset, $value ) {
415+ $this->result[ $offset ] = $value;
416+ }
417+
418+ /**
419+ * Unset a specific offset in the array.
420+ *
421+ * @since 4.9.0
422+ *
423+ * @param mixed $offset The offset to unset.
424+ */
425+ public function offsetUnset( $offset ) {
426+ trigger_error( 'Cannot unset string offset', E_USER_ERROR );
427+ }
428+
429+ /**
430+ * Get the result of evaluating the string proxy object.
431+ *
432+ * @since 4.9.0
433+ *
434+ * @return string
435+ */
436+ abstract protected function get_result();
437+}
438diff --git src/wp-includes/l10n/class-wp-translation-proxy.php src/wp-includes/l10n/class-wp-translation-proxy.php
439new file mode 100644
440index 0000000000..988eec097f
441--- /dev/null
442+++ src/wp-includes/l10n/class-wp-translation-proxy.php
443@@ -0,0 +1,38 @@
444+<?php
445+
446+/**
447+ * L10n: WP_Translation_Proxy class.
448+ *
449+ * @package WordPress
450+ * @subpackage L10n
451+ * @since 4.9.0
452+ */
453+final class WP_Translation_Proxy extends WP_String_Proxy {
454+
455+ private $text;
456+ private $domain;
457+
458+ /**
459+ * Instantiate a WP_Translation_Proxy object.
460+ *
461+ * @since 4.9.0
462+ *
463+ * @param string $text Text to translate.
464+ * @param string $domain Optional. Text domain to use for the translation.
465+ */
466+ public function __construct( $text, $domain = 'default' ) {
467+ $this->text = $text;
468+ $this->domain = $domain;
469+ }
470+
471+ /**
472+ * Get the result of evaluating the string proxy object.
473+ *
474+ * @since 4.9.0
475+ *
476+ * @return string
477+ */
478+ protected function get_result() {
479+ return translate( $this->text, $this->domain );
480+ }
481+}
482diff --git src/wp-settings.php src/wp-settings.php
483index d1505c502d..748aa7a99d 100644
484--- src/wp-settings.php
485+++ src/wp-settings.php
486@@ -239,6 +239,11 @@ require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.p
487 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );
488 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-term-meta-fields.php' );
489 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-user-meta-fields.php' );
490+require( ABSPATH . WPINC . '/l10n/class-wp-string-proxy.php' );
491+require( ABSPATH . WPINC . '/l10n/class-wp-translation-proxy.php' );
492+require( ABSPATH . WPINC . '/l10n/class-wp-contextual_translation-proxy.php' );
493+require( ABSPATH . WPINC . '/l10n/class-wp-escape-attribute-proxy.php' );
494+require( ABSPATH . WPINC . '/l10n/class-wp-escape-html-proxy.php' );
495
496 $GLOBALS['wp_embed'] = new WP_Embed();
497
498diff --git tools/i18n/extract.php tools/i18n/extract.php
499index 023b28bc69..0449cc7aca 100644
500--- tools/i18n/extract.php
501+++ tools/i18n/extract.php
502@@ -12,6 +12,7 @@ class StringExtractor {
503 var $rules = array(
504 '__' => array( 'string' ),
505 '_e' => array( 'string' ),
506+ '_l' => array( 'string' ),
507 '_n' => array( 'singular', 'plural' ),
508 );
509 var $comment_prefix = 'translators:';
510diff --git tools/i18n/makepot.php tools/i18n/makepot.php
511index 75b95e1bd2..0f4d76aca0 100644
512--- tools/i18n/makepot.php
513+++ tools/i18n/makepot.php
514@@ -44,6 +44,7 @@ class MakePOT {
515 '__' => array('string'),
516 '_e' => array('string'),
517 '_c' => array('string'),
518+ '_l' => array('string'),
519 '_n' => array('singular', 'plural'),
520 '_n_noop' => array('singular', 'plural'),
521 '_nc' => array('singular', 'plural'),
522@@ -51,6 +52,7 @@ class MakePOT {
523 '__ngettext_noop' => array('singular', 'plural'),
524 '_x' => array('string', 'context'),
525 '_ex' => array('string', 'context'),
526+ '_lx' => array('string', 'context'),
527 '_nx' => array('singular', 'plural', null, 'context'),
528 '_nx_noop' => array('singular', 'plural', 'context'),
529 '_n_js' => array('singular', 'plural'),
530@@ -59,6 +61,8 @@ class MakePOT {
531 'esc_html__' => array('string'),
532 'esc_attr_e' => array('string'),
533 'esc_html_e' => array('string'),
534+ 'esc_attr_l' => array('string'),
535+ 'esc_html_l' => array('string'),
536 'esc_attr_x' => array('string', 'context'),
537 'esc_html_x' => array('string', 'context'),
538 'comments_number_link' => array('string', 'singular', 'plural'),