WordPress.org

Make WordPress Core

Ticket #41305: 41305.implementation

File 41305.implementation, 14.6 KB (added by schlessera, 2 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'),