<?php
/**原作者为Liangent https://github.com/liangent/mediawiki-maintenance/blob/master/translateVariants.php
**用法参见 https://zh.wikipedia.org/wiki/User:Liangent-bot/crontab/liangent-php
**该版本与mediawiki 1.34.0兼容
*/
require_once( dirname( __FILE__ ) . '/Maintenance.php' );
class TranslateVariants extends Maintenance {
public $tablesPrepared = false;
public function __construct() {
parent::__construct();
$this->addOption( 'lang', 'Language to translate, such as zh', true, true );
$this->addOption( 'ns', 'Namespace ids to translate, all subject namespaces by default', false, true );
# Determined by ns automatically.
# $this->addOption( 'msg', 'If a variant code equals to $wgLanguageCode, write it without suffix.', false );
$this->addOption( 'dry-run', 'Do not really publish translations', false );
$this->addOption( 'delete', 'Follow delete actions as well', false );
$this->addOption( 'table', 'Extra conversion table to load (subpage)', false, true );
$this->addOption( 'init', 'Initialization page (use -{H| }- markups for example)', false, true );
}
private function getTargets( $title, $lang ) {
global $wgContLang;
$targets = array();
# Blacklist CSS/JS pages.
$parts = explode( '/', $title->getText() );
unset( $parts[count( $parts ) - 1] );
$btitle = Title::makeTitleSafe( $title->getNamespace(), implode( '/', $parts ) );
if ( $btitle->isCssOrJsPage() ) {
return $targets;
}
foreach ( $lang->getVariants() as $variant ) {
$base = ( $title->getNamespace() === NS_MEDIAWIKI && $variant === $wgContLang->getCode() );
if ( $variant === $lang->getCode() && !$base ) {
continue;
}
# Find the title & page for this variant
$parts = explode( '/', $title->getText() );
if ( $base ) {
unset( $parts[count( $parts ) - 1] );
} else {
$parts[count( $parts ) - 1] = $variant;
}
$vtitle = Title::makeTitleSafe( $title->getNamespace(), implode( '/', $parts ) );
if ( !$vtitle ) {
continue;
}
$targets[$variant] = $vtitle;
}
return $targets;
}
private function prepareTables( $lang ) {
if ( $this->tablesPrepared ) {
return;
}
$this->tablesPrepared = true;
$converter = $lang->getConverter();
$converter->mTablesLoaded = true;
$converter->mTables = false;
$converter->loadDefaultTables();
foreach ( $converter->mVariants as $var ) {
$cached = $converter->parseCachedTable( $var );
$converter->mTables[$var]->mergeArray( $cached );
if ( !$this->hasOption( 'table' ) ) {
continue;
}
$cached = $converter->parseCachedTable( $var, $this->getOption( 'table' ) );
$converter->mTables[$var]->mergeArray( $cached );
}
$converter->postLoadTables();
if ( $this->hasOption( 'init' )
&& ( $initTitle = Title::newFromText( $this->getOption( 'init' ) ) )
&& ( $initRevision = Revision::newFromTitle( $initTitle ) )
&& ( $initContent = $initRevision->getContent() )
) {
$this->output( "Using initialization page [[{$initTitle->getPrefixedText()}]]\n" );
$initContent->getParserOutput( $initTitle );
}
}
public function execute() {
global $wgContLang;
$lang = Language::factory( $this->getOption( 'lang' ) );
$this->output( "Translating messages in {$lang->getCode()}: " );
if ( $lang->hasVariants() ) {
$this->output( implode( ', ', $lang->getVariants() ) . ".\n" );
} else {
$this->output( "no variants.\n" );
return;
}
$allns = MWNamespace::getValidNamespaces();
if ( $this->hasOption( 'ns' ) ) {
$ns = array_intersect( $allns, array_map( 'intval', preg_split(
'/,|:/', $this->getOption( 'ns' ), null, PREG_SPLIT_NO_EMPTY
) ) );
} else {
$ns = array_filter( $allns, 'MWNamespace::isSubject' );
}
$this->output( 'Namespace(s): ' );
if ( count( $ns ) ) {
$this->output( implode( ', ', array_map(
array( $wgContLang, 'getFormattedNsText' ), $ns
) ) . ".\n" );
} else {
$this->output( "no valid namespaces.\n" );
return;
}
sort( $ns );
$cachekey = wfMemcKey( 'TranslateVariants', $lang->getCode(), implode( '!', $ns ) );
$this->output( "Using cache key: $cachekey\n" );
$cache = ObjectCache::getInstance( CACHE_DB );
$revid = $cache->get( $cachekey );
if ( !$revid ) {
$revid = 0;
}
$this->output( "Starting from revision $revid.\n" );
# Find all /lang pages which have been updated since last run.
$dbw = wfGetDB( DB_MASTER );
$res = $dbw->select(
'page',
WikiPage::selectFields(),
array(
'page_namespace' => $ns,
'page_title' . $dbw->buildLike(
$dbw->anyString(), '/', $lang->getCode()
),
'page_latest > ' . $revid,
),
__METHOD__,
array(
'ORDER BY' => 'page_latest',
)
);
while ( $row = $dbw->fetchObject( $res ) ) {
$title = Title::newFromRow( $row );
$page = WikiPage::newFromRow( $row );
$text_rev = $page->getRevision();
$content = $text_rev->getContent( Revision::RAW );
$text = ContentHandler::getContentText( $content );
if ( $text === '' ) { # FIXME: update cache?
continue;
}
$this->output( "Translating [[{$title->getPrefixedText()}]], revision {$page->getLatest()}.\n" );
$summary = "Automatically converted from [[{$title->getPrefixedText()}]], revision {$page->getLatest()}";
$userText = $page->getUserText();
$summary = wfMessage( $userText === '' ? 'ts-variant-translate-from' : 'ts-variant-translate-from-user' )
->params( $title->getPrefixedText(), $page->getLatest(), $userText )->text();
foreach ( $this->getTargets( $title, $lang ) as $variant => $vtitle ) {
$this->prepareTables( $lang );
$vpage = WikiPage::factory( $vtitle );
# Translate text to the specified variant
$vtext = $lang->getConverter()->convertTo($text, $variant );
$this->output( "Editing [[{$vtitle->getPrefixedText()}]]..." );
if ( $this->hasOption( 'dry-run' ) ) {
$this->output( " with content: $vtext\n" );
} else {
$newcontent = new WikitextContent( $vtext );
$st = $vpage->doEditContent( $newcontent, $summary, EDIT_SUPPRESS_RC );
if ( $st->isOK() ) {
$this->output( " done.\n" );
} else {
$this->output( " ERROR.\n");
}
}
}
if ( !$this->hasOption( 'dry-run' ) ) {
$this->output( "Updating cache..." );
if ( $cache->set( $cachekey, $page->getLatest() ) ) {
$this->output( " done.\n" );
} else {
$this->output( " ERROR.\n");
}
}
}
if ( !$this->hasOption( 'delete' ) ) {
# We're not expected to follow deletion then.
return;
}
$cachekey = wfMemcKey( 'TranslateVariants', $lang->getCode(), implode( '!', $ns ), 'delete' );
$this->output( "Using cache key: $cachekey\n" );
$cache = ObjectCache::getInstance( CACHE_DB );
$logid = $cache->get( $cachekey );
if ( !$logid ) {
$logid = 0;
}
$this->output( "Starting from log entry $logid.\n" );
# Find all log entries of /lang pages since last run.
$query = DatabaseLogEntry::getSelectQueryData();
$query['conds'][] = 'log_id > ' . $logid;
$query['conds']['log_type'] = 'delete';
$query['conds']['log_action'] = 'delete';
$query['conds']['log_namespace'] = $ns;
$query['conds'][] = 'log_title' . $dbw->buildLike( $dbw->anyString(), '/', $lang->getCode() );
$query['options']['ORDER BY'] = 'log_id';
$res = $dbw->select( $query['tables'], $query['fields'], $query['conds'],
__METHOD__, $query['options'], $query['join_conds'] );
while ( $row = $dbw->fetchObject( $res ) ) {
$logentry = DatabaseLogEntry::newFromRow( $row );
$title = $logentry->getTarget();
$user = $logentry->getPerformer();
if ( $title->exists() ) {
continue;
}
$this->output( "Processing deletion of [[{$title->getPrefixedText()}]].\n" );
$comment = $logentry->getComment();
$reason = wfMessage( $comment === '' ? 'ts-variant-delete' : 'ts-variant-delete-reason' )
->params( $title->getPrefixedText(), $user->getName(), $logentry->getType(), $comment )->text();
foreach ( $this->getTargets( $title, $lang ) as $variant => $vtitle ) {
if ( !$vtitle->exists() ) {
continue;
}
$vpage = WikiPage::factory( $vtitle );
$this->output( "Deleting [[{$vtitle->getPrefixedText()}]]..." );
if ( $this->hasOption( 'dry-run' ) ) {
$this->output( "\n" );
} else {
$ok = $vpage->doDeleteArticle( $reason );
if ( $ok ) {
$this->output( " done.\n" );
} else {
$this->output( " ERROR.\n");
}
}
}
if ( !$this->hasOption( 'dry-run' ) ) {
$this->output( "Updating cache..." );
if ( $cache->set( $cachekey, $logentry->getId() ) ) {
$this->output( " done.\n" );
} else {
$this->output( " ERROR.\n");
}
}
}
}
}
$maintClass = "TranslateVariants";
require_once( RUN_MAINTENANCE_IF_MAIN );