diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2013-08-12 09:28:15 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2013-08-12 09:28:15 +0200 |
commit | 08aa4418c30cfc18ccc69a0f0f9cb9e17be6c196 (patch) | |
tree | 577a29fb579188d16003a209ce2a2e9c5b0aa2bd /extensions/LocalisationUpdate | |
parent | cacc939b34e315b85e2d72997811eb6677996cc1 (diff) |
Update to MediaWiki 1.21.1
Diffstat (limited to 'extensions/LocalisationUpdate')
-rw-r--r-- | extensions/LocalisationUpdate/KNOWN_ISSUES.txt | 11 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/LocalisationUpdate.class.php | 588 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/LocalisationUpdate.i18n.php | 528 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/LocalisationUpdate.php | 42 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/QuickArrayReader.php | 187 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/README_FIRST.txt | 8 | ||||
-rw-r--r-- | extensions/LocalisationUpdate/update.php | 38 |
7 files changed, 1402 insertions, 0 deletions
diff --git a/extensions/LocalisationUpdate/KNOWN_ISSUES.txt b/extensions/LocalisationUpdate/KNOWN_ISSUES.txt new file mode 100644 index 00000000..7ce14cd0 --- /dev/null +++ b/extensions/LocalisationUpdate/KNOWN_ISSUES.txt @@ -0,0 +1,11 @@ +- Only works with SVN revision 50605 or later of the + MediaWiki core + + + +Key issues at the moment: +* Seems to want to store a copy of the localization updates in each local database. +We've got hundreds of wikis run from the same installation set; we don't want to multiply our effort by 1000. + +* It doesn't seem to be using available memcached stuff; unsure yet whether this is taken care of +by the general message caching or if we're going to end up making extra hits we don't need. diff --git a/extensions/LocalisationUpdate/LocalisationUpdate.class.php b/extensions/LocalisationUpdate/LocalisationUpdate.class.php new file mode 100644 index 00000000..39368b7c --- /dev/null +++ b/extensions/LocalisationUpdate/LocalisationUpdate.class.php @@ -0,0 +1,588 @@ +<?php + +/** + * Class for localization updates. + * + * TODO: refactor code to remove duplication + */ +class LocalisationUpdate { + + private static $newHashes = null; + private static $filecache = array(); + + /** + * LocalisationCacheRecache hook handler. + * + * @param $lc LocalisationCache + * @param $langcode String + * @param $cache Array + * + * @return true + */ + public static function onRecache( LocalisationCache $lc, $langcode, array &$cache ) { + // Handle fallback sequence and load all fallback messages from the cache + $codeSequence = array_merge( array( $langcode ), $cache['fallbackSequence'] ); + // Iterate over the fallback sequence in reverse, otherwise the fallback + // language will override the requested language + foreach ( array_reverse( $codeSequence ) as $code ) { + if ( $code == 'en' ) { + // Skip English, otherwise we end up trying to read + // the nonexistent cache file for en a couple hundred times + continue; + } + + $cache['messages'] = array_merge( + $cache['messages'], + self::readFile( $code ) + ); + + $cache['deps'][] = new FileDependency( + self::filename( $code ) + ); + } + + return true; + } + + /** + * Called from the cronjob to fetch new messages from SVN. + * + * @param $options Array + * + * @return true + */ + public static function updateMessages( array $options ) { + global $wgLocalisationUpdateDirectory, $wgLocalisationUpdateCoreURL, + $wgLocalisationUpdateExtensionURL, $wgLocalisationUpdateSVNURL; + + $verbose = !isset( $options['quiet'] ); + $all = isset( $options['all'] ); + $skipCore = isset( $options['skip-core'] ); + $skipExtensions = isset( $options['skip-extensions'] ); + + if( isset( $options['outdir'] ) ) { + $wgLocalisationUpdateDirectory = $options['outdir']; + } + + $coreUrl = $wgLocalisationUpdateCoreURL; + $extUrl = $wgLocalisationUpdateExtensionURL; + + // Some ugly BC + if ( $wgLocalisationUpdateSVNURL ) { + $coreUrl = $wgLocalisationUpdateSVNURL . '/phase3/$2'; + $extUrl = $wgLocalisationUpdateSVNURL . '/extensions/$1/$2'; + } + + // Some more ugly BC + if ( isset( $options['svnurl'] ) ) { + $coreUrl = $options['svnurl'] . '/phase3/$2'; + $extUrl = $options['svnurl'] . '/extensions/$1/$2'; + } + + $result = 0; + + // Update all MW core messages. + if( !$skipCore ) { + $result = self::updateMediawikiMessages( $verbose, $coreUrl ); + } + + // Update all Extension messages. + if( !$skipExtensions ) { + if( $all ) { + global $IP; + $extFiles = array(); + + // Look in extensions/ for all available items... + // TODO: add support for $wgExtensionAssetsPath + $dirs = new RecursiveDirectoryIterator( "$IP/extensions/" ); + + // I ain't kidding... RecursiveIteratorIterator. + foreach( new RecursiveIteratorIterator( $dirs ) as $pathname => $item ) { + $filename = basename( $pathname ); + $matches = array(); + if( preg_match( '/^(.*)\.i18n\.php$/', $filename, $matches ) ) { + $group = $matches[1]; + $extFiles[$group] = $pathname; + } + } + } else { + global $wgExtensionMessagesFiles; + $extFiles = $wgExtensionMessagesFiles; + } + foreach ( $extFiles as $extension => $locFile ) { + $result += self::updateExtensionMessages( $locFile, $extension, $verbose, $extUrl ); + } + } + + self::writeHashes(); + + // And output the result! + self::myLog( "Updated {$result} messages in total" ); + self::myLog( "Done" ); + + return true; + } + + /** + * Update Extension Messages. + * + * @param $file String + * @param $extension String + * @param $verbose Boolean + * + * @return Integer: the amount of updated messages + */ + public static function updateExtensionMessages( $file, $extension, $verbose, $extUrl ) { + $match = array(); + $ok = preg_match( '~^.*/extensions/([^/]+)/(.*)$~U', $file, $match ); + if ( !$ok ) { + return null; + } + + $ext = $match[1]; + $extFile = $match[2]; + + // Create a full path. + $svnfile = str_replace( + array( '$1', '$2' ), + array( $ext, $extFile ), + $extUrl + ); + + // Compare the 2 files. + $result = self::compareExtensionFiles( $extension, $svnfile, $file, $verbose ); + + return $result; + } + + /** + * Update the MediaWiki Core Messages. + * + * @param $verbose Boolean + * + * @return Integer: the amount of updated messages + */ + public static function updateMediawikiMessages( $verbose, $coreUrl ) { + // Find the changed English strings (as these messages won't be updated in ANY language). + $localUrl = Language::getMessagesFileName( 'en' ); + $repoUrl = str_replace( '$2', 'languages/messages/MessagesEn.php', $coreUrl ); + $changedEnglishStrings = self::compareFiles( $repoUrl, $localUrl, $verbose ); + + // Count the changes. + $changedCount = 0; + + $languages = Language::fetchLanguageNames( null, 'mwfile' ); + foreach ( array_keys( $languages ) as $code ) { + $localUrl = Language::getMessagesFileName( $code ); + // Not prefixed with $IP + $filename = Language::getFilename( 'languages/messages/Messages', $code ); + $repoUrl = str_replace( '$2', $filename, $coreUrl ); + + // Compare the files. + $changedCount += self::compareFiles( $repoUrl, $localUrl, $verbose, $changedEnglishStrings, false, true ); + } + + // Log some nice info. + self::myLog( "{$changedCount} MediaWiki messages are updated" ); + + return $changedCount; + } + + /** + * Removes all unneeded content from a file and returns it. + * + * @param $contents String + * + * @return String + */ + public static function cleanupFile( $contents ) { + // We don't need any PHP tags. + $contents = strtr( $contents, + array( + '<?php' => '', + '?' . '>' => '' + ) + ); + + $results = array(); + + // And we only want message arrays. + preg_match_all( '/\$messages(.*\s)*?\);/', $contents, $results ); + + // But we want them all in one string. + if( !empty( $results[0] ) && is_array( $results[0] ) ) { + $contents = implode( "\n\n", $results[0] ); + } else { + $contents = ''; + } + + // And we hate the windows vs linux linebreaks. + $contents = preg_replace( '/\r\n?/', "\n", $contents ); + + return $contents; + } + + /** + * Returns the contents of a file or false on failiure. + * + * @param $file String + * + * @return string or false + */ + public static function getFileContents( $file ) { + global $wgLocalisationUpdateRetryAttempts; + + $attempts = 0; + $filecontents = ''; + + // Use cURL to get the SVN contents. + if ( preg_match( "/^http/", $file ) ) { + while( !$filecontents && $attempts <= $wgLocalisationUpdateRetryAttempts ) { + if( $attempts > 0 ) { + $delay = 1; + self::myLog( 'Failed to download ' . $file . "; retrying in ${delay}s..." ); + sleep( $delay ); + } + + $filecontents = Http::get( $file ); + $attempts++; + } + if ( !$filecontents ) { + self::myLog( 'Cannot get the contents of ' . $file . ' (curl)' ); + return false; + } + } else {// otherwise try file_get_contents + if ( !( $filecontents = file_get_contents( $file ) ) ) { + self::myLog( 'Cannot get the contents of ' . $file ); + return false; + } + } + + return $filecontents; + } + + /** + * Returns a pair of arrays containing the messages from two files, or + * a pair of nulls if the files don't need to be checked. + * + * @param $tag String + * @param $file1 String + * @param $file2 String + * @param $verbose Boolean + * @param $alwaysGetResult Boolean + * + * @return array + */ + public static function loadFilesToCompare( $tag, $file1, $file2, $verbose, $alwaysGetResult = true ) { + $file1contents = self::getFileContents( $file1 ); + if ( $file1contents === false || $file1contents === '' ) { + self::myLog( "Failed to read $file1" ); + return array( null, null ); + } + + $file2contents = self::getFileContents( $file2 ); + if ( $file2contents === false || $file2contents === '' ) { + self::myLog( "Failed to read $file2" ); + return array( null, null ); + } + + // Only get the part we need. + $file1contents = self::cleanupFile( $file1contents ); + $file1hash = md5( $file1contents ); + + $file2contents = self::cleanupFile( $file2contents ); + $file2hash = md5( $file2contents ); + + // Check if the file has changed since our last update. + if ( !$alwaysGetResult ) { + if ( !self::checkHash( $file1, $file1hash ) && !self::checkHash( $file2, $file2hash ) ) { + self::myLog( "Skipping {$tag} since the files haven't changed since our last update", $verbose ); + return array( null, null ); + } + } + + // Get the array with messages. + $messages1 = self::parsePHP( $file1contents, 'messages' ); + if ( !is_array( $messages1 ) ) { + if ( strpos( $file1contents, '$messages' ) === false ) { + // No $messages array. This happens for some languages that only have a fallback + $messages1 = array(); + } else { + // Broken file? Report and bail + self::myLog( "Failed to parse $file1" ); + return array( null, null ); + } + } + + $messages2 = self::parsePHP( $file2contents, 'messages' ); + if ( !is_array( $messages2 ) ) { + // Broken file? Report and bail + if ( strpos( $file2contents, '$messages' ) === false ) { + // No $messages array. This happens for some languages that only have a fallback + $messages2 = array(); + } else { + self::myLog( "Failed to parse $file2" ); + return array( null, null ); + } + } + + self::saveHash( $file1, $file1hash ); + self::saveHash( $file2, $file2hash ); + + return array( $messages1, $messages2 ); + } + + /** + * Compare new and old messages lists, and optionally save the new + * messages if they've changed. + * + * @param $langcode String + * @param $old_messages Array + * @param $new_messages Array + * @param $verbose Boolean + * @param $forbiddenKeys Array + * @param $saveResults Boolean + * + * @return array|int + */ + private static function compareLanguageArrays( $langcode, $old_messages, $new_messages, $verbose, $forbiddenKeys, $saveResults ) { + // Get the currently-cached messages, if any + $cur_messages = self::readFile( $langcode ); + + // Update the messages lists with the cached messages + $old_messages = array_merge( $old_messages, $cur_messages ); + $new_messages = array_merge( $cur_messages, $new_messages ); + + // Use the old/cached version for any forbidden keys + if ( count( $forbiddenKeys ) ) { + $new_messages = array_merge( + array_diff_key( $new_messages, $forbiddenKeys ), + array_intersect_key( $old_messages, $forbiddenKeys ) + ); + } + + + if ( $saveResults ) { + // If anything has changed from the saved version, save the new version + if ( $new_messages != $cur_messages ) { + // Count added, updated, and deleted messages: + // diff( new, cur ) gives added + updated, and diff( cur, new ) + // gives deleted + updated. + $changed = array_diff_assoc( $new_messages, $cur_messages ) + + array_diff_assoc( $cur_messages, $new_messages ); + $updates = count( $changed ); + self::myLog( "{$updates} messages updated for {$langcode}.", $verbose ); + self::writeFile( $langcode, $new_messages ); + } else { + $updates = 0; + } + return $updates; + } else { + // Find all deleted or changed messages + $changedStrings = array_diff_assoc( $old_messages, $new_messages ); + return $changedStrings; + } + } + + /** + * Returns an array containing the differences between the files. + * + * @param $newfile String + * @param $oldfile String + * @param $verbose Boolean + * @param $forbiddenKeys Array + * @param $alwaysGetResult Boolean + * @param $saveResults Boolean + * + * @return array|int + */ + public static function compareFiles( $newfile, $oldfile, $verbose, array $forbiddenKeys = array(), $alwaysGetResult = true, $saveResults = false ) { + // Get the languagecode. + $langcode = Language::getCodeFromFileName( $newfile, 'Messages' ); + + list( $new_messages, $old_messages ) = self::loadFilesToCompare( + $langcode, $newfile, $oldfile, $verbose, $alwaysGetResult + ); + if ( $new_messages === null || $old_messages === null ) { + return $saveResults ? 0 : array(); + } + + return self::compareLanguageArrays( $langcode, $old_messages, $new_messages, $verbose, $forbiddenKeys, $saveResults ); + } + + /** + * + * @param $extension String + * @param $newfile String + * @param $oldfile String + * @param $verbose Boolean + * @param $alwaysGetResult Boolean + * @param $saveResults Boolean + * + * @return Integer: the amount of updated messages + */ + public static function compareExtensionFiles( $extension, $newfile, $oldfile, $verbose ) { + list( $new_messages, $old_messages ) = self::loadFilesToCompare( + $extension, $newfile, $oldfile, $verbose, false + ); + if ( $new_messages === null || $old_messages === null ) { + return 0; + } + + // Update counter. + $updates = 0; + + if ( empty( $new_messages['en'] ) ) { + $new_messages['en'] = array(); + } + + if ( empty( $old_messages['en'] ) ) { + $old_messages['en'] = array(); + } + + // Find the changed english strings. + $forbiddenKeys = self::compareLanguageArrays( 'en', $old_messages['en'], $new_messages['en'], $verbose, array(), false ); + + // Do an update for each language. + foreach ( $new_messages as $language => $messages ) { + if ( $language == 'en' ) { // Skip english. + continue; + } + + if ( !isset( $old_messages[$language] ) ) { + $old_messages[$language] = array(); + } + + $updates += self::compareLanguageArrays( $language, $old_messages[$language], $messages, $verbose, $forbiddenKeys, true ); + } + + // And log some stuff. + self::myLog( "Updated " . $updates . " messages for the '{$extension}' extension", $verbose ); + + return $updates; + } + + /** + * Checks whether a messages file has a certain hash. + * + * TODO: Swap return values, this is insane + * + * @param $file string Filename + * @param $hash string Hash + * + * @return bool True if $file does NOT have hash $hash, false if it does + */ + public static function checkHash( $file, $hash ) { + $hashes = self::readFile( 'hashes' ); + return @$hashes[$file] !== $hash; + } + + /** + * @param $file + * @param $hash + */ + public static function saveHash( $file, $hash ) { + if ( is_null( self::$newHashes ) ) { + self::$newHashes = self::readFile( 'hashes' ); + } + + self::$newHashes[$file] = $hash; + } + + public static function writeHashes() { + self::writeFile( 'hashes', self::$newHashes ); + } + + /** + * Logs a message. + * + * @param $log String + * @param bool $verbose + */ + public static function myLog( $log, $verbose = true ) { + if ( !$verbose ) { + return; + } + if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) { + wfDebug( $log . "\n" ); + } else { + print( $log . "\n" ); + } + } + + /** + * @param $php + * @param $varname + * @return bool|array + */ + public static function parsePHP( $php, $varname ) { + try { + $reader = new QuickArrayReader("<?php $php"); + return $reader->getVar( $varname ); + } catch( Exception $e ) { + self::myLog( "Failed to read file: " . $e ); + return false; + } + } + + /** + * @param $lang + * @return string + * @throws MWException + */ + public static function filename( $lang ) { + global $wgLocalisationUpdateDirectory, $wgCacheDirectory; + + $dir = $wgLocalisationUpdateDirectory ? + $wgLocalisationUpdateDirectory : + $wgCacheDirectory; + + if ( !$dir ) { + throw new MWException( 'No cache directory configured' ); + } + + return "$dir/l10nupdate-$lang.cache"; + } + + /** + * @param $lang + * @return mixed + */ + public static function readFile( $lang ) { + if ( !isset( self::$filecache[$lang] ) ) { + $file = self::filename( $lang ); + $contents = @file_get_contents( $file ); + + if ( $contents === false ) { + wfDebug( "Failed to read file '$file'\n" ); + $retval = array(); + } else { + $retval = unserialize( $contents ); + + if ( $retval === false ) { + wfDebug( "Corrupted data in file '$file'\n" ); + $retval = array(); + } + } + self::$filecache[$lang] = $retval; + } + + return self::$filecache[$lang]; + } + + /** + * @param $lang + * @param $var + * @throws MWException + */ + public static function writeFile( $lang, $var ) { + $file = self::filename( $lang ); + + if ( !@file_put_contents( $file, serialize( $var ) ) ) { + throw new MWException( "Failed to write to file '$file'" ); + } + + self::$filecache[$lang] = $var; + } + +} diff --git a/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php b/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php new file mode 100644 index 00000000..5d52609f --- /dev/null +++ b/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php @@ -0,0 +1,528 @@ +<?php +/** + * Internationalisation file for LocalisationUpdate extension. + * + * @file + * @ingroup Extensions + */ + +$messages = array(); + +/** English + * @author Tom Maaswinkel + */ +$messages['en'] = array( + 'localisationupdate-desc' => 'Keeps the localised messages as up to date as possible', +); + +/** Message documentation (Message documentation) + * @author Fryed-peach + * @author Purodha + * @author Shirayuki + */ +$messages['qqq'] = array( + 'localisationupdate-desc' => '{{desc|name=Localisation Update|url=http://www.mediawiki.org/wiki/Extension:LocalisationUpdate}}', +); + +/** Afrikaans (Afrikaans) + * @author Naudefj + */ +$messages['af'] = array( + 'localisationupdate-desc' => 'Hou die gelokaliseerde boodskappe so op datum as moontlik', +); + +/** Arabic (العربية) + * @author Meno25 + */ +$messages['ar'] = array( + 'localisationupdate-desc' => 'يبقي الرسائل المترجمة محدثة كأفضل ما يكون', +); + +/** Asturian (asturianu) + * @author Xuacu + */ +$messages['ast'] = array( + 'localisationupdate-desc' => 'Caltién los mensaxes llocalizaos tan anovaos como se pueda', +); + +/** Bashkir (башҡортса) + * @author Assele + */ +$messages['ba'] = array( + 'localisationupdate-desc' => 'Локалләштерелгән хәбәрҙәрҙең мөмкин тиклем яңы булыуын тәьмин итә', +); + +/** Bavarian (Boarisch) + * @author Man77 + */ +$messages['bar'] = array( + 'localisationupdate-desc' => "Lokalisiade Texte und Nåchrichtn so aktuell håidn wia's gråd gehd", +); + +/** Belarusian (Taraškievica orthography) (беларуская (тарашкевіца)) + * @author EugeneZelenko + * @author Wizardist + */ +$messages['be-tarask'] = array( + 'localisationupdate-desc' => 'Сочыць за актуальнасьцю лякалізаваных паведамленьняў, наколькі гэта магчыма', +); + +/** Bulgarian (български) + * @author DCLXVI + */ +$messages['bg'] = array( + 'localisationupdate-desc' => 'Поддържа локализираните съобщения възможно най-актуални', +); + +/** Bengali (বাংলা) + * @author Bellayet + */ +$messages['bn'] = array( + 'localisationupdate-desc' => 'স্থানীয়করণকৃত বার্তাসমূহ যথাসম্ভব হালনাগাদ রাখে', +); + +/** Breton (brezhoneg) + * @author Fulup + */ +$messages['br'] = array( + 'localisationupdate-desc' => "Derc'hel da hizivaat ar c'hemennoù troet ken fonnus ha ma'z eus tu", +); + +/** Bosnian (bosanski) + * @author CERminator + */ +$messages['bs'] = array( + 'localisationupdate-desc' => 'Zadržavanje lokaliziranih poruka ažurnim koliko je god moguće', +); + +/** Catalan (català) + * @author Paucabot + */ +$messages['ca'] = array( + 'localisationupdate-desc' => 'Manté els missatges localitzats tan actualitzats com sigui possible', +); + +/** Czech (česky) + * @author Mormegil + */ +$messages['cs'] = array( + 'localisationupdate-desc' => 'Udržuje lokalizovaná hlášení co možná nejaktuálnější', +); + +/** Welsh (Cymraeg) + * @author Lloffiwr + */ +$messages['cy'] = array( + 'localisationupdate-desc' => "Yn diweddaru'r cyfieithiadau o negeseuon mor aml â phosib", +); + +/** Danish (dansk) + * @author Peter Alberti + */ +$messages['da'] = array( + 'localisationupdate-desc' => 'Holder de lokaliserede meddelelser så opdaterede som muligt', +); + +/** German (Deutsch) + * @author Kghbln + * @author Purodha + */ +$messages['de'] = array( + 'localisationupdate-desc' => 'Ermöglicht es lokalisierte Texte und Nachrichten so aktuell wie möglich zu halten', +); + +/** Lower Sorbian (dolnoserbski) + * @author Michawiki + */ +$messages['dsb'] = array( + 'localisationupdate-desc' => 'Źaržy lokalizěrowane powěźeńki tak aktualne ako móžno', +); + +/** Greek (Ελληνικά) + * @author Omnipaedista + */ +$messages['el'] = array( + 'localisationupdate-desc' => 'Διατηρεί τις μεταφράσεις μηνυμάτων όσο πιο ενημερωμένες γίνεται', +); + +/** Esperanto (Esperanto) + * @author Yekrats + */ +$messages['eo'] = array( + 'localisationupdate-desc' => 'Ĝisdatigas la asimilitajn mesaĝojn tiom eble', +); + +/** Spanish (español) + * @author Crazymadlover + */ +$messages['es'] = array( + 'localisationupdate-desc' => 'Mantiene los mensajes localizados tan actualizados como sea posible', +); + +/** Estonian (eesti) + * @author Pikne + */ +$messages['et'] = array( + 'localisationupdate-desc' => 'Hoiab lokaliseeritud sõnumid nii ajakohased kui võimalik.', +); + +/** Basque (euskara) + * @author Kobazulo + */ +$messages['eu'] = array( + 'localisationupdate-desc' => 'Itzulitako mezuak ahalik eta eguneratuen mantentzen ditu', +); + +/** Persian (فارسی) + * @author ZxxZxxZ + */ +$messages['fa'] = array( + 'localisationupdate-desc' => 'پیغامهای محلیسازیشده را تا جای ممکن بهروز نگه میدارد', +); + +/** Finnish (suomi) + * @author Crt + * @author Nike + */ +$messages['fi'] = array( + 'localisationupdate-desc' => 'Pitää ohjelmiston käännöksen ajantasaisena.', +); + +/** French (français) + * @author Crochet.david + */ +$messages['fr'] = array( + 'localisationupdate-desc' => 'Maintenir la traduction des messages à jour autant que possible', +); + +/** Galician (galego) + * @author Toliño + */ +$messages['gl'] = array( + 'localisationupdate-desc' => 'Mantén as mensaxes localizadas tan actualizadas como é posible', +); + +/** Swiss German (Alemannisch) + * @author Als-Holder + */ +$messages['gsw'] = array( + 'localisationupdate-desc' => 'Halt d Syschtemnochrichte so aktuälle wie megli', +); + +/** Hebrew (עברית) + * @author YaronSh + */ +$messages['he'] = array( + 'localisationupdate-desc' => 'שמירת ההודעות המתורגמות מעודכנות ככל הניתן', +); + +/** Hiligaynon (Ilonggo) + * @author Tagimata + */ +$messages['hil'] = array( + 'localisationupdate-desc' => 'Gatugo sang mga mensahe nga lokal para mapahibalo sang madali', +); + +/** Croatian (hrvatski) + * @author SpeedyGonsales + */ +$messages['hr'] = array( + 'localisationupdate-desc' => 'Dogradnja za osvježavanje lokalizacije poruka MediaWikija', +); + +/** Upper Sorbian (hornjoserbsce) + * @author Michawiki + */ +$messages['hsb'] = array( + 'localisationupdate-desc' => 'Dźerži lokalizowane zdźělenki tak aktualne kaž móžno', +); + +/** Hungarian (magyar) + * @author Glanthor Reviol + */ +$messages['hu'] = array( + 'localisationupdate-desc' => 'Frissíti a lefordított üzeneteket', +); + +/** Interlingua (interlingua) + * @author McDutchie + */ +$messages['ia'] = array( + 'localisationupdate-desc' => 'Mantene le messages localisate tanto actual como possibile', +); + +/** Indonesian (Bahasa Indonesia) + * @author Bennylin + */ +$messages['id'] = array( + 'localisationupdate-desc' => 'Mengusahakan agar pesan-pesan yang telah diterjemahkan tetap semutakhir mungkin', +); + +/** Iloko (Ilokano) + * @author Lam-ang + */ +$messages['ilo'] = array( + 'localisationupdate-desc' => 'Taginayonenna a mapabaro dagiti naipatarus a mensahe', +); + +/** Italian (italiano) + * @author Darth Kule + */ +$messages['it'] = array( + 'localisationupdate-desc' => 'Mantiene i messaggi localizzati quanto più aggiornati è possibile', +); + +/** Japanese (日本語) + * @author Fryed-peach + * @author Shirayuki + */ +$messages['ja'] = array( + 'localisationupdate-desc' => 'メッセージの翻訳をできるだけ最新に保つ', +); + +/** Khmer (ភាសាខ្មែរ) + * @author វ័ណថារិទ្ធ + */ +$messages['km'] = array( + 'localisationupdate-desc' => 'រក្សាសារដែលបានប្រែសម្រួលទាំងឡាយ អោយនៅទាន់សម័យតាមដែលអាចធ្វើទៅបាន', +); + +/** Korean (한국어) + * @author Kwj2772 + */ +$messages['ko'] = array( + 'localisationupdate-desc' => '번역된 시스템 메시지를 가능한 한 최신으로 유지', +); + +/** Colognian (Ripoarisch) + * @author Purodha + */ +$messages['ksh'] = array( + 'localisationupdate-desc' => 'Texte un Nohreeschte vum Wiki esu joot wi müjjelich om neueste Shtand halde', +); + +/** Luxembourgish (Lëtzebuergesch) + * @author Robby + */ +$messages['lb'] = array( + 'localisationupdate-desc' => 'hält déi lokaliséiert Messagen esou aktuell wéi méiglech.', +); + +/** Macedonian (македонски) + * @author Bjankuloski06 + */ +$messages['mk'] = array( + 'localisationupdate-desc' => 'Ги одржува локализираните пораки колку што е можно пообновени и повеќе во тек со настаните', +); + +/** Malayalam (മലയാളം) + * @author Praveenp + */ +$messages['ml'] = array( + 'localisationupdate-desc' => 'പ്രാദേശികഭാഷയിലാക്കിയ സന്ദേശങ്ങൾ കഴിയുന്നത്ര വേഗം ചേർക്കാൻ ഉപയോഗിക്കുന്നു', +); + +/** Malay (Bahasa Melayu) + * @author Anakmalaysia + */ +$messages['ms'] = array( + 'localisationupdate-desc' => 'Memastikan kekemaskinian mesej-mesej yang disetempatkan', +); + +/** Norwegian Bokmål (norsk (bokmål)) + * @author Nghtwlkr + */ +$messages['nb'] = array( + 'localisationupdate-desc' => 'Holder de lokaliserte meldingene så oppdaterte som mulig', +); + +/** Dutch (Nederlands) + * @author Siebrand + */ +$messages['nl'] = array( + 'localisationupdate-desc' => 'Houdt de gelokaliseerde berichten zo actueel mogelijk', +); + +/** Norwegian Nynorsk (norsk (nynorsk)) + * @author Gunnernett + */ +$messages['nn'] = array( + 'localisationupdate-desc' => 'Held dei lokaliserte meldingane så oppdaterte som mogleg', +); + +/** Occitan (occitan) + * @author Cedric31 + */ +$messages['oc'] = array( + 'localisationupdate-desc' => 'Manténer la traduccion dels messatges a jorn autant que possible', +); + +/** Polish (polski) + * @author Sp5uhe + */ +$messages['pl'] = array( + 'localisationupdate-desc' => 'Uaktualnia lokalne komunikaty w miarę możliwości na bieżąco', +); + +/** Piedmontese (Piemontèis) + * @author Dragonòt + */ +$messages['pms'] = array( + 'localisationupdate-desc' => 'A manten i messagi localisà ël pì agiornà possìbil', +); + +/** Portuguese (português) + * @author Hamilton Abreu + * @author Malafaya + */ +$messages['pt'] = array( + 'localisationupdate-desc' => 'Mantém as mensagens localizadas tão actualizadas quanto possível', +); + +/** Brazilian Portuguese (português do Brasil) + * @author Eduardo.mps + */ +$messages['pt-br'] = array( + 'localisationupdate-desc' => 'Mantém as mensagens localizadas tão atualizadas quanto possível', +); + +/** Romanian (română) + * @author KlaudiuMihaila + */ +$messages['ro'] = array( + 'localisationupdate-desc' => 'Menține mesajele localizate cât mai actualizate', +); + +/** tarandíne (tarandíne) + * @author Joetaras + */ +$messages['roa-tara'] = array( + 'localisationupdate-desc' => "Mandine le messagge localizzate 'u cchiù aggiornate possibbile", +); + +/** Russian (русский) + * @author Александр Сигачёв + */ +$messages['ru'] = array( + 'localisationupdate-desc' => 'Поддерживает актуальность локализованных сообщений, насколько это возможно', +); + +/** Slovak (slovenčina) + * @author Helix84 + */ +$messages['sk'] = array( + 'localisationupdate-desc' => 'Udržiava lokalizované správy čo najaktuálnejšie', +); + +/** Serbian (Cyrillic script) (српски (ћирилица)) + * @author Михајло Анђелковић + */ +$messages['sr-ec'] = array( + 'localisationupdate-desc' => 'Ажурира локализоване поруке колико је то могуће', +); + +/** Serbian (Latin script) (srpski (latinica)) + * @author Liangent + */ +$messages['sr-el'] = array( + 'localisationupdate-desc' => 'Ažurira lokalizovane poruke koliko je to moguće', +); + +/** Sundanese (Basa Sunda) + * @author Kandar + */ +$messages['su'] = array( + 'localisationupdate-desc' => 'Ngajaga sangkan talatah-talatah nu geus dialihbasakeun salawasnya mutahir', +); + +/** Swedish (svenska) + * @author Boivie + */ +$messages['sv'] = array( + 'localisationupdate-desc' => 'Håller de lokaliserade meddelandena så uppdaterade som möjligt', +); + +/** Tamil (தமிழ்) + * @author செல்வா + */ +$messages['ta'] = array( + 'localisationupdate-desc' => 'உட்சூழலுக்கான செய்திகளை கூடியமட்டிலும் இன்றையநிலையில் வைக்கப்பட்டுள்ளன', +); + +/** Telugu (తెలుగు) + * @author Veeven + */ +$messages['te'] = array( + 'localisationupdate-desc' => 'స్ధానికీకరించిన సందేశాలను సాధ్యమైనంత తాజాగా ఉంచుతుంది', +); + +/** Tagalog (Tagalog) + * @author AnakngAraw + */ +$messages['tl'] = array( + 'localisationupdate-desc' => 'Pinananatili ang mga mensaheng lokalisado bilang pinaka nasasapanahon', +); + +/** Turkish (Türkçe) + * @author Joseph + */ +$messages['tr'] = array( + 'localisationupdate-desc' => 'Yerelleştirilen mesajları mümkün olabildiğince güncel tutar', +); + +/** Ukrainian (українська) + * @author Prima klasy4na + */ +$messages['uk'] = array( + 'localisationupdate-desc' => 'Забезпечує оновлення локалізованих повідомлень у міру можливості', +); + +/** Veps (vepsän kel’) + * @author Игорь Бродский + */ +$messages['vep'] = array( + 'localisationupdate-desc' => 'Pidab lokaliziruidud tedotused veresin, ku voib', +); + +/** Vietnamese (Tiếng Việt) + * @author Vinhtantran + */ +$messages['vi'] = array( + 'localisationupdate-desc' => 'Giữ các thông điệp bản địa hóa được cập nhật nhất có thể', +); + +/** Walloon (walon) + * @author Srtxg + */ +$messages['wa'] = array( + 'localisationupdate-desc' => "Po wårder les ratournaedjes di l' eterface li pus a djoû possibe", +); + +/** Yiddish (ייִדיש) + * @author פוילישער + */ +$messages['yi'] = array( + 'localisationupdate-desc' => 'האלטן די לאקאליזירטע מעלדונגען אקטועל ווי נאר מעגלעך', +); + +/** Cantonese (粵語) + * @author Tom Maaswinkel + */ +$messages['yue'] = array( + 'localisationupdate-desc' => '將本地化嘅信息保持最新', +); + +/** Simplified Chinese (中文(简体)) + * @author Tom Maaswinkel + */ +$messages['zh-hans'] = array( + 'localisationupdate-desc' => '将本地化的信息保持最新', +); + +/** Traditional Chinese (中文(繁體)) + * @author Mark85296341 + * @author Tom Maaswinkel + */ +$messages['zh-hant'] = array( + 'localisationupdate-desc' => '將本地化的資訊盡可能保持最新', +); diff --git a/extensions/LocalisationUpdate/LocalisationUpdate.php b/extensions/LocalisationUpdate/LocalisationUpdate.php new file mode 100644 index 00000000..89f0659d --- /dev/null +++ b/extensions/LocalisationUpdate/LocalisationUpdate.php @@ -0,0 +1,42 @@ +<?php + + +/** + * Directory to store serialized cache files in. Defaults to $wgCacheDirectory. + * It's OK to share this directory among wikis as long as the wiki you run + * update.php on has all extensions the other wikis using the same directory + * have. + * NOTE: If this variable and $wgCacheDirectory are both false, this extension + * WILL NOT WORK. + */ +$wgLocalisationUpdateDirectory = false; + + +/** + * These should point to either an HTTP-accessible file or local file system. + * $1 is the name of the repo (for extensions) and $2 is the name of file in the repo. + */ +$wgLocalisationUpdateCoreURL = "https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/core.git;a=blob_plain;f=$2;hb=HEAD"; +$wgLocalisationUpdateExtensionURL = "https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/extensions/$1.git;a=blob_plain;f=$2;hb=HEAD"; + +/// Deprecated +$wgLocalisationUpdateSVNURL = false; + +$wgLocalisationUpdateRetryAttempts = 5; + +// Info about me! +$wgExtensionCredits['other'][] = array( + 'path' => __FILE__, + 'name' => 'LocalisationUpdate', + 'author' => array( 'Tom Maaswinkel', 'Niklas Laxström', 'Roan Kattouw' ), + 'version' => '1.0', + 'url' => 'https://www.mediawiki.org/wiki/Extension:LocalisationUpdate', + 'descriptionmsg' => 'localisationupdate-desc', +); + +$wgHooks['LocalisationCacheRecache'][] = 'LocalisationUpdate::onRecache'; + +$dir = __DIR__ . '/'; +$wgExtensionMessagesFiles['LocalisationUpdate'] = $dir . 'LocalisationUpdate.i18n.php'; +$wgAutoloadClasses['LocalisationUpdate'] = $dir . 'LocalisationUpdate.class.php'; +$wgAutoloadClasses['QuickArrayReader'] = $dir . 'QuickArrayReader.php'; diff --git a/extensions/LocalisationUpdate/QuickArrayReader.php b/extensions/LocalisationUpdate/QuickArrayReader.php new file mode 100644 index 00000000..214d5a61 --- /dev/null +++ b/extensions/LocalisationUpdate/QuickArrayReader.php @@ -0,0 +1,187 @@ +<?php + +/** + * Quickie parser class that can happily read the subset of PHP we need + * for our localization arrays safely. + * + * About an order of magnitude faster than ConfEditor(), but still an + * order of magnitude slower than eval(). + */ +class QuickArrayReader { + var $vars = array(); + + /** + * @param $string string + */ + function __construct( $string ) { + $scalarTypes = array( + T_LNUMBER => true, + T_DNUMBER => true, + T_STRING => true, + T_CONSTANT_ENCAPSED_STRING => true, + ); + $skipTypes = array( + T_WHITESPACE => true, + T_COMMENT => true, + T_DOC_COMMENT => true, + ); + $tokens = token_get_all( $string ); + $count = count( $tokens ); + for( $i = 0; $i < $count; ) { + while( isset($skipTypes[$tokens[$i][0]] ) ) { + $i++; + } + switch( $tokens[$i][0] ) { + case T_OPEN_TAG: + $i++; + continue; + case T_VARIABLE: + // '$messages' -> 'messages' + $varname = trim( substr( $tokens[$i][1], 1 ) ); + $varindex = null; + + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( $tokens[$i] === '[' ) { + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( isset($scalarTypes[$tokens[$i][0]] ) ) { + $varindex = $this->parseScalar( $tokens[$i] ); + } else { + throw $this->except( $tokens[$i], 'scalar index' ); + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( $tokens[$i] !== ']' ) { + throw $this->except( $tokens[$i], ']' ); + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + } + + if( $tokens[$i] !== '=' ) { + throw $this->except( $tokens[$i], '=' ); + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( isset($scalarTypes[$tokens[$i][0]] ) ) { + $buildval = $this->parseScalar( $tokens[$i] ); + } elseif( $tokens[$i][0] === T_ARRAY ) { + while( isset($skipTypes[$tokens[++$i][0]] ) ); + if( $tokens[$i] !== '(' ) { + throw $this->except( $tokens[$i], '(' ); + } + $buildval = array(); + do { + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( $tokens[$i] === ')' ) { + break; + } + if( isset($scalarTypes[$tokens[$i][0]] ) ) { + $key = $this->parseScalar( $tokens[$i] ); + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( $tokens[$i][0] !== T_DOUBLE_ARROW ) { + throw $this->except( $tokens[$i], '=>' ); + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( isset($scalarTypes[$tokens[$i][0]] ) ) { + $val = $this->parseScalar( $tokens[$i] ); + } + @$buildval[$key] = $val; + while( isset($skipTypes[$tokens[++$i][0]] ) ); + + if( $tokens[$i] === ',' ) { + continue; + } elseif( $tokens[$i] === ')' ) { + break; + } else { + throw $this->except( $tokens[$i], ', or )' ); + } + } while(true); + } else { + throw $this->except( $tokens[$i], 'scalar or array' ); + } + if( is_null( $varindex ) ) { + $this->vars[$varname] = $buildval; + } else { + @$this->vars[$varname][$varindex] = $buildval; + } + while( isset($skipTypes[$tokens[++$i][0]] ) ); + if( $tokens[$i] !== ';' ) { + throw $this->except($tokens[$i], ';'); + } + $i++; + break; + default: + throw $this->except($tokens[$i], 'open tag, whitespace, or variable.'); + } + } + } + + /** + * @param $got string + * @param $expected string + * @return Exception + */ + private function except( $got, $expected ) { + if( is_array( $got ) ) { + $got = token_name( $got[0] ) . " ('" . $got[1] . "')"; + } else { + $got = "'" . $got . "'"; + } + return new Exception( "Expected $expected, got $got" ); + } + + /** + * Parse a scalar value in PHP + * + * @param $token string + * + * @return mixed Parsed value + */ + function parseScalar( $token ) { + if( is_array( $token ) ) { + $str = $token[1]; + } else { + $str = $token; + } + if ( $str !== '' && $str[0] == '\'' ) + // Single-quoted string + // @fixme trim() call is due to mystery bug where whitespace gets + // appended to the token; without it we ended up reading in the + // extra quote on the end! + return strtr( substr( trim( $str ), 1, -1 ), + array( '\\\'' => '\'', '\\\\' => '\\' ) ); + if ( $str !== '' && @$str[0] == '"' ) + // Double-quoted string + // @fixme trim() call is due to mystery bug where whitespace gets + // appended to the token; without it we ended up reading in the + // extra quote on the end! + return stripcslashes( substr( trim( $str ), 1, -1 ) ); + if ( substr( $str, 0, 4 ) === 'true' ) + return true; + if ( substr( $str, 0, 5 ) === 'false' ) + return false; + if ( substr( $str, 0, 4 ) === 'null' ) + return null; + // Must be some kind of numeric value, so let PHP's weak typing + // be useful for a change + return $str; + } + + /** + * @param $varname string + * @return null|string + */ + function getVar( $varname ) { + if( isset( $this->vars[$varname] ) ) { + return $this->vars[$varname]; + } else { + return null; + } + } +} + diff --git a/extensions/LocalisationUpdate/README_FIRST.txt b/extensions/LocalisationUpdate/README_FIRST.txt new file mode 100644 index 00000000..3973c435 --- /dev/null +++ b/extensions/LocalisationUpdate/README_FIRST.txt @@ -0,0 +1,8 @@ +To install this extension first include +LocalisationUpdate/LocalisationUpdate.php in your LocalSettings.php + +Then add the required new tables to your database by running +php maintenance/update.php on the command line. + +Whenever you want to run an update, run +php extensions/LocalisationUpdate/update.php on the command line. diff --git a/extensions/LocalisationUpdate/update.php b/extensions/LocalisationUpdate/update.php new file mode 100644 index 00000000..750fc4f2 --- /dev/null +++ b/extensions/LocalisationUpdate/update.php @@ -0,0 +1,38 @@ +<?php + +$IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' + ? getenv( 'MW_INSTALL_PATH' ) + : realpath( dirname( __FILE__ ) . "/../../" ); + +// TODO: migrate to maintenance class +require_once( "$IP/maintenance/commandLine.inc" ); + +if( isset( $options['help'] ) ) { + print "Fetches updated localisation files from MediaWiki development SVN\n"; + print "and saves into local database to merge with release defaults.\n"; + print "\n"; + print "Usage: php extensions/LocalisationUpdate/update.php\n"; + print "Options:\n"; + print " --quiet Suppress progress output\n"; + print " --skip-core Don't fetch MediaWiki core files\n"; + print " --skip-extensions Don't fetch any extension files\n"; + print " --all Fetch all present extensions, not just those enabled\n"; + print " --outdir=<dir> Override output directory for serialized update files\n"; + print " --svnurl=<url> URL to SVN repository, or path to local SVN checkout. Deprecated.\n"; + print "\n"; + exit( 0 ); +} + + +$starttime = microtime( true ); + +// Prevent the script from timing out +set_time_limit( 0 ); +ini_set( "max_execution_time", 0 ); +ini_set( 'memory_limit', -1 ); + +LocalisationUpdate::updateMessages( $options ); + +$endtime = microtime( true ); +$totaltime = ( $endtime - $starttime ); +print "All done in " . $totaltime . " seconds\n"; |