export.php

Go to the documentation of this file.
00001 <?php
00012 // Standard boilerplate to define $IP
00013 if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
00014     $IP = getenv( 'MW_INSTALL_PATH' );
00015 } else {
00016     $dir = __DIR__;
00017     $IP = "$dir/../../..";
00018 }
00019 require_once "$IP/maintenance/Maintenance.php";
00020 
00021 class CommandlineExport extends Maintenance {
00022     public function __construct() {
00023         parent::__construct();
00024         $this->mDescription = 'Message exporter.';
00025         $this->addOption(
00026             'group',
00027             'Comma separated list of group IDs (can use * as wildcard)',
00028             true, /*required*/
00029             true /*has arg*/
00030         );
00031         $this->addOption(
00032             'lang',
00033             'Comma separated list of language codes or *',
00034             true, /*required*/
00035             true /*has arg*/
00036         );
00037         $this->addOption(
00038             'target',
00039             'Target directory for exported files',
00040             true, /*required*/
00041             true /*has arg*/
00042         );
00043         $this->addOption(
00044             'skip',
00045             '(optional) Languages to skip, comma separated list',
00046             false, /*required*/
00047             true /*has arg*/
00048         );
00049         $this->addOption(
00050             'skipgroup',
00051             '(optional) Comma separated list of group IDs that should not be exported',
00052             false, /*required*/
00053             true /*has arg*/
00054         );
00055         $this->addOption(
00056             'threshold',
00057             '(optional) Do not export under this percentage translated',
00058             false, /*required*/
00059             true /*has arg*/
00060         );
00061         $this->addOption(
00062             'hours',
00063             '(optional) Only export languages with changes in the last given number of hours',
00064             false, /*required*/
00065             true /*has arg*/
00066         );
00067         $this->addOption(
00068             'ppgettext',
00069             '(optional) Group root path for checkout of product. "msgmerge" will post ' .
00070             'process on the export result based on the current source file ' .
00071             'in that location (from sourcePattern or definitionFile)',
00072             false, /*required*/
00073             true /*has arg*/
00074         );
00075         $this->addOption(
00076             'no-location',
00077             '(optional) Only used combined with "ppgettext". This option will rebuild ' .
00078             'the gettext file without location information',
00079             false, /*required*/
00080             false /*has arg*/
00081         );
00082         $this->addOption(
00083             'no-fuzzy',
00084             '(optional) Do not include any messages marked as fuzzy/outdated',
00085             false, /*required*/
00086             false /*has arg*/
00087         );
00088         $this->addOption(
00089             'codemaponly',
00090             '(optional) Only export languages that have a codeMap entry',
00091             false, /*required*/
00092             false /*has arg*/
00093         );
00094         $this->addOption(
00095             'core-meta',
00096             '(optional) Allow export of specific MediaWiki core meta groups ' .
00097                 '(translatewiki.net specific)',
00098             false, /*required*/
00099             false /*has arg*/
00100         );
00101     }
00102 
00103     public function execute() {
00104         $target = $this->getOption( 'target' );
00105         if ( !is_writable( $target ) ) {
00106             $this->error( "Target directory is not writable ($target).", 1 );
00107         }
00108 
00109         $threshold = $this->getOption( 'threshold' );
00110         $noFuzzy = $this->hasOption( 'no-fuzzy' );
00111 
00112         $noLocation = '';
00113         if ( $this->hasOption( 'no-location' ) ) {
00114             $noLocation = '--no-location ';
00115         };
00116 
00117         $skip = array();
00118         if ( $this->hasOption( 'skip' ) ) {
00119             $skip = array_map( 'trim', explode( ',', $this->getOption( 'skip' ) ) );
00120         }
00121 
00122         $reqLangs = TranslateUtils::parseLanguageCodes( $this->getOption( 'lang' ) );
00123         $reqLangs = array_flip( $reqLangs );
00124         foreach ( $skip as $skipLang ) {
00125             unset( $reqLangs[$skipLang] );
00126         }
00127         $reqLangs = array_flip( $reqLangs );
00128 
00129         $codemapOnly = $this->hasOption( 'codemaponly' );
00130 
00131         $groupIds = explode( ',', trim( $this->getOption( 'group' ) ) );
00132         $groupIds = MessageGroups::expandWildcards( $groupIds );
00133         $groups = MessageGroups::getGroupsById( $groupIds );
00134 
00135         $coreMeta = $this->hasOption( 'core-meta' );
00136         foreach ( $groups as $groupId => $group ) {
00137             if ( !$group instanceof MessageGroup ) {
00138                 $this->error( "EE2: Unknown message group $groupId.", 1 );
00139             }
00140 
00141             if ( $group->isMeta() ) {
00142                 if ( !$coreMeta ) {
00143                     $this->output( "Skipping meta message group $groupId.\n" );
00144                     unset( $groups[$groupId] );
00145                     continue;
00146                 } elseif ( strstr( $group->getId(), 'core-1', true ) !== '' ) {
00147                     // Special case for MediaWiki core branches.
00148                     $this->output( "Skipping meta message group $groupId.\n" );
00149                     unset( $groups[$groupId] );
00150                     continue;
00151                 }
00152             }
00153         }
00154 
00155         if ( !count( $groups ) ) {
00156             $this->error( "EE1: No valid message groups identified.", 1 );
00157         }
00158 
00159         $changeFilter = false;
00160         $hours = $this->getOption( 'hours' );
00161         if ( $hours ) {
00162             $namespaces = array();
00163 
00165             foreach ( $groups as $group ) {
00166                 $namespaces[$group->getNamespace()] = true;
00167             }
00168             $namespaces = array_keys( $namespaces );
00169             $bots = true;
00170 
00171             $changeFilter = array();
00172             $rows = TranslateUtils::translationChanges( $hours, $bots, $namespaces );
00173             foreach ( $rows as $row ) {
00174                 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
00175                 $handle = new MessageHandle( $title );
00176                 $code = $handle->getCode();
00177                 if ( !$code ) {
00178                     continue;
00179                 }
00180                 $groupIds = $handle->getGroupIds();
00181                 foreach ( $groupIds as $groupId ) {
00182                     $changeFilter[$groupId][$code] = true;
00183                 }
00184             }
00185         }
00186 
00187         $skipGroups = array();
00188         if ( $this->hasOption( 'skipgroup' ) ) {
00189             $skipGroups = array_map( 'trim', explode( ',', $this->getOption( 'skipgroup' ) ) );
00190         }
00191 
00192         foreach ( $groups as $groupId => $group ) {
00193             if ( in_array( $groupId, $skipGroups ) ) {
00194                 $this->output( "Group $groupId is in skipgroup.\n" );
00195                 continue;
00196             }
00197 
00198             // No changes to this group at all
00199             if ( is_array( $changeFilter ) && !isset( $changeFilter[$groupId] ) ) {
00200                 $this->output( "No recent changes to $groupId.\n" );
00201                 continue;
00202             }
00203 
00204             $langs = $reqLangs;
00205 
00206             if ( $codemapOnly ) {
00207                 foreach ( $langs as $index => $code ) {
00208                     if ( $group->mapCode( $code ) === $code ) {
00209                         unset( $langs[$index] );
00210                     }
00211                 }
00212             }
00213 
00214             if ( $threshold ) {
00215                 $stats = MessageGroupStats::forGroup( $groupId );
00216                 foreach ( $langs as $index => $code ) {
00217                     if ( !isset( $stats[$code] ) ) {
00218                         unset( $langs[$index] );
00219                     }
00220 
00221                     $total = $stats[$code][MessageGroupStats::TOTAL];
00222                     $translated = $stats[$code][MessageGroupStats::TRANSLATED];
00223                     if ( $translated / $total * 100 < $threshold ) {
00224                         unset( $langs[$index] );
00225                     }
00226                 }
00227             }
00228 
00229             // Filter out unchanged languages from requested languages
00230             if ( is_array( $changeFilter ) ) {
00231                 $langs = array_intersect( $langs, array_keys( $changeFilter[$groupId] ) );
00232             }
00233 
00234             if ( !count( $langs ) ) {
00235                 continue;
00236             }
00237 
00238             $this->output( "Exporting $groupId...\n" );
00239 
00240             if ( $group instanceof FileBasedMessageGroup ) {
00241                 $ffs = $group->getFFS();
00242                 $ffs->setWritePath( $target );
00243                 $sourceLanguage = $group->getSourceLanguage();
00244                 $collection = $group->initCollection( $sourceLanguage );
00245 
00246                 $definitionFile = false;
00247 
00248                 if ( $this->hasOption( 'ppgettext' ) && $ffs instanceof GettextFFS ) {
00249                     global $wgMaxShellMemory, $wgTranslateGroupRoot;
00250 
00251                     // Need more shell memory for msgmerge.
00252                     $wgMaxShellMemory = 402400;
00253 
00254                     $path = $group->getSourceFilePath( $sourceLanguage );
00255                     $definitionFile = str_replace(
00256                         $wgTranslateGroupRoot,
00257                         $this->getOption( 'ppgettext' ),
00258                         $path
00259                     );
00260                 }
00261 
00262                 $whitelist = $group->getTranslatableLanguages();
00263 
00264                 foreach ( $langs as $lang ) {
00265                     // Do not export languges that are blacklisted (or not whitelisted).
00266                     // Also check that whitelist is not null, which means that all
00267                     // languages are allowed for translation and export.
00268                     if ( is_array( $whitelist ) && !isset( $whitelist[$lang] ) ) {
00269                         continue;
00270                     }
00271 
00272                     $collection->resetForNewLanguage( $lang );
00273                     $collection->loadTranslations();
00274                     // Don't export ignored, unless it is the source language
00275                     // or message documentation
00276                     global $wgTranslateDocumentationLanguageCode;
00277                     if ( $lang !== $wgTranslateDocumentationLanguageCode
00278                         && $lang !== $sourceLanguage
00279                     ) {
00280                         $collection->filter( 'ignored' );
00281                     }
00282 
00283                     if ( $noFuzzy ) {
00284                         $collection->filter( 'fuzzy' );
00285                     }
00286 
00287                     $ffs->write( $collection );
00288 
00289                     // Do post processing if requested.
00290                     if ( $definitionFile ) {
00291                         if ( is_file( $definitionFile ) ) {
00292                             $targetFileName = $ffs->getWritePath() .
00293                                 "/" . $group->getTargetFilename( $collection->code );
00294                             $cmd = "msgmerge --quiet " . $noLocation . "--output-file=" .
00295                                 $targetFileName . ' ' . $targetFileName . ' ' . $definitionFile;
00296                             wfShellExec( $cmd, $ret );
00297 
00298                             // Report on errors.
00299                             if ( $ret ) {
00300                                 $this->error( "ERROR: $ret" );
00301                             }
00302                         } else {
00303                             $this->error( "$definitionFile does not exist.", 1 );
00304                         }
00305                     }
00306                 }
00307             } else {
00308                 if ( $noFuzzy ) {
00309                     $this->error( "--no-fuzzy is not supported for this message group." );
00310                 }
00311 
00312                 $writer = $group->getWriter();
00313                 $writer->fileExport( $langs, $target );
00314             }
00315         }
00316     }
00317 }
00318 
00319 $maintClass = 'CommandlineExport';
00320 require_once RUN_MAINTENANCE_IF_MAIN;
Generated on Tue Oct 29 00:00:24 2013 for MediaWiki Translate Extension by  doxygen 1.6.3