[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/maintenance/ -> generateJsonI18n.php (source)

   1  <?php
   2  
   3  /**
   4   * Convert a PHP messages file to a set of JSON messages files.
   5   *
   6   * Usage:
   7   *    php generateJsonI18n.php ExtensionName.i18n.php i18n/
   8   *
   9   * This program is free software; you can redistribute it and/or modify
  10   * it under the terms of the GNU General Public License as published by
  11   * the Free Software Foundation; either version 2 of the License, or
  12   * (at your option) any later version.
  13   *
  14   * This program is distributed in the hope that it will be useful,
  15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17   * GNU General Public License for more details.
  18   *
  19   * You should have received a copy of the GNU General Public License along
  20   * with this program; if not, write to the Free Software Foundation, Inc.,
  21   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22   * http://www.gnu.org/copyleft/gpl.html
  23   *
  24   * @file
  25   * @ingroup Maintenance
  26   */
  27  
  28  require_once  __DIR__ . '/Maintenance.php';
  29  
  30  /**
  31   * Maintenance script to generate JSON i18n files from a PHP i18n file.
  32   *
  33   * @ingroup Maintenance
  34   */
  35  class GenerateJsonI18n extends Maintenance {
  36  	public function __construct() {
  37          parent::__construct();
  38          $this->mDescription = "Build JSON messages files from a PHP messages file";
  39  
  40          $this->addArg( 'phpfile', 'PHP file defining a $messages array', false );
  41          $this->addArg( 'jsondir', 'Directory to write JSON files to', false );
  42          $this->addOption( 'langcode', 'Language code; only needed for converting core i18n files',
  43              false, true );
  44          $this->addOption( 'extension', 'Perform default conversion on an extension',
  45              false, true );
  46          $this->addOption( 'shim-only', 'Only create or update the backward-compatibility shim' );
  47          $this->addOption( 'supplementary', 'Find supplementary i18n files in subdirs and convert those',
  48              false, false );
  49      }
  50  
  51  	public function execute() {
  52          global $IP;
  53  
  54          $phpfile = $this->getArg( 0 );
  55          $jsondir = $this->getArg( 1 );
  56          $extension = $this->getOption( 'extension' );
  57          $convertSupplementaryI18nFiles = $this->hasOption( 'supplementary' );
  58  
  59          if ( $extension ) {
  60              if ( $phpfile ) {
  61                  $this->error( "The phpfile is already specified, conflicts with --extension.\n", 1 );
  62              }
  63              $phpfile = "$IP/extensions/$extension/$extension.i18n.php";
  64          }
  65  
  66          if ( !$phpfile ) {
  67              $this->error( "I'm here for an argument!\n" );
  68              $this->maybeHelp( true );
  69              // dies.
  70          }
  71  
  72          if ( $convertSupplementaryI18nFiles ) {
  73              if ( is_readable( $phpfile ) ) {
  74                  $this->transformI18nFile( $phpfile, $jsondir );
  75              } else {
  76                  // This is non-fatal because we might want to continue searching for
  77                  // i18n files in subdirs even if the extension does not include a
  78                  // primary i18n.php.
  79                  $this->error( "Warning: no primary i18n file was found." );
  80              }
  81              $this->output( "Searching for supplementary i18n files...\n" );
  82              $dir_iterator = new RecursiveDirectoryIterator( dirname( $phpfile ) );
  83              $iterator = new RecursiveIteratorIterator(
  84                  $dir_iterator, RecursiveIteratorIterator::LEAVES_ONLY );
  85              foreach ( $iterator as $path => $fileObject ) {
  86                  if ( fnmatch( "*.i18n.php", $fileObject->getFilename() ) ) {
  87                      $this->output( "Converting $path.\n" );
  88                      $this->transformI18nFile( $path );
  89                  }
  90              }
  91          } else {
  92              // Just convert the primary i18n file.
  93              $this->transformI18nFile( $phpfile, $jsondir );
  94          }
  95      }
  96  
  97  	public function transformI18nFile( $phpfile, $jsondir = null ) {
  98          if ( !$jsondir ) {
  99              // Assume the json directory should be in the same directory as the
 100              // .i18n.php file.
 101              $jsondir = dirname( $phpfile ) . "/i18n";
 102          }
 103          if ( !is_dir( $jsondir ) ) {
 104              $this->output( "Creating directory $jsondir.\n" );
 105              $success = mkdir( $jsondir );
 106              if ( !$success ) {
 107                  $this->error( "Could not create directory $jsondir\n", 1 );
 108              }
 109          }
 110  
 111          if ( $this->hasOption( 'shim-only' ) ) {
 112              $this->shimOnly( $phpfile, $jsondir );
 113  
 114              return;
 115          }
 116  
 117          if ( $jsondir === null ) {
 118              $this->error( 'Argument [jsondir] is required unless --shim-only is specified.' );
 119              $this->maybeHelp( true );
 120          }
 121  
 122          if ( !is_readable( $phpfile ) ) {
 123              $this->error( "Error reading $phpfile\n", 1 );
 124          }
 125          include $phpfile;
 126          $phpfileContents = file_get_contents( $phpfile );
 127  
 128          if ( !isset( $messages ) ) {
 129              $this->error( "PHP file $phpfile does not define \$messages array\n", 1 );
 130          }
 131  
 132          $extensionStyle = true;
 133          if ( !isset( $messages['en'] ) || !is_array( $messages['en'] ) ) {
 134              if ( !$this->hasOption( 'langcode' ) ) {
 135                  $this->error( "PHP file $phpfile does not set language codes, --langcode " .
 136                      "is required.\n", 1 );
 137              }
 138              $extensionStyle = false;
 139              $langcode = $this->getOption( 'langcode' );
 140              $messages = array( $langcode => $messages );
 141          } elseif ( $this->hasOption( 'langcode' ) ) {
 142              $this->output( "Warning: --langcode option set but will not be used.\n" );
 143          }
 144  
 145          foreach ( $messages as $langcode => $langmsgs ) {
 146              $authors = $this->getAuthorsFromComment( $this->findCommentBefore(
 147                  $extensionStyle ? "\$messages['$langcode'] =" : '$messages =',
 148                  $phpfileContents
 149              ) );
 150              // Make sure the @metadata key is the first key in the output
 151              $langmsgs = array_merge(
 152                  array( '@metadata' => array( 'authors' => $authors ) ),
 153                  $langmsgs
 154              );
 155  
 156              $jsonfile = "$jsondir/$langcode.json";
 157              $success = file_put_contents(
 158                  $jsonfile,
 159                  FormatJson::encode( $langmsgs, "\t", FormatJson::ALL_OK ) . "\n"
 160              );
 161              if ( $success === false ) {
 162                  $this->error( "FAILED to write $jsonfile", 1 );
 163              }
 164              $this->output( "$jsonfile\n" );
 165          }
 166  
 167          if ( !$this->hasOption( 'langcode' ) ) {
 168              $shim = $this->doShim( $jsondir );
 169              file_put_contents( $phpfile, $shim );
 170          }
 171  
 172          $this->output( "All done.\n" );
 173          $this->output( "Also add \$wgMessagesDirs['YourExtension'] = __DIR__ . '/i18n';\n" );
 174      }
 175  
 176  	protected function shimOnly( $phpfile, $jsondir ) {
 177          if ( file_exists( $phpfile ) ) {
 178              if ( !is_readable( $phpfile ) ) {
 179                  $this->error( "Error reading $phpfile\n", 1 );
 180              }
 181  
 182              $phpfileContents = file_get_contents( $phpfile );
 183              $m = array();
 184              if ( !preg_match( '!"/([^"$]+)/\$csCode.json";!', $phpfileContents, $m ) ) {
 185                  $this->error( "Cannot recognize $phpfile as a shim.\n", 1 );
 186              }
 187  
 188              if ( $jsondir === null ) {
 189                  $jsondir = $m[1];
 190              }
 191  
 192              $this->output( "Updating existing shim $phpfile\n" );
 193          } elseif ( $jsondir === null ) {
 194              $this->error( "$phpfile does not exist.\n" .
 195                  "Argument [jsondir] is required in order to create a new shim.\n", 1 );
 196          } else {
 197              $this->output( "Creating new shim $phpfile\n" );
 198          }
 199  
 200          $shim = $this->doShim( $jsondir );
 201          file_put_contents( $phpfile, $shim );
 202          $this->output( "All done.\n" );
 203      }
 204  
 205  	protected function doShim( $jsondir ) {
 206          $shim = <<<'PHP'
 207  <?php
 208  /**
 209   * This is a backwards-compatibility shim, generated by:
 210   * https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/maintenance%2FgenerateJsonI18n.php
 211   *
 212   * Beginning with MediaWiki 1.23, translation strings are stored in json files,
 213   * and the EXTENSION.i18n.php file only exists to provide compatibility with
 214   * older releases of MediaWiki. For more information about this migration, see:
 215   * https://www.mediawiki.org/wiki/Requests_for_comment/Localisation_format
 216   *
 217   * This shim maintains compatibility back to MediaWiki 1.17.
 218   */
 219  $messages = array();
 220  if ( !function_exists( '{{FUNC}}' ) ) {
 221      function {{FUNC}}( $cache, $code, &$cachedData ) {
 222          $codeSequence = array_merge( array( $code ), $cachedData['fallbackSequence'] );
 223          foreach ( $codeSequence as $csCode ) {
 224              $fileName = dirname( __FILE__ ) . "/{{OUT}}/$csCode.json";
 225              if ( is_readable( $fileName ) ) {
 226                  $data = FormatJson::decode( file_get_contents( $fileName ), true );
 227                  foreach ( array_keys( $data ) as $key ) {
 228                      if ( $key === '' || $key[0] === '@' ) {
 229                          unset( $data[$key] );
 230                      }
 231                  }
 232                  $cachedData['messages'] = array_merge( $data, $cachedData['messages'] );
 233              }
 234  
 235              $cachedData['deps'][] = new FileDependency( $fileName );
 236          }
 237          return true;
 238      }
 239  
 240      $GLOBALS['wgHooks']['LocalisationCacheRecache'][] = '{{FUNC}}';
 241  }
 242  
 243  PHP;
 244  
 245          $jsondir = str_replace( '\\', '/', $jsondir );
 246          $shim = str_replace( '{{OUT}}', $jsondir, $shim );
 247          $shim = str_replace( '{{FUNC}}', 'wfJsonI18nShim' . wfRandomString( 16 ), $shim );
 248  
 249          return $shim;
 250      }
 251  
 252      /**
 253       * Find the documentation comment immediately before a given search string
 254       * @param string $needle String to search for
 255       * @param string $haystack String to search in
 256       * @return string Substring of $haystack starting at '/**' ending right before $needle, or empty
 257       */
 258  	protected function findCommentBefore( $needle, $haystack ) {
 259          $needlePos = strpos( $haystack, $needle );
 260          if ( $needlePos === false ) {
 261              return '';
 262          }
 263          // Need to pass a negative offset to strrpos() so it'll search backwards from the
 264          // offset
 265          $startPos = strrpos( $haystack, '/**', $needlePos - strlen( $haystack ) );
 266          if ( $startPos === false ) {
 267              return '';
 268          }
 269  
 270          return substr( $haystack, $startPos, $needlePos - $startPos );
 271      }
 272  
 273      /**
 274       * Get an array of author names from a documentation comment containing @author declarations.
 275       * @param string $comment Documentation comment
 276       * @return array Array of author names (strings)
 277       */
 278  	protected function getAuthorsFromComment( $comment ) {
 279          $matches = null;
 280          preg_match_all( '/@author (.*?)$/m', $comment, $matches );
 281  
 282          return $matches && $matches[1] ? $matches[1] : array();
 283      }
 284  }
 285  
 286  $maintClass = "GenerateJsonI18n";
 287  require_once RUN_MAINTENANCE_IF_MAIN;


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1