summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/scripts/export.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Translate/scripts/export.php')
-rw-r--r--www/wiki/extensions/Translate/scripts/export.php301
1 files changed, 301 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/scripts/export.php b/www/wiki/extensions/Translate/scripts/export.php
new file mode 100644
index 00000000..469bd2ac
--- /dev/null
+++ b/www/wiki/extensions/Translate/scripts/export.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * Script to export translations of one message group to file(s).
+ *
+ * @author Niklas Laxström
+ * @author Siebrand Mazeland
+ * @copyright Copyright © 2008-2013, Niklas Laxström, Siebrand Mazeland
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+// Standard boilerplate to define $IP
+if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
+ $IP = getenv( 'MW_INSTALL_PATH' );
+} else {
+ $dir = __DIR__;
+ $IP = "$dir/../../..";
+}
+require_once "$IP/maintenance/Maintenance.php";
+
+class CommandlineExport extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Message exporter.';
+ $this->addOption(
+ 'group',
+ 'Comma separated list of group IDs (can use * as wildcard)',
+ true, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'lang',
+ 'Comma separated list of language codes or *',
+ true, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'target',
+ 'Target directory for exported files',
+ true, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'skip',
+ '(optional) Languages to skip, comma separated list',
+ false, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'skipgroup',
+ '(optional) Comma separated list of group IDs that should not be exported',
+ false, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'threshold',
+ '(optional) Do not export under this percentage translated',
+ false, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'hours',
+ '(optional) Only export languages with changes in the last given number of hours',
+ false, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'ppgettext',
+ '(optional) Group root path for checkout of product. "msgmerge" will post ' .
+ 'process on the export result based on the current source file ' .
+ 'in that location (from sourcePattern or definitionFile)',
+ false, /*required*/
+ true /*has arg*/
+ );
+ $this->addOption(
+ 'no-location',
+ '(optional) Only used combined with "ppgettext". This option will rebuild ' .
+ 'the gettext file without location information',
+ false, /*required*/
+ false /*has arg*/
+ );
+ $this->addOption(
+ 'no-fuzzy',
+ '(optional) Do not include any messages marked as fuzzy/outdated',
+ false, /*required*/
+ false /*has arg*/
+ );
+ $this->addOption(
+ 'codemaponly',
+ '(optional) Only export languages that have a codeMap entry',
+ false, /*required*/
+ false /*has arg*/
+ );
+ }
+
+ public function execute() {
+ $target = $this->getOption( 'target' );
+ if ( !is_writable( $target ) ) {
+ $this->error( "Target directory is not writable ($target).", 1 );
+ }
+
+ $threshold = $this->getOption( 'threshold' );
+ $noFuzzy = $this->hasOption( 'no-fuzzy' );
+
+ $noLocation = '';
+ if ( $this->hasOption( 'no-location' ) ) {
+ $noLocation = '--no-location ';
+ };
+
+ $skip = [];
+ if ( $this->hasOption( 'skip' ) ) {
+ $skip = array_map( 'trim', explode( ',', $this->getOption( 'skip' ) ) );
+ }
+
+ $reqLangs = TranslateUtils::parseLanguageCodes( $this->getOption( 'lang' ) );
+ $reqLangs = array_flip( $reqLangs );
+ foreach ( $skip as $skipLang ) {
+ unset( $reqLangs[$skipLang] );
+ }
+ $reqLangs = array_flip( $reqLangs );
+
+ $codemapOnly = $this->hasOption( 'codemaponly' );
+
+ $groupIds = explode( ',', trim( $this->getOption( 'group' ) ) );
+ $groupIds = MessageGroups::expandWildcards( $groupIds );
+ $groups = MessageGroups::getGroupsById( $groupIds );
+
+ /** @var FileBasedMessageGroup $group */
+ foreach ( $groups as $groupId => $group ) {
+ if ( $group->isMeta() ) {
+ $this->output( "Skipping meta message group $groupId.\n" );
+ unset( $groups[$groupId] );
+ continue;
+ }
+
+ if ( !$group instanceof FileBasedMessageGroup ) {
+ $this->output( "EE2: Unexportable message group $groupId.\n" );
+ unset( $groups[$groupId] );
+ continue;
+ }
+ }
+
+ if ( !count( $groups ) ) {
+ $this->error( 'EE1: No valid message groups identified.', 1 );
+ }
+
+ $changeFilter = false;
+ $hours = $this->getOption( 'hours' );
+ if ( $hours ) {
+ $namespaces = [];
+
+ /** @var FileBasedMessageGroup $group */
+ foreach ( $groups as $group ) {
+ $namespaces[$group->getNamespace()] = true;
+ }
+
+ $namespaces = array_keys( $namespaces );
+ $bots = true;
+
+ $changeFilter = [];
+ $rows = TranslateUtils::translationChanges( $hours, $bots, $namespaces );
+ foreach ( $rows as $row ) {
+ $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $handle = new MessageHandle( $title );
+ $code = $handle->getCode();
+ if ( !$code ) {
+ continue;
+ }
+ $groupIds = $handle->getGroupIds();
+ foreach ( $groupIds as $groupId ) {
+ $changeFilter[$groupId][$code] = true;
+ }
+ }
+ }
+
+ $skipGroups = [];
+ if ( $this->hasOption( 'skipgroup' ) ) {
+ $skipGroups = array_map( 'trim', explode( ',', $this->getOption( 'skipgroup' ) ) );
+ }
+
+ foreach ( $groups as $groupId => $group ) {
+ if ( in_array( $groupId, $skipGroups ) ) {
+ $this->output( "Group $groupId is in skipgroup.\n" );
+ continue;
+ }
+
+ // No changes to this group at all
+ if ( is_array( $changeFilter ) && !isset( $changeFilter[$groupId] ) ) {
+ $this->output( "No recent changes to $groupId.\n" );
+ continue;
+ }
+
+ $langs = $reqLangs;
+
+ if ( $codemapOnly ) {
+ foreach ( $langs as $index => $code ) {
+ if ( $group->mapCode( $code ) === $code ) {
+ unset( $langs[$index] );
+ }
+ }
+ }
+
+ if ( $threshold ) {
+ $stats = MessageGroupStats::forGroup( $groupId );
+ foreach ( $langs as $index => $code ) {
+ if ( !isset( $stats[$code] ) ) {
+ unset( $langs[$index] );
+ continue;
+ }
+
+ $total = $stats[$code][MessageGroupStats::TOTAL];
+ $translated = $stats[$code][MessageGroupStats::TRANSLATED];
+ if ( $translated / $total * 100 < $threshold ) {
+ unset( $langs[$index] );
+ }
+ }
+ }
+
+ // Filter out unchanged languages from requested languages
+ if ( is_array( $changeFilter ) ) {
+ $langs = array_intersect( $langs, array_keys( $changeFilter[$groupId] ) );
+ }
+
+ if ( !count( $langs ) ) {
+ continue;
+ }
+
+ $this->output( "Exporting $groupId...\n" );
+
+ $ffs = $group->getFFS();
+ $ffs->setWritePath( $target );
+ $sourceLanguage = $group->getSourceLanguage();
+ $collection = $group->initCollection( $sourceLanguage );
+
+ $definitionFile = false;
+
+ if ( $this->hasOption( 'ppgettext' ) && $ffs instanceof GettextFFS ) {
+ global $wgMaxShellMemory, $wgTranslateGroupRoot;
+
+ // Need more shell memory for msgmerge.
+ $wgMaxShellMemory = 402400;
+
+ $path = $group->getSourceFilePath( $sourceLanguage );
+ $definitionFile = str_replace(
+ $wgTranslateGroupRoot,
+ $this->getOption( 'ppgettext' ),
+ $path
+ );
+ }
+
+ $whitelist = $group->getTranslatableLanguages();
+
+ foreach ( $langs as $lang ) {
+ // Do not export languages that are blacklisted (or not whitelisted).
+ // Also check that whitelist is not null, which means that all
+ // languages are allowed for translation and export.
+ if ( is_array( $whitelist ) && !isset( $whitelist[$lang] ) ) {
+ continue;
+ }
+
+ $collection->resetForNewLanguage( $lang );
+ $collection->loadTranslations();
+ // Don't export ignored, unless it is the source language
+ // or message documentation
+ global $wgTranslateDocumentationLanguageCode;
+ if ( $lang !== $wgTranslateDocumentationLanguageCode
+ && $lang !== $sourceLanguage
+ ) {
+ $collection->filter( 'ignored' );
+ }
+
+ if ( $noFuzzy ) {
+ $collection->filter( 'fuzzy' );
+ }
+
+ $ffs->write( $collection );
+
+ // Do post processing if requested.
+ if ( $definitionFile ) {
+ if ( is_file( $definitionFile ) ) {
+ $targetFileName = $ffs->getWritePath() .
+ '/' . $group->getTargetFilename( $collection->code );
+ $cmd = 'msgmerge --quiet ' . $noLocation . '--output-file=' .
+ $targetFileName . ' ' . $targetFileName . ' ' . $definitionFile;
+ wfShellExec( $cmd, $ret );
+
+ // Report on errors.
+ if ( $ret ) {
+ $this->error( "ERROR: $ret" );
+ }
+ } else {
+ $this->error( "$definitionFile does not exist.", 1 );
+ }
+ }
+ }
+ }
+ }
+}
+
+$maintClass = CommandlineExport::class;
+require_once RUN_MAINTENANCE_IF_MAIN;