Eloquence has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/314679

Change subject: Support for CLDR plural keywords in JSON file format
......................................................................

Support for CLDR plural keywords in JSON file format

This adds a configuration flag, parseCLDRPlurals, for the JSON
format. If set to yes, we will flatten/unflatten plural keywords
in the JSON source similarly to how it's already done for YAML,
using largely identical code (but also supporting the separator
option).

This makes redundant the YAML subclass implementation, which will
be refactored out in a follow-up change.

Change-Id: I15a9fb4a7f01f7ddb2f0b64c3a81062219dc2ac9
---
M ffs/JsonFFS.php
M utils/ArrayFlattener.php
2 files changed, 212 insertions(+), 16 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Translate 
refs/changes/79/314679/1

diff --git a/ffs/JsonFFS.php b/ffs/JsonFFS.php
index 9e89d19..b94a7ba 100644
--- a/ffs/JsonFFS.php
+++ b/ffs/JsonFFS.php
@@ -49,8 +49,11 @@
                unset( $messages['@metadata'] );
 
                if ( isset( $this->extra['nestingSeparator'] ) ) {
-                        $flattener = new ArrayFlattener( 
$this->extra['nestingSeparator'] );
-                        $messages = $flattener->flatten( $messages );
+                       $parseCLDRPlurals = isset( 
$this->extra['parseCLDRPlurals'] ) ?
+                               $this->extra['parseCLDRPlurals'] : false;
+                       $flattener = new ArrayFlattener( 
$this->extra['nestingSeparator'],
+                               $parseCLDRPlurals );
+                       $messages = $flattener->flatten( $messages );
                }
 
                $messages = $this->group->getMangler()->mangle( $messages );
@@ -111,8 +114,11 @@
                }
 
                if ( isset( $this->extra['nestingSeparator'] ) ) {
-                        $flattener = new ArrayFlattener( 
$this->extra['nestingSeparator'] );
-                        $messages = $flattener->unflatten( $messages );
+                       $parseCLDRPlurals = isset( 
$this->extra['parseCLDRPlurals'] ) ?
+                               $this->extra['parseCLDRPlurals'] : false;
+                       $flattener = new ArrayFlattener( 
$this->extra['nestingSeparator'],
+                               $parseCLDRPlurals );
+                       $messages = $flattener->unflatten( $messages );
                }
 
                return FormatJson::encode( $messages, "\t", FormatJson::ALL_OK 
) . "\n";
@@ -129,6 +135,9 @@
                                                        'nestingSeparator' => 
array(
                                                                '_type' => 
'text',
                                                        ),
+                                                       'parseCLDRPlurals' => 
array(
+                                                               '_type' => 
'boolean',
+                                                       )
                                                )
                                        )
                                )
diff --git a/utils/ArrayFlattener.php b/utils/ArrayFlattener.php
index 8845ef1..38fa4fb 100644
--- a/utils/ArrayFlattener.php
+++ b/utils/ArrayFlattener.php
@@ -1,9 +1,11 @@
 <?php
 /**
- * Support for JSON message file format.
+ * Flattens message arrays for further processing. Supports parsing CLDR
+ * plural messages and converting them into MediaWiki's {{PLURAL}} syntax
+ * in a single message.
  *
  * @file
- * @author Niklas Laxström
+ * @author Niklas Laxström, Erik Moeller
  * @license GPL-2.0+
  * @since 2016.01
  */
@@ -11,14 +13,25 @@
 class ArrayFlattener {
        protected $sep;
 
-       public function __construct( $sep = '.' ) {
+       // For CLDR pluralization rules
+       protected static $pluralWords = array(
+               'zero' => 1,
+               'one' => 1,
+               'many' => 1,
+               'few' => 1,
+               'other' => 1,
+               'two' => 1
+       );
+
+       public function __construct( $sep = '.', $parseCLDRPlurals = false ) {
                $this->sep = $sep;
+               $this->parseCLDRPlurals = $parseCLDRPlurals;
        }
 
        /**
         * Flattens multidimensional array.
         *
-        * @param array $unflat It's an array.
+        * @param array $unflat Array of messages
         * @return array
         */
        public function flatten( array $unflat ) {
@@ -30,14 +43,21 @@
                                continue;
                        }
 
-                       // Placeholder for special plural processing
-
-                       $temp = array();
-                       foreach ( $value as $subKey => $subValue ) {
-                               $newKey = "$key{$this->sep}$subKey";
-                               $temp[$newKey] = $subValue;
+                       $plurals = false;
+                       if ( $this->parseCLDRPlurals ) {
+                               $plurals = $this->flattenCLDRPlurals( $value );
                        }
-                       $flat += $this->flatten( $temp );
+
+                       if ( $this->parseCLDRPlurals && $plurals ) {
+                               $flat[$key] = $plurals;
+                       } else {
+                               $temp = array();
+                               foreach ( $value as $subKey => $subValue ) {
+                                       $newKey = "$key{$this->sep}$subKey";
+                                       $temp[$newKey] = $subValue;
+                               }
+                               $flat += $this->flatten( $temp );
+                       }
 
                        // Can as well keep only one copy around.
                        unset( $unflat[$key] );
@@ -47,15 +67,87 @@
        }
 
        /**
+        * Flattens arrays that contain CLDR plural keywords into single values 
using
+        * MediaWiki's plural syntax.
+        *
+        * @param array $messages Array of messages
+        *
+        * @throws MWException
+        * @return bool|string
+        */
+       public function flattenCLDRPlurals( $messages ) {
+
+               $pluralKeys = false;
+               $nonPluralKeys = false;
+               foreach ( $messages as $key => $value ) {
+                       if ( is_array( $value ) ) {
+                               // Plurals can only happen in the lowest level 
of the structure
+                               return false;
+                       }
+
+                       // Check if we find any reserved plural keyword
+                       if ( isset( self::$pluralWords[$key] ) ) {
+                               $pluralKeys = true;
+                       } else {
+                               $nonPluralKeys = true;
+                       }
+               }
+
+               // No plural keys at all, we can skip
+               if ( !$pluralKeys ) {
+                       return false;
+               }
+
+               // Mixed plural keys with other keys, should not happen
+               if ( $nonPluralKeys ) {
+                       $keys = implode( ', ', array_keys( $messages ) );
+                       throw new MWException( "Reserved plural keywords mixed 
with other keys: $keys." );
+               }
+
+               $pls = '{{PLURAL';
+               foreach ( $messages as $key => $value ) {
+                       if ( $key === 'other' ) {
+                               continue;
+                       }
+
+                       $pls .= "|$key=$value";
+               }
+
+               // Put the "other" alternative last, without other= prefix.
+               $other = isset( $messages['other'] ) ? '|' . $messages['other'] 
: '';
+               $pls .= "$other}}";
+
+               return $pls;
+       }
+
+       /**
         * Performs the reverse operation of flatten.
         *
-        * @param array $flat It's an array
+        * @param array $flat Array of messages
         * @return array
         */
        public function unflatten( $flat ) {
+
                $unflat = array();
 
+               if ( $this->parseCLDRPlurals ) {
+                       $unflattenedPlurals = array();
+                       foreach ( $flat as $key => $value ) {
+                               $plurals = false;
+                               if ( !is_array( $value ) ) {
+                                       $plurals = $this->unflattenCLDRPlurals( 
$key, $value );
+                               }
+                               if ( $plurals ) {
+                                       $unflattenedPlurals += $plurals;
+                               } else {
+                                       $unflattenedPlurals[$key] = $value;
+                               }
+                       }
+                       $flat = $unflattenedPlurals;
+               }
+
                foreach ( $flat as $key => $value ) {
+
                        $path = explode( $this->sep, $key );
                        if ( count( $path ) === 1 ) {
                                $unflat[$key] = $value;
@@ -86,4 +178,99 @@
 
                return $unflat;
        }
+
+       /**
+        * Converts the MediaWiki plural syntax to array of CLDR style plurals
+        *
+        * @param string $key Message key prefix
+        * @param string $message The plural string
+        *
+        * @return bool|array
+        */
+       public function unflattenCLDRPlurals( $key, $message ) {
+
+               // Quick escape.
+               if ( strpos( $message, '{{PLURAL' ) === false ) {
+                       return false;
+               }
+
+               /*
+                * Replace all variables with placeholders. Possible source of 
bugs
+                * if other characters that given below are used.
+                */
+               $regex = '~\{[a-zA-Z_-]+}~';
+               $placeholders = array();
+               $match = array();
+
+               while ( preg_match( $regex, $message, $match ) ) {
+                       $uniqkey = TranslateUtils::getPlaceholder();
+                       $placeholders[$uniqkey] = $match[0];
+                       $search = preg_quote( $match[0], '~' );
+                       $message = preg_replace( "~$search~", $uniqkey, 
$message );
+               }
+
+               // Then replace (possible multiple) plural instances into 
placeholders.
+               $regex = '~\{\{PLURAL\|(.*?)}}~s';
+               $matches = array();
+               $match = array();
+
+               while ( preg_match( $regex, $message, $match ) ) {
+                       $uniqkey = TranslateUtils::getPlaceholder();
+                       $matches[$uniqkey] = $match;
+                       $message = preg_replace( $regex, $uniqkey, $message, 1 
);
+               }
+
+               // No plurals, should not happen.
+               if ( !count( $matches ) ) {
+                       return false;
+               }
+
+               // The final array of alternative plurals forms.
+               $alts = array();
+
+               /*
+                * Then loop trough each plural block and replacing the 
placeholders
+                * to construct the alternatives. Produces invalid output if 
there is
+                * multiple plural bocks which don't have the same set of keys.
+                */
+               $pluralChoice = implode( '|', array_keys( self::$pluralWords ) 
);
+               $regex = "~($pluralChoice)\s*=\s*(.+)~s";
+               foreach ( $matches as $ph => $plu ) {
+                       $forms = explode( '|', $plu[1] );
+
+                       foreach ( $forms as $form ) {
+                               if ( $form === '' ) {
+                                       continue;
+                               }
+
+                               $match = array();
+                               if ( preg_match( $regex, $form, $match ) ) {
+                                       $formWord = 
"$key{$this->sep}{$match[1]}";
+                                       $value = $match[2];
+                               } else {
+                                       $formWord = "$key{$this->sep}other";
+                                       $value = $form;
+                               }
+
+                               if ( !isset( $alts[$formWord] ) ) {
+                                       $alts[$formWord] = $message;
+                               }
+
+                               $string = $alts[$formWord];
+                               $alts[$formWord] = str_replace( $ph, $value, 
$string );
+                       }
+               }
+
+               // Replace other variables.
+               foreach ( $alts as &$value ) {
+                       $value = str_replace( array_keys( $placeholders ), 
array_values( $placeholders ), $value );
+               }
+
+               if ( !isset( $alts["$key{$this->sep}other"] ) ) {
+                       wfWarn( "Other not set for key $key" );
+               }
+
+               return $alts;
+       }
+
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/314679
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I15a9fb4a7f01f7ddb2f0b64c3a81062219dc2ac9
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Translate
Gerrit-Branch: master
Gerrit-Owner: Eloquence <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to