diff options
Diffstat (limited to 'extensions/LocalisationUpdate')
94 files changed, 1523 insertions, 1245 deletions
diff --git a/extensions/LocalisationUpdate/Autoload.php b/extensions/LocalisationUpdate/Autoload.php new file mode 100644 index 00000000..0b3f14dc --- /dev/null +++ b/extensions/LocalisationUpdate/Autoload.php @@ -0,0 +1,31 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +global $wgAutoloadClasses; +$dir = __DIR__; + +$wgAutoloadClasses += array( + 'LocalisationUpdate' => "$dir/LocalisationUpdate.class.php", + 'LU_Updater' => "$dir/Updater.php", + 'QuickArrayReader' => "$dir/QuickArrayReader.php", + + # fetcher + 'LU_Fetcher' => "$dir/fetcher/Fetcher.php", + 'LU_FetcherFactory' => "$dir/fetcher/FetcherFactory.php", + 'LU_FileSystemFetcher' => "$dir/fetcher/FileSystemFetcher.php", + 'LU_GitHubFetcher' => "$dir/fetcher/GitHubFetcher.php", + 'LU_HttpFetcher' => "$dir/fetcher/HttpFetcher.php", + + # finder + 'LU_Finder' => "$dir/finder/Finder.php", + + # reader + 'LU_JSONReader' => "$dir/reader/JSONReader.php", + 'LU_PHPReader' => "$dir/reader/PHPReader.php", + 'LU_Reader' => "$dir/reader/Reader.php", + 'LU_ReaderFactory' => "$dir/reader/ReaderFactory.php", +); diff --git a/extensions/LocalisationUpdate/KNOWN_ISSUES.txt b/extensions/LocalisationUpdate/KNOWN_ISSUES.txt deleted file mode 100644 index 7ce14cd0..00000000 --- a/extensions/LocalisationUpdate/KNOWN_ISSUES.txt +++ /dev/null @@ -1,11 +0,0 @@ -- 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 index 66b63232..349c35f0 100644 --- a/extensions/LocalisationUpdate/LocalisationUpdate.class.php +++ b/extensions/LocalisationUpdate/LocalisationUpdate.class.php @@ -1,596 +1,81 @@ <?php /** - * Class for localization updates. - * - * TODO: refactor code to remove duplication + * Class for localization update hooks and static methods. */ 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; - } + /** @todo Remove this once pre-1.24 versions of MW are no longer supported. */ + private static $onRecacheFallbackCalled = false; /** - * Called from the cronjob to fetch new messages from SVN. - * - * @param $options Array - * - * @return true + * Hook: LocalisationCacheRecacheFallback */ - 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; + public static function onRecacheFallback( LocalisationCache $lc, $code, array &$cache ) { + self::$onRecacheFallbackCalled = true; - // Update all MW core messages. - if( !$skipCore ) { - $result = self::updateMediawikiMessages( $verbose, $coreUrl ); + $dir = LocalisationUpdate::getDirectory(); + if ( !$dir ) { + return true; } - // 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 ); - } + $fileName = "$dir/" . self::getFilename( $code ); + if ( is_readable( $fileName ) ) { + $data = FormatJson::decode( file_get_contents( $fileName ), true ); + $cache['messages'] = array_merge( $cache['messages'], $data ); } - 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', '$3', '$4' ), - array( $ext, $extFile, urlencode( $ext ), urlencode( $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( - array( '$2', '$4' ), - array( 'languages/messages/MessagesEn.php', 'languages%2Fmessages%2FMessagesEn.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( - array( '$2', '$4' ), - array( $filename, urlencode( $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 + * Hook: LocalisationCacheRecache */ - 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(); + public static function onRecache( LocalisationCache $lc, $code, array &$cache ) { + $dir = LocalisationUpdate::getDirectory(); + if ( !$dir ) { + return true; + } + + $codeSequence = array_merge( array( $code ), $cache['fallbackSequence'] ); + foreach ( $codeSequence as $csCode ) { + $fileName = "$dir/" . self::getFilename( $csCode ); + if ( !self::$onRecacheFallbackCalled && is_readable( $fileName ) ) { + // We're on an old version of MW that doesn't have the hook + // needed to do things correctly. L10n will be broken here in + // certain reasonably-common situations (see bug 68781), but + // there's nothing we can do about it. + $data = FormatJson::decode( file_get_contents( $fileName ), true ); + $cache['messages'] = array_merge( $cache['messages'], $data ); } - - $updates += self::compareLanguageArrays( $language, $old_messages[$language], $messages, $verbose, $forbiddenKeys, true ); + $cache['deps'][] = new FileDependency( $fileName ); } - // 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 ); + return true; } /** - * Logs a message. + * Returns a directory where updated translations are stored. * - * @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 + * @return string|false False if not configured. + * @since 1.1 */ - public static function filename( $lang ) { + public static function getDirectory() { global $wgLocalisationUpdateDirectory, $wgCacheDirectory; - $dir = $wgLocalisationUpdateDirectory ? + // ?: can be used once we drop support for MW 1.19 + return $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 + * Returns a filename where updated translations are stored. + * + * @param string $language Language tag + * @return string + * @since 1.1 */ - 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; + public static function getFilename( $language ) { + return "l10nupdate-$language.json"; } - } diff --git a/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php b/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php index c45af9e2..f715d123 100644 --- a/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php +++ b/extensions/LocalisationUpdate/LocalisationUpdate.i18n.php @@ -1,530 +1,35 @@ <?php /** - * Internationalisation file for LocalisationUpdate extension. + * This is a backwards-compatibility shim, generated by: + * https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/maintenance%2FgenerateJsonI18n.php * - * @file - * @ingroup Extensions + * Beginning with MediaWiki 1.23, translation strings are stored in json files, + * and the EXTENSION.i18n.php file only exists to provide compatibility with + * older releases of MediaWiki. For more information about this migration, see: + * https://www.mediawiki.org/wiki/Requests_for_comment/Localisation_format + * + * This shim maintains compatibility back to MediaWiki 1.17. */ - $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 - * @author 아라 - */ -$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 Luckas - * @author Malafaya - */ -$messages['pt'] = array( - 'localisationupdate-desc' => 'Mantém as mensagens localizadas tão atualizadas 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' => '將本地化的資訊盡可能保持最新', -); +if ( !function_exists( 'wfJsonI18nShim2520f8a069e9942f' ) ) { + function wfJsonI18nShim2520f8a069e9942f( $cache, $code, &$cachedData ) { + $codeSequence = array_merge( array( $code ), $cachedData['fallbackSequence'] ); + foreach ( $codeSequence as $csCode ) { + $fileName = dirname( __FILE__ ) . "/i18n/$csCode.json"; + if ( is_readable( $fileName ) ) { + $data = FormatJson::decode( file_get_contents( $fileName ), true ); + foreach ( array_keys( $data ) as $key ) { + if ( $key === '' || $key[0] === '@' ) { + unset( $data[$key] ); + } + } + $cachedData['messages'] = array_merge( $data, $cachedData['messages'] ); + } + + $cachedData['deps'][] = new FileDependency( $fileName ); + } + return true; + } + + $GLOBALS['wgHooks']['LocalisationCacheRecache'][] = 'wfJsonI18nShim2520f8a069e9942f'; +} diff --git a/extensions/LocalisationUpdate/LocalisationUpdate.php b/extensions/LocalisationUpdate/LocalisationUpdate.php index abec5406..53aa5adc 100644 --- a/extensions/LocalisationUpdate/LocalisationUpdate.php +++ b/extensions/LocalisationUpdate/LocalisationUpdate.php @@ -1,6 +1,5 @@ <?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 @@ -11,34 +10,46 @@ */ $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. - * $3 and $4 are the same, respectively, but urlencoded for e.g. gitblit. + * Default repository source to use. + * @since 2014-03 */ +$wgLocalisationUpdateRepository = 'github'; -$wgLocalisationUpdateCoreURL = "https://git.wikimedia.org/raw/mediawiki%2Fcore.git/HEAD/$4"; -$wgLocalisationUpdateExtensionURL = "https://git.wikimedia.org/raw/mediawiki%2Fextensions%2F$3.git/HEAD/$4"; - -/// Deprecated -$wgLocalisationUpdateSVNURL = false; +/** + * Available repository sources. + * @since 2014-03 + */ +$wgLocalisationUpdateRepositories = array(); +$wgLocalisationUpdateRepositories['github'] = array( + 'mediawiki' => + 'https://raw.github.com/wikimedia/mediawiki-core/master/%PATH%', + 'extension' => + 'https://raw.github.com/wikimedia/mediawiki-extensions-%NAME%/master/%PATH%', +); -$wgLocalisationUpdateRetryAttempts = 5; +// Example for local filesystem configuration +#$wgLocalisationUpdateRepositories['local'] = array( +# 'mediawiki' => +# 'file:///resources/projects/mediawiki/master/%PATH%', +# 'extension' => +# 'file:///resources/projects/mediawiki-extensions/extensions/%NAME%/%PATH%', +#); -// 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', + 'path' => __FILE__, + 'name' => 'LocalisationUpdate', + 'author' => array( 'Tom Maaswinkel', 'Niklas Laxström', 'Roan Kattouw' ), + 'version' => '1.3.0', + 'url' => 'https://www.mediawiki.org/wiki/Extension:LocalisationUpdate', 'descriptionmsg' => 'localisationupdate-desc', ); $wgHooks['LocalisationCacheRecache'][] = 'LocalisationUpdate::onRecache'; +$wgHooks['LocalisationCacheRecacheFallback'][] = 'LocalisationUpdate::onRecacheFallback'; + +$dir = __DIR__; +$wgMessagesDirs['LocalisationUpdate'] = __DIR__ . '/i18n'; +$wgExtensionMessagesFiles['LocalisationUpdate'] = "$dir/LocalisationUpdate.i18n.php"; -$dir = __DIR__ . '/'; -$wgExtensionMessagesFiles['LocalisationUpdate'] = $dir . 'LocalisationUpdate.i18n.php'; -$wgAutoloadClasses['LocalisationUpdate'] = $dir . 'LocalisationUpdate.class.php'; -$wgAutoloadClasses['QuickArrayReader'] = $dir . 'QuickArrayReader.php'; +require "$dir/Autoload.php"; diff --git a/extensions/LocalisationUpdate/QuickArrayReader.php b/extensions/LocalisationUpdate/QuickArrayReader.php index 214d5a61..691f4a6b 100644 --- a/extensions/LocalisationUpdate/QuickArrayReader.php +++ b/extensions/LocalisationUpdate/QuickArrayReader.php @@ -4,11 +4,10 @@ * 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(). + * Still an order of magnitude slower than eval(). */ class QuickArrayReader { - var $vars = array(); + private $vars = array(); /** * @param $string string @@ -27,96 +26,100 @@ class QuickArrayReader { ); $tokens = token_get_all( $string ); $count = count( $tokens ); - for( $i = 0; $i < $count; ) { - while( isset($skipTypes[$tokens[$i][0]] ) ) { + 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]] ) ); + 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] === '[' ) { - 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]] ) ) { - $varindex = $this->parseScalar( $tokens[$i] ); + 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] ); + } + wfSuppressWarnings(); + $buildval[$key] = $val; + wfRestoreWarnings(); + 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 index' ); + throw $this->except( $tokens[$i], 'scalar or array' ); } - while( isset($skipTypes[$tokens[++$i][0]] ) ); - - if( $tokens[$i] !== ']' ) { - throw $this->except( $tokens[$i], ']' ); + if ( is_null( $varindex ) ) { + $this->vars[$varname] = $buildval; + } else { + wfSuppressWarnings(); + $this->vars[$varname][$varindex] = $buildval; + wfRestoreWarnings(); } - 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], '(' ); + 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.'); + $i++; + break; + default: + throw $this->except( $tokens[$i], 'open tag, whitespace, or variable.' ); } } } @@ -127,11 +130,12 @@ class QuickArrayReader { * @return Exception */ private function except( $got, $expected ) { - if( is_array( $got ) ) { + if ( is_array( $got ) ) { $got = token_name( $got[0] ) . " ('" . $got[1] . "')"; } else { $got = "'" . $got . "'"; } + return new Exception( "Expected $expected, got $got" ); } @@ -143,30 +147,43 @@ class QuickArrayReader { * @return mixed Parsed value */ function parseScalar( $token ) { - if( is_array( $token ) ) { + if ( is_array( $token ) ) { $str = $token[1]; } else { $str = $token; } - if ( $str !== '' && $str[0] == '\'' ) + 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] == '"' ) + } + + wfSuppressWarnings(); + 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! + wfRestoreWarnings(); return stripcslashes( substr( trim( $str ), 1, -1 ) ); - if ( substr( $str, 0, 4 ) === 'true' ) + } + wfRestoreWarnings(); + + if ( substr( $str, 0, 4 ) === 'true' ) { return true; - if ( substr( $str, 0, 5 ) === 'false' ) + } + + if ( substr( $str, 0, 5 ) === 'false' ) { return false; - if ( substr( $str, 0, 4 ) === 'null' ) + } + + 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; @@ -177,7 +194,7 @@ class QuickArrayReader { * @return null|string */ function getVar( $varname ) { - if( isset( $this->vars[$varname] ) ) { + if ( isset( $this->vars[$varname] ) ) { return $this->vars[$varname]; } else { return null; diff --git a/extensions/LocalisationUpdate/README b/extensions/LocalisationUpdate/README new file mode 100644 index 00000000..3df784b5 --- /dev/null +++ b/extensions/LocalisationUpdate/README @@ -0,0 +1,34 @@ +== Localisation Update == +Localisation Update extension can update the MediaWiki messages at any time, +without needing to upgrade the MediaWiki software. + +For more information see: + https://www.mediawiki.org/wiki/Extension:LocalisationUpdate + +== Installation == +1. Add the following to LocalSettings.php of your MediaWiki setup: + + require_once "$IP/extensions/LocalisationUpdate/LocalisationUpdate.php"; + $wgLocalisationUpdateDirectory = "$IP/cache"; + +2. Create a cache folder in the installation directory, and be sure the server +has permissions to write on it. + +If localization updates don't seem to come through, you may need to run, + + php maintenance/rebuildLocalisationCache.php --force. + +3. Whenever you want to run an update, run, + + php extensions/LocalisationUpdate/update.php + +For detailed help, see: + + php extensions/LocalisationUpdate/update.php --help + +4. If you are on Unix like system, you should add LocalisationUpdate to +crontab: + + crontab -e + # Add the following line + @daily php /path/to/your/wiki/extensions/LocalisationUpdate/update.php --quiet diff --git a/extensions/LocalisationUpdate/README_FIRST.txt b/extensions/LocalisationUpdate/README_FIRST.txt deleted file mode 100644 index 3973c435..00000000 --- a/extensions/LocalisationUpdate/README_FIRST.txt +++ /dev/null @@ -1,8 +0,0 @@ -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/Updater.php b/extensions/LocalisationUpdate/Updater.php new file mode 100644 index 00000000..bae492dd --- /dev/null +++ b/extensions/LocalisationUpdate/Updater.php @@ -0,0 +1,194 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Executes the localisation update. + */ +class LU_Updater { + /** + * Whether the path is a pattern and thus we need to use appropriate + * code for fetching directories. + * + * @param string $path Url + * @return bool + */ + public function isDirectory( $path ) { + $filename = basename( $path ); + return strpos( $filename, '*' ) !== false; + } + + /** + * Expands repository relative path to full url with the given repository + * patterns. Extra variables in $info are used as variables and will be + * replaced the pattern. + * + * @param array $info Component information. + * @param array $repos Repository information. + * @return string + */ + public function expandRemotePath( $info, $repos ) { + $pattern = $repos[$info['repo']]; + unset( $info['repo'], $info['orig'] ); + + // This assumes all other keys are used as variables + // in the pattern. For example name -> %NAME%. + $keys = array(); + foreach ( array_keys( $info ) as $key ) { + $keys[] = '%' . strtoupper( $key ) . '%'; + } + + $values = array_values( $info ); + return str_replace( $keys, $values, $pattern ); + } + + /** + * Parses translations from given list of files. + * + * @param LU_ReaderFactory $readerFactory Factory to construct parsers. + * @param array $files List of files with their contents as array values. + * @return array List of translations indexed by language code. + */ + public function readMessages( LU_ReaderFactory $readerFactory, array $files ) { + $messages = array(); + + foreach ( $files as $filename => $contents ) { + $reader = $readerFactory->getReader( $filename ); + try { + $parsed = $reader->parse( $contents ); + } catch ( Exception $e ) { + trigger_error( __METHOD__ . ": Unable to parse messages from $filename", E_USER_WARNING ); + continue; + } + + foreach ( $parsed as $code => $langMessages ) { + if ( !isset( $messages[$code] ) ) { + $messages[$code] = array(); + } + $messages[$code] = array_merge( $messages[$code], $langMessages ); + } + + $c = array_sum( array_map( 'count', $parsed ) ); + // Useful for debugging, maybe create interface to pass this to the script? + #echo "$filename with " . get_class( $reader ) . " and $c\n"; + } + + return $messages; + } + + /** + * Find new and changed translations in $remote and returns them. + * + * @param array $origin + * @param array $remote + * @param array [$blacklist] Array of message keys to ignore, keys as as array keys. + * @return array + */ + public function findChangedTranslations( $origin, $remote, $blacklist = array() ) { + $changed = array(); + foreach ( $remote as $key => $value ) { + if ( isset( $blacklist[$key] ) ) { + continue; + } + + if ( !isset( $origin[$key] ) || $value !== $origin[$key] ) { + $changed[$key] = $value; + } + } + return $changed; + } + + /** + * Fetches files from given Url pattern. + * + * @param LU_FetcherFactory $factory Factory to construct fetchers. + * @param string $path Url to the file or pattern of files. + * @return array List of Urls with file contents as path. + */ + public function fetchFiles( LU_FetcherFactory $factory, $path ) { + $fetcher = $factory->getFetcher( $path ); + + if ( $this->isDirectory( $path ) ) { + $files = $fetcher->fetchDirectory( $path ); + } else { + $files = array( $path => $fetcher->fetchFile( $path ) ); + } + + // Remove files which were not found + return array_filter( $files ); + } + + public function execute( + LU_Finder $finder, + LU_ReaderFactory $readerFactory, + LU_FetcherFactory $fetcherFactory, + array $repos + ) { + + $components = $finder->getComponents(); + + $updatedMessages = array(); + + foreach ( $components as $key => $info ) { + $originFiles = $this->fetchFiles( $fetcherFactory, $info['orig'] ); + $remoteFiles = $this->fetchFiles( $fetcherFactory, $this->expandRemotePath( $info, $repos ) ); + + if ( $remoteFiles === array() ) { + // Small optimization: if nothing to compare with, skip + continue; + } + + $originMessages = $this->readMessages( $readerFactory, $originFiles ); + $remoteMessages = $this->readMessages( $readerFactory, $remoteFiles ); + + if ( !isset( $remoteMessages['en'] ) ) { + // Could not find remote messages + continue; + } + + // If remote translation in English is not present or differs, we do not want + // translations for other languages for those messages, as they are either not + // used in this version of code or can be incompatible. + $forbiddenKeys = $this->findChangedTranslations( + $originMessages['en'], + $remoteMessages['en'] + ); + + // We never accept updates for English strings + unset( $originMessages['en'], $remoteMessages['en'] ); + + // message: string in all languages; translation: string in one language. + foreach ( $remoteMessages as $language => $remoteTranslations ) { + // Check for completely new languages + $originTranslations = array(); + if ( isset( $originMessages[$language] ) ) { + $originTranslations = $originMessages[$language]; + } + + $updatedTranslations = $this->findChangedTranslations( + $originTranslations, + $remoteTranslations, + $forbiddenKeys + ); + + // Avoid empty arrays + if ( $updatedTranslations === array() ) { + continue; + } + + if ( !isset( $updatedMessages[$language] ) ) { + $updatedMessages[$language] = array(); + } + + // In case of conflicts, which should not exist, this prefers the + // first translation seen. + $updatedMessages[$language] += $updatedTranslations; + } + } + + return $updatedMessages; + } +} diff --git a/extensions/LocalisationUpdate/fetcher/Fetcher.php b/extensions/LocalisationUpdate/fetcher/Fetcher.php new file mode 100644 index 00000000..9c06c105 --- /dev/null +++ b/extensions/LocalisationUpdate/fetcher/Fetcher.php @@ -0,0 +1,26 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Interface for classes which fetch files over different protocols and ways. + */ +interface LU_Fetcher { + /** + * Fetches a single resource. + * + * @return bool|string False on failure. + */ + public function fetchFile( $url ); + + /** + * Fetch a list of resources. This has the benefit of being able to pick up + * new languages as they appear if languages are stored in separate files. + * + * @return array + */ + public function fetchDirectory( $pattern ); +} diff --git a/extensions/LocalisationUpdate/fetcher/FetcherFactory.php b/extensions/LocalisationUpdate/fetcher/FetcherFactory.php new file mode 100644 index 00000000..4e26dc15 --- /dev/null +++ b/extensions/LocalisationUpdate/fetcher/FetcherFactory.php @@ -0,0 +1,24 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Constructs fetchers based on the repository urls. + */ +class LU_FetcherFactory { + public function getFetcher( $path ) { + + if ( strpos( $path, 'https://raw.github.com/' ) === 0 ) { + return new LU_GitHubFetcher(); + } elseif ( strpos( $path, 'http://' ) === 0 ) { + return new LU_HttpFetcher(); + } elseif ( strpos( $path, 'https://' ) === 0 ) { + return new LU_HttpFetcher(); + } else { + return new LU_FileSystemFetcher(); + } + } +} diff --git a/extensions/LocalisationUpdate/fetcher/FileSystemFetcher.php b/extensions/LocalisationUpdate/fetcher/FileSystemFetcher.php new file mode 100644 index 00000000..a463d6cd --- /dev/null +++ b/extensions/LocalisationUpdate/fetcher/FileSystemFetcher.php @@ -0,0 +1,35 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Accesses file system directly. + */ +class LU_FileSystemFetcher implements LU_Fetcher { + public function fetchFile( $url ) { + // Remove the protocol prefix + $url = preg_replace( '~^file://~', '', $url ); + + if ( !is_readable( $url ) ) { + return false; + } + + return file_get_contents( $url ); + } + + public function fetchDirectory( $pattern ) { + // Remove the protocol prefix + $pattern = preg_replace( '~^file://~', '', $pattern ); + + $data = array(); + foreach ( glob( $pattern ) as $file ) { + if ( is_readable( $file ) ) { + $data["file://$file"] = file_get_contents( $file ); + } + } + return $data; + } +} diff --git a/extensions/LocalisationUpdate/fetcher/GitHubFetcher.php b/extensions/LocalisationUpdate/fetcher/GitHubFetcher.php new file mode 100644 index 00000000..00c9b6ca --- /dev/null +++ b/extensions/LocalisationUpdate/fetcher/GitHubFetcher.php @@ -0,0 +1,38 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * This class uses GitHub api to obtain a list of files present in a directory + * to avoid fetching files that don't exist. + * + * @todo Could use file hashes to 1) avoid fetching files with same hash as + * the source. 2) avoid fetching files which haven't changed since last check + * if we store them. + */ +class LU_GitHubFetcher extends LU_HttpFetcher { + + public function fetchDirectory( $pattern ) { + $p = '~^https://raw.github\.com/(?P<org>[^/]+)/(?P<repo>[^/]+)/(?P<branch>[^/]+)/(?P<path>.+)/.+$~'; + preg_match( $p, $pattern, $m ); + + $json = Http::get( "https://api.github.com/repos/{$m['org']}/{$m['repo']}/contents/{$m['path']}" ); + if ( !$json ) { + throw new MWException( "Unable to get directory listing for {$m['org']}/{$m['repo']}" ); + } + + $files = array(); + $json = FormatJson::decode( $json, true ); + foreach ( $json as $fileinfo ) { + $fileurl = dirname( $pattern ) . '/' . $fileinfo['name']; + $file = $this->fetchFile( $fileurl ); + if ( $file ) { + $files[$fileurl] = $file; + } + } + return $files; + } +} diff --git a/extensions/LocalisationUpdate/fetcher/HttpFetcher.php b/extensions/LocalisationUpdate/fetcher/HttpFetcher.php new file mode 100644 index 00000000..9dfed8db --- /dev/null +++ b/extensions/LocalisationUpdate/fetcher/HttpFetcher.php @@ -0,0 +1,40 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Fetches files over HTTP(s). + */ +class LU_HttpFetcher implements LU_Fetcher { + public function fetchFile( $url ) { + return Http::get( $url ); + } + + /** + * This is horribly inefficient. Subclasses have more efficient + * implementation of this. + */ + public function fetchDirectory( $pattern ) { + $files = array(); + + $languages = Language::fetchLanguageNames( null, 'mwfile' ); + + foreach( array_keys( $languages ) as $code ) { + // Hack for core + if ( strpos( $pattern, 'Messages*.php' ) !== false ) { + $code = ucfirst( strtr( $code, '-', '_' ) ); + } + + $url = str_replace( '*', $code, $pattern ); + $file = $this->fetchFile( $url ); + if ( $file ) { + $files[$url] = $file; + } + } + + return $files; + } +} diff --git a/extensions/LocalisationUpdate/finder/Finder.php b/extensions/LocalisationUpdate/finder/Finder.php new file mode 100644 index 00000000..5c598730 --- /dev/null +++ b/extensions/LocalisationUpdate/finder/Finder.php @@ -0,0 +1,78 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Interface for classes which provide list of components, which should be + * included for l10n updates. + */ +class LU_Finder { + /** + * @param array $php See $wgExtensionMessagesFiles + * @param array $json See $wgMessagesDirs + * @param string $core Absolute path to MediaWiki core + */ + public function __construct( $php, $json, $core ) { + $this->php = $php; + $this->json = $json; + $this->core = $core; + } + + /** + * @return array + */ + public function getComponents() { + $components = array(); + + // For older versions of Mediawiki, pull json updates even though its still using php + if ( !isset( $this->json['core'] ) ) { + $components['core'] = array( + 'repo' => 'mediawiki', + 'orig' => "file://{$this->core}/languages/messages/Messages*.php", + 'path' => 'languages/messages/i18n/*.json', + ); + } + + foreach ( $this->json as $key => $value ) { + // Json should take priority if both exist + unset( $this->php[$key] ); + + foreach ( (array)$value as $subkey => $subvalue ) { + // This ignores magic, alias etc. non message files + $matches = array(); + $ok = preg_match( '~/extensions/(?P<name>[^/]+)/(?P<path>.*)$~', $subvalue, $matches ); + if ( !$ok ) { + continue; + } + + $components["$key-$subkey"] = array( + 'repo' => 'extension', + 'name' => $matches['name'], + 'orig' => "file://$subvalue/*.json", + 'path' => "{$matches['path']}/*.json", + ); + } + } + + foreach ( $this->php as $key => $value ) { + // This currently skips core i18n files like resources/oojs-ui/i18n + $matches = array(); + $ok = preg_match( '~/extensions/(?P<name>[^/]+)/(?P<path>.*\.i18n\.php)$~', $value, $matches ); + if ( !$ok ) { + continue; + } + + $components[$key] = array( + 'repo' => 'extension', + 'name' => $matches['name'], + 'orig' => "file://$value", + 'path' => $matches['path'], + ); + } + + return $components; + } +} diff --git a/extensions/LocalisationUpdate/i18n/af.json b/extensions/LocalisationUpdate/i18n/af.json new file mode 100644 index 00000000..3739dc26 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/af.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Naudefj" + ] + }, + "localisationupdate-desc": "Hou die gelokaliseerde boodskappe so op datum as moontlik" +} diff --git a/extensions/LocalisationUpdate/i18n/ar.json b/extensions/LocalisationUpdate/i18n/ar.json new file mode 100644 index 00000000..101e54ee --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ar.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Meno25" + ] + }, + "localisationupdate-desc": "يبقي الرسائل المترجمة محدثة كأفضل ما يكون" +} diff --git a/extensions/LocalisationUpdate/i18n/ast.json b/extensions/LocalisationUpdate/i18n/ast.json new file mode 100644 index 00000000..13fcd456 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ast.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Xuacu" + ] + }, + "localisationupdate-desc": "Caltién los mensaxes llocalizaos tan anovaos como se pueda" +} diff --git a/extensions/LocalisationUpdate/i18n/ba.json b/extensions/LocalisationUpdate/i18n/ba.json new file mode 100644 index 00000000..e96ee2b4 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ba.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Assele" + ] + }, + "localisationupdate-desc": "Локалләштерелгән хәбәрҙәрҙең мөмкин тиклем яңы булыуын тәьмин итә" +} diff --git a/extensions/LocalisationUpdate/i18n/bar.json b/extensions/LocalisationUpdate/i18n/bar.json new file mode 100644 index 00000000..0c4c56f7 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/bar.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Man77" + ] + }, + "localisationupdate-desc": "Lokalisiade Texte und Nåchrichtn so aktuell håidn wia's gråd gehd" +} diff --git a/extensions/LocalisationUpdate/i18n/be-tarask.json b/extensions/LocalisationUpdate/i18n/be-tarask.json new file mode 100644 index 00000000..01f487be --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/be-tarask.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "EugeneZelenko", + "Wizardist" + ] + }, + "localisationupdate-desc": "Сочыць за актуальнасьцю лякалізаваных паведамленьняў, наколькі гэта магчыма" +} diff --git a/extensions/LocalisationUpdate/i18n/bg.json b/extensions/LocalisationUpdate/i18n/bg.json new file mode 100644 index 00000000..59a6edbd --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/bg.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "DCLXVI" + ] + }, + "localisationupdate-desc": "Поддържа локализираните съобщения възможно най-актуални" +} diff --git a/extensions/LocalisationUpdate/i18n/bn.json b/extensions/LocalisationUpdate/i18n/bn.json new file mode 100644 index 00000000..6936be62 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/bn.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Bellayet" + ] + }, + "localisationupdate-desc": "স্থানীয়করণকৃত বার্তাসমূহ যথাসম্ভব হালনাগাদ রাখে" +} diff --git a/extensions/LocalisationUpdate/i18n/br.json b/extensions/LocalisationUpdate/i18n/br.json new file mode 100644 index 00000000..89db4778 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/br.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Fulup" + ] + }, + "localisationupdate-desc": "Derc'hel da hizivaat ar c'hemennoù troet ken fonnus ha ma'z eus tu" +} diff --git a/extensions/LocalisationUpdate/i18n/bs.json b/extensions/LocalisationUpdate/i18n/bs.json new file mode 100644 index 00000000..339dae1d --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/bs.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "CERminator" + ] + }, + "localisationupdate-desc": "Zadržavanje lokaliziranih poruka ažurnim koliko je god moguće" +} diff --git a/extensions/LocalisationUpdate/i18n/ca.json b/extensions/LocalisationUpdate/i18n/ca.json new file mode 100644 index 00000000..d37e93a9 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ca.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Paucabot" + ] + }, + "localisationupdate-desc": "Manté els missatges localitzats tan actualitzats com sigui possible" +} diff --git a/extensions/LocalisationUpdate/i18n/ce.json b/extensions/LocalisationUpdate/i18n/ce.json new file mode 100644 index 00000000..579d7dbb --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ce.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Умар" + ] + }, + "localisationupdate-desc": "Таро ма хуьйла хаамашан болх бан гӀо до" +} diff --git a/extensions/LocalisationUpdate/i18n/cs.json b/extensions/LocalisationUpdate/i18n/cs.json new file mode 100644 index 00000000..d72e9110 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/cs.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Mormegil" + ] + }, + "localisationupdate-desc": "Udržuje lokalizovaná hlášení co možná nejaktuálnější" +} diff --git a/extensions/LocalisationUpdate/i18n/cy.json b/extensions/LocalisationUpdate/i18n/cy.json new file mode 100644 index 00000000..9ab89a53 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/cy.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Lloffiwr" + ] + }, + "localisationupdate-desc": "Yn diweddaru'r cyfieithiadau o negeseuon mor aml â phosib" +} diff --git a/extensions/LocalisationUpdate/i18n/da.json b/extensions/LocalisationUpdate/i18n/da.json new file mode 100644 index 00000000..645d0842 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/da.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Peter Alberti" + ] + }, + "localisationupdate-desc": "Holder de lokaliserede meddelelser så opdaterede som muligt" +} diff --git a/extensions/LocalisationUpdate/i18n/de.json b/extensions/LocalisationUpdate/i18n/de.json new file mode 100644 index 00000000..3f1d3fbc --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/de.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Kghbln", + "Purodha" + ] + }, + "localisationupdate-desc": "Ermöglicht es lokalisierte Texte und Nachrichten so aktuell wie möglich zu halten" +} diff --git a/extensions/LocalisationUpdate/i18n/dsb.json b/extensions/LocalisationUpdate/i18n/dsb.json new file mode 100644 index 00000000..11cbf5d9 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/dsb.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Michawiki" + ] + }, + "localisationupdate-desc": "Źaržy lokalizěrowane powěźeńki tak aktualne ako móžno" +} diff --git a/extensions/LocalisationUpdate/i18n/el.json b/extensions/LocalisationUpdate/i18n/el.json new file mode 100644 index 00000000..7b4db44c --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/el.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Omnipaedista" + ] + }, + "localisationupdate-desc": "Διατηρεί τις μεταφράσεις μηνυμάτων όσο πιο ενημερωμένες γίνεται" +} diff --git a/extensions/LocalisationUpdate/i18n/en.json b/extensions/LocalisationUpdate/i18n/en.json new file mode 100644 index 00000000..813a9e74 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/en.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Tom Maaswinkel" + ] + }, + "localisationupdate-desc": "Keeps the localised messages as up to date as possible" +}
\ No newline at end of file diff --git a/extensions/LocalisationUpdate/i18n/eo.json b/extensions/LocalisationUpdate/i18n/eo.json new file mode 100644 index 00000000..c1251faa --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/eo.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Yekrats" + ] + }, + "localisationupdate-desc": "Ĝisdatigas la asimilitajn mesaĝojn tiom eble" +} diff --git a/extensions/LocalisationUpdate/i18n/es.json b/extensions/LocalisationUpdate/i18n/es.json new file mode 100644 index 00000000..2526e852 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/es.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Crazymadlover" + ] + }, + "localisationupdate-desc": "Mantiene los mensajes localizados tan actualizados como sea posible" +} diff --git a/extensions/LocalisationUpdate/i18n/et.json b/extensions/LocalisationUpdate/i18n/et.json new file mode 100644 index 00000000..9cf22620 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/et.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Pikne" + ] + }, + "localisationupdate-desc": "Hoiab lokaliseeritud sõnumid nii ajakohased kui võimalik." +} diff --git a/extensions/LocalisationUpdate/i18n/eu.json b/extensions/LocalisationUpdate/i18n/eu.json new file mode 100644 index 00000000..3f55906c --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/eu.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Kobazulo" + ] + }, + "localisationupdate-desc": "Itzulitako mezuak ahalik eta eguneratuen mantentzen ditu" +} diff --git a/extensions/LocalisationUpdate/i18n/fa.json b/extensions/LocalisationUpdate/i18n/fa.json new file mode 100644 index 00000000..ae1a2a27 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/fa.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "ZxxZxxZ" + ] + }, + "localisationupdate-desc": "پیغامهای محلیسازیشده را تا جای ممکن بهروز نگه میدارد" +} diff --git a/extensions/LocalisationUpdate/i18n/fi.json b/extensions/LocalisationUpdate/i18n/fi.json new file mode 100644 index 00000000..253c2de7 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/fi.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Crt", + "Nike" + ] + }, + "localisationupdate-desc": "Pitää ohjelmiston käännöksen ajantasaisena." +} diff --git a/extensions/LocalisationUpdate/i18n/fr.json b/extensions/LocalisationUpdate/i18n/fr.json new file mode 100644 index 00000000..b62e2b6b --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/fr.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Crochet.david" + ] + }, + "localisationupdate-desc": "Maintenir la traduction des messages à jour autant que possible" +} diff --git a/extensions/LocalisationUpdate/i18n/gl.json b/extensions/LocalisationUpdate/i18n/gl.json new file mode 100644 index 00000000..f80215b3 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/gl.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Toliño" + ] + }, + "localisationupdate-desc": "Mantén as mensaxes localizadas tan actualizadas como é posible" +} diff --git a/extensions/LocalisationUpdate/i18n/gsw.json b/extensions/LocalisationUpdate/i18n/gsw.json new file mode 100644 index 00000000..9ce091c0 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/gsw.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Als-Holder" + ] + }, + "localisationupdate-desc": "Halt d Syschtemnochrichte so aktuälle wie megli" +} diff --git a/extensions/LocalisationUpdate/i18n/he.json b/extensions/LocalisationUpdate/i18n/he.json new file mode 100644 index 00000000..9a1b8c64 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/he.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "YaronSh" + ] + }, + "localisationupdate-desc": "שמירת ההודעות המתורגמות מעודכנות ככל הניתן" +} diff --git a/extensions/LocalisationUpdate/i18n/hil.json b/extensions/LocalisationUpdate/i18n/hil.json new file mode 100644 index 00000000..cff1ac40 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/hil.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Tagimata" + ] + }, + "localisationupdate-desc": "Gatugo sang mga mensahe nga lokal para mapahibalo sang madali" +} diff --git a/extensions/LocalisationUpdate/i18n/hr.json b/extensions/LocalisationUpdate/i18n/hr.json new file mode 100644 index 00000000..8d196c34 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/hr.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "SpeedyGonsales" + ] + }, + "localisationupdate-desc": "Dogradnja za osvježavanje lokalizacije poruka MediaWikija" +} diff --git a/extensions/LocalisationUpdate/i18n/hsb.json b/extensions/LocalisationUpdate/i18n/hsb.json new file mode 100644 index 00000000..04adee04 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/hsb.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Michawiki" + ] + }, + "localisationupdate-desc": "Dźerži lokalizowane zdźělenki tak aktualne kaž móžno" +} diff --git a/extensions/LocalisationUpdate/i18n/hu.json b/extensions/LocalisationUpdate/i18n/hu.json new file mode 100644 index 00000000..f8bd039b --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/hu.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Glanthor Reviol" + ] + }, + "localisationupdate-desc": "Frissíti a lefordított üzeneteket" +} diff --git a/extensions/LocalisationUpdate/i18n/ia.json b/extensions/LocalisationUpdate/i18n/ia.json new file mode 100644 index 00000000..7ab58ab9 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ia.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "McDutchie" + ] + }, + "localisationupdate-desc": "Mantene le messages localisate tanto actual como possibile" +} diff --git a/extensions/LocalisationUpdate/i18n/id.json b/extensions/LocalisationUpdate/i18n/id.json new file mode 100644 index 00000000..6f46173a --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/id.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Bennylin" + ] + }, + "localisationupdate-desc": "Mengusahakan agar pesan-pesan yang telah diterjemahkan tetap semutakhir mungkin" +} diff --git a/extensions/LocalisationUpdate/i18n/ilo.json b/extensions/LocalisationUpdate/i18n/ilo.json new file mode 100644 index 00000000..56428785 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ilo.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Lam-ang" + ] + }, + "localisationupdate-desc": "Taginayonenna a mapabaro dagiti naipatarus a mensahe" +} diff --git a/extensions/LocalisationUpdate/i18n/it.json b/extensions/LocalisationUpdate/i18n/it.json new file mode 100644 index 00000000..b1ee5aa3 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/it.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Darth Kule" + ] + }, + "localisationupdate-desc": "Mantiene i messaggi localizzati quanto più aggiornati è possibile" +} diff --git a/extensions/LocalisationUpdate/i18n/ja.json b/extensions/LocalisationUpdate/i18n/ja.json new file mode 100644 index 00000000..8eba705e --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ja.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Fryed-peach", + "Shirayuki" + ] + }, + "localisationupdate-desc": "メッセージの翻訳をできるだけ最新に保つ" +} diff --git a/extensions/LocalisationUpdate/i18n/km.json b/extensions/LocalisationUpdate/i18n/km.json new file mode 100644 index 00000000..c3f78d86 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/km.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "វ័ណថារិទ្ធ" + ] + }, + "localisationupdate-desc": "រក្សាសារដែលបានប្រែសម្រួលទាំងឡាយ អោយនៅទាន់សម័យតាមដែលអាចធ្វើទៅបាន" +} diff --git a/extensions/LocalisationUpdate/i18n/ko.json b/extensions/LocalisationUpdate/i18n/ko.json new file mode 100644 index 00000000..0f906a59 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ko.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Kwj2772", + "아라" + ] + }, + "localisationupdate-desc": "번역된 메시지를 가능한 한 최신으로 유지합니다" +} diff --git a/extensions/LocalisationUpdate/i18n/ksh.json b/extensions/LocalisationUpdate/i18n/ksh.json new file mode 100644 index 00000000..ecb69cd7 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ksh.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Purodha" + ] + }, + "localisationupdate-desc": "Texte un Nohreeschte vum Wiki esu joot wi müjjelich om neueste Shtand halde" +} diff --git a/extensions/LocalisationUpdate/i18n/lb.json b/extensions/LocalisationUpdate/i18n/lb.json new file mode 100644 index 00000000..f2eed733 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/lb.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Robby", + "Soued031" + ] + }, + "localisationupdate-desc": "hält déi lokaliséiert Messagen sou aktuell wéi méiglech." +} diff --git a/extensions/LocalisationUpdate/i18n/mk.json b/extensions/LocalisationUpdate/i18n/mk.json new file mode 100644 index 00000000..10eb0966 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/mk.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Bjankuloski06" + ] + }, + "localisationupdate-desc": "Ги одржува локализираните пораки колку што е можно пообновени и повеќе во тек со настаните" +} diff --git a/extensions/LocalisationUpdate/i18n/ml.json b/extensions/LocalisationUpdate/i18n/ml.json new file mode 100644 index 00000000..5e7dfd76 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ml.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Praveenp" + ] + }, + "localisationupdate-desc": "പ്രാദേശികഭാഷയിലാക്കിയ സന്ദേശങ്ങൾ കഴിയുന്നത്ര വേഗം ചേർക്കാൻ ഉപയോഗിക്കുന്നു" +} diff --git a/extensions/LocalisationUpdate/i18n/ms.json b/extensions/LocalisationUpdate/i18n/ms.json new file mode 100644 index 00000000..5081ed8e --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ms.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Anakmalaysia" + ] + }, + "localisationupdate-desc": "Memastikan kekemaskinian mesej-mesej yang disetempatkan" +} diff --git a/extensions/LocalisationUpdate/i18n/nb.json b/extensions/LocalisationUpdate/i18n/nb.json new file mode 100644 index 00000000..c7978a78 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/nb.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Nghtwlkr" + ] + }, + "localisationupdate-desc": "Holder de lokaliserte meldingene så oppdaterte som mulig" +} diff --git a/extensions/LocalisationUpdate/i18n/nl.json b/extensions/LocalisationUpdate/i18n/nl.json new file mode 100644 index 00000000..a05698e6 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/nl.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Siebrand" + ] + }, + "localisationupdate-desc": "Houdt de gelokaliseerde berichten zo actueel mogelijk" +} diff --git a/extensions/LocalisationUpdate/i18n/nn.json b/extensions/LocalisationUpdate/i18n/nn.json new file mode 100644 index 00000000..b9730f27 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/nn.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Gunnernett" + ] + }, + "localisationupdate-desc": "Held dei lokaliserte meldingane så oppdaterte som mogleg" +} diff --git a/extensions/LocalisationUpdate/i18n/oc.json b/extensions/LocalisationUpdate/i18n/oc.json new file mode 100644 index 00000000..541dd0b9 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/oc.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Cedric31" + ] + }, + "localisationupdate-desc": "Manténer la traduccion dels messatges a jorn autant que possible" +} diff --git a/extensions/LocalisationUpdate/i18n/pl.json b/extensions/LocalisationUpdate/i18n/pl.json new file mode 100644 index 00000000..2ed878ea --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/pl.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Sp5uhe" + ] + }, + "localisationupdate-desc": "Uaktualnia lokalne komunikaty w miarę możliwości na bieżąco" +} diff --git a/extensions/LocalisationUpdate/i18n/pms.json b/extensions/LocalisationUpdate/i18n/pms.json new file mode 100644 index 00000000..bebbbd8a --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/pms.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Dragonòt" + ] + }, + "localisationupdate-desc": "A manten i messagi localisà ël pì agiornà possìbil" +} diff --git a/extensions/LocalisationUpdate/i18n/pt-br.json b/extensions/LocalisationUpdate/i18n/pt-br.json new file mode 100644 index 00000000..2684cfa9 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/pt-br.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Eduardo.mps" + ] + }, + "localisationupdate-desc": "Mantém as mensagens localizadas tão atualizadas quanto possível" +} diff --git a/extensions/LocalisationUpdate/i18n/pt.json b/extensions/LocalisationUpdate/i18n/pt.json new file mode 100644 index 00000000..07fa8c3d --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/pt.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Hamilton Abreu", + "Luckas", + "Malafaya" + ] + }, + "localisationupdate-desc": "Mantém as mensagens localizadas tão atualizadas quanto possível" +} diff --git a/extensions/LocalisationUpdate/i18n/qqq.json b/extensions/LocalisationUpdate/i18n/qqq.json new file mode 100644 index 00000000..c6978bbb --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/qqq.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Fryed-peach", + "Purodha", + "Shirayuki" + ] + }, + "localisationupdate-desc": "{{desc|name=Localisation Update|url=http://www.mediawiki.org/wiki/Extension:LocalisationUpdate}}" +} diff --git a/extensions/LocalisationUpdate/i18n/ro.json b/extensions/LocalisationUpdate/i18n/ro.json new file mode 100644 index 00000000..b609969c --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ro.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "KlaudiuMihaila" + ] + }, + "localisationupdate-desc": "Menține mesajele localizate cât mai actualizate" +} diff --git a/extensions/LocalisationUpdate/i18n/roa-tara.json b/extensions/LocalisationUpdate/i18n/roa-tara.json new file mode 100644 index 00000000..47d5c72c --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/roa-tara.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Joetaras" + ] + }, + "localisationupdate-desc": "Mandine le messagge localizzate 'u cchiù aggiornate possibbile" +} diff --git a/extensions/LocalisationUpdate/i18n/ru.json b/extensions/LocalisationUpdate/i18n/ru.json new file mode 100644 index 00000000..a0e4739e --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ru.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Александр Сигачёв" + ] + }, + "localisationupdate-desc": "Поддерживает актуальность локализованных сообщений, насколько это возможно" +} diff --git a/extensions/LocalisationUpdate/i18n/sk.json b/extensions/LocalisationUpdate/i18n/sk.json new file mode 100644 index 00000000..396998ad --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/sk.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Helix84" + ] + }, + "localisationupdate-desc": "Udržiava lokalizované správy čo najaktuálnejšie" +} diff --git a/extensions/LocalisationUpdate/i18n/sr-ec.json b/extensions/LocalisationUpdate/i18n/sr-ec.json new file mode 100644 index 00000000..dfd900a4 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/sr-ec.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Михајло Анђелковић" + ] + }, + "localisationupdate-desc": "Ажурира локализоване поруке колико је то могуће" +} diff --git a/extensions/LocalisationUpdate/i18n/sr-el.json b/extensions/LocalisationUpdate/i18n/sr-el.json new file mode 100644 index 00000000..f303273b --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/sr-el.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Liangent" + ] + }, + "localisationupdate-desc": "Ažurira lokalizovane poruke koliko je to moguće" +} diff --git a/extensions/LocalisationUpdate/i18n/su.json b/extensions/LocalisationUpdate/i18n/su.json new file mode 100644 index 00000000..2c9d2f13 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/su.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Kandar" + ] + }, + "localisationupdate-desc": "Ngajaga sangkan talatah-talatah nu geus dialihbasakeun salawasnya mutahir" +} diff --git a/extensions/LocalisationUpdate/i18n/sv.json b/extensions/LocalisationUpdate/i18n/sv.json new file mode 100644 index 00000000..a68f4521 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/sv.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Boivie" + ] + }, + "localisationupdate-desc": "Håller de lokaliserade meddelandena så uppdaterade som möjligt" +} diff --git a/extensions/LocalisationUpdate/i18n/ta.json b/extensions/LocalisationUpdate/i18n/ta.json new file mode 100644 index 00000000..153519e6 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/ta.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "செல்வா" + ] + }, + "localisationupdate-desc": "உட்சூழலுக்கான செய்திகளை கூடியமட்டிலும் இன்றையநிலையில் வைக்கப்பட்டுள்ளன" +} diff --git a/extensions/LocalisationUpdate/i18n/te.json b/extensions/LocalisationUpdate/i18n/te.json new file mode 100644 index 00000000..4bc8cd36 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/te.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Veeven" + ] + }, + "localisationupdate-desc": "స్ధానికీకరించిన సందేశాలను సాధ్యమైనంత తాజాగా ఉంచుతుంది" +} diff --git a/extensions/LocalisationUpdate/i18n/tl.json b/extensions/LocalisationUpdate/i18n/tl.json new file mode 100644 index 00000000..e493bc7b --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/tl.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "AnakngAraw" + ] + }, + "localisationupdate-desc": "Pinananatili ang mga mensaheng lokalisado bilang pinaka nasasapanahon" +} diff --git a/extensions/LocalisationUpdate/i18n/tr.json b/extensions/LocalisationUpdate/i18n/tr.json new file mode 100644 index 00000000..63191719 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/tr.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Joseph" + ] + }, + "localisationupdate-desc": "Yerelleştirilen mesajları mümkün olabildiğince güncel tutar" +} diff --git a/extensions/LocalisationUpdate/i18n/uk.json b/extensions/LocalisationUpdate/i18n/uk.json new file mode 100644 index 00000000..e2c2446e --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/uk.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Prima klasy4na" + ] + }, + "localisationupdate-desc": "Забезпечує оновлення локалізованих повідомлень у міру можливості" +} diff --git a/extensions/LocalisationUpdate/i18n/vep.json b/extensions/LocalisationUpdate/i18n/vep.json new file mode 100644 index 00000000..8f3916d5 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/vep.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Игорь Бродский" + ] + }, + "localisationupdate-desc": "Pidab lokaliziruidud tedotused veresin, ku voib" +} diff --git a/extensions/LocalisationUpdate/i18n/vi.json b/extensions/LocalisationUpdate/i18n/vi.json new file mode 100644 index 00000000..9c0e7091 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/vi.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Vinhtantran" + ] + }, + "localisationupdate-desc": "Giữ các thông điệp bản địa hóa được cập nhật nhất có thể" +} diff --git a/extensions/LocalisationUpdate/i18n/wa.json b/extensions/LocalisationUpdate/i18n/wa.json new file mode 100644 index 00000000..cba6cba1 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/wa.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Srtxg" + ] + }, + "localisationupdate-desc": "Po wårder les ratournaedjes di l' eterface li pus a djoû possibe" +} diff --git a/extensions/LocalisationUpdate/i18n/yi.json b/extensions/LocalisationUpdate/i18n/yi.json new file mode 100644 index 00000000..eab1afc4 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/yi.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "פוילישער" + ] + }, + "localisationupdate-desc": "האלטן די לאקאליזירטע מעלדונגען אקטועל ווי נאר מעגלעך" +} diff --git a/extensions/LocalisationUpdate/i18n/yue.json b/extensions/LocalisationUpdate/i18n/yue.json new file mode 100644 index 00000000..1376dc0e --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/yue.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Tom Maaswinkel" + ] + }, + "localisationupdate-desc": "將本地化嘅信息保持最新" +} diff --git a/extensions/LocalisationUpdate/i18n/zh-hans.json b/extensions/LocalisationUpdate/i18n/zh-hans.json new file mode 100644 index 00000000..8141f127 --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/zh-hans.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Tom Maaswinkel" + ] + }, + "localisationupdate-desc": "将本地化的信息保持最新" +} diff --git a/extensions/LocalisationUpdate/i18n/zh-hant.json b/extensions/LocalisationUpdate/i18n/zh-hant.json new file mode 100644 index 00000000..97e51cfe --- /dev/null +++ b/extensions/LocalisationUpdate/i18n/zh-hant.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Mark85296341", + "Tom Maaswinkel" + ] + }, + "localisationupdate-desc": "將本地化的資訊盡可能保持最新" +} diff --git a/extensions/LocalisationUpdate/reader/JSONReader.php b/extensions/LocalisationUpdate/reader/JSONReader.php new file mode 100644 index 00000000..636168c8 --- /dev/null +++ b/extensions/LocalisationUpdate/reader/JSONReader.php @@ -0,0 +1,30 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Reads MediaWiki JSON i18n files. + */ +class LU_JSONReader implements LU_Reader { + /// @var string Language tag + protected $code; + + public function __construct( $code = null ) { + $this->code = $code; + } + + public function parse( $contents ) { + $messages = FormatJson::decode( $contents, true ); + unset( $messages['@metadata'] ); + + if ( $this->code ) { + return array( $this->code => $messages ); + } + + // Assuming that the array is keyed by language codes + return $messages; + } +} diff --git a/extensions/LocalisationUpdate/reader/PHPReader.php b/extensions/LocalisationUpdate/reader/PHPReader.php new file mode 100644 index 00000000..986d7b52 --- /dev/null +++ b/extensions/LocalisationUpdate/reader/PHPReader.php @@ -0,0 +1,54 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Reads MediaWiki PHP i18n files. + */ +class LU_PHPReader implements LU_Reader { + /// @var string Language tag + protected $code; + + public function __construct( $code = null ) { + $this->code = $code; + } + + public function parse( $contents ) { + if ( strpos( $contents, '$messages' ) === false ) { + // This happens for some core languages that only have a fallback. + return array(); + } + + $php = $this->cleanupFile( $contents ); + $reader = new QuickArrayReader( "<?php $php" ); + $messages = $reader->getVar( 'messages' ); + + if ( $this->code ) { + return array( $this->code => $messages ); + } + + // Assuming that the array is keyed by language codes + return $messages; + } + + /** + * Removes all unneeded content from a file and returns it. + * + * @param string $contents String + * @return string PHP code without PHP tags + */ + protected function cleanupFile( $contents ) { + // We hate the windows vs linux linebreaks. + $contents = preg_replace( '/\r\n?/', "\n", $contents ); + + // We only want message arrays. + $results = array(); + preg_match_all( '/\$messages(?:.*\s)*?\);/', $contents, $results ); + + // But we want them all in one string. + return implode( "\n\n", $results[0] ); + } +} diff --git a/extensions/LocalisationUpdate/reader/Reader.php b/extensions/LocalisationUpdate/reader/Reader.php new file mode 100644 index 00000000..f55a9372 --- /dev/null +++ b/extensions/LocalisationUpdate/reader/Reader.php @@ -0,0 +1,19 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Interface for file readers. + */ +interface LU_Reader { + /** + * Returns a list of messages indexed by language code. Example + * array( 'en' => array( 'key' => 'value' ) ); + * @param string $contents File contents as a string. + * @return array + */ + public function parse( $contents ); +} diff --git a/extensions/LocalisationUpdate/reader/ReaderFactory.php b/extensions/LocalisationUpdate/reader/ReaderFactory.php new file mode 100644 index 00000000..530ab3f9 --- /dev/null +++ b/extensions/LocalisationUpdate/reader/ReaderFactory.php @@ -0,0 +1,36 @@ +<?php +/** + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Constructs readers for files based on the names. + */ +class LU_ReaderFactory { + /** + * Constructs a suitable reader for a given path. + * @param string $filename Usually a relative path to the file name. + * @return LU_Reader + * @throw MWException + */ + public function getReader( $filename ) { + if ( preg_match( '/i18n\.php$/', $filename ) ) { + return new LU_PHPReader(); + } + + // Ugly hack for core i18n files + if ( preg_match( '/Messages(.*)\.php$/', $filename ) ) { + $code = Language::getCodeFromFileName( basename( $filename ), 'Messages' ); + return new LU_PHPReader( $code ); + } + + if ( preg_match( '/\.json/', $filename ) ) { + $code = basename( $filename, '.json' ); + return new LU_JSONReader( $code ); + } + + throw new MWException( "Unknown file format: " . $filename ); + } +} diff --git a/extensions/LocalisationUpdate/update.php b/extensions/LocalisationUpdate/update.php index 750fc4f2..1f66e736 100644 --- a/extensions/LocalisationUpdate/update.php +++ b/extensions/LocalisationUpdate/update.php @@ -3,36 +3,73 @@ $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' ? getenv( 'MW_INSTALL_PATH' ) : realpath( dirname( __FILE__ ) . "/../../" ); +// Can use __DIR__ once we drop support for MW 1.19 -// 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 ); -} +require "$IP/maintenance/Maintenance.php"; + +class LU extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = 'Fetches translation updates to MediaWiki and extensions.'; + $this->addOption( + 'repoid', + 'Fetch translations from repositories identified by this', + false, /*required*/ + true /*has arg*/ + ); + } + + public function execute() { + // Prevent the script from timing out + set_time_limit( 0 ); + ini_set( "max_execution_time", 0 ); + ini_set( 'memory_limit', -1 ); + + global $wgExtensionMessagesFiles, $wgMessagesDirs, $IP; + global $wgLocalisationUpdateRepositories; + global $wgLocalisationUpdateRepository; + $dir = LocalisationUpdate::getDirectory(); + if ( !$dir ) { + $this->error( "No cache directory configured", true ); + return; + } -$starttime = microtime( true ); + $finder = new LU_Finder( $wgExtensionMessagesFiles, $wgMessagesDirs, $IP ); + $readerFactory = new LU_ReaderFactory(); + $fetcherFactory = new LU_FetcherFactory(); -// Prevent the script from timing out -set_time_limit( 0 ); -ini_set( "max_execution_time", 0 ); -ini_set( 'memory_limit', -1 ); + $repoid = $this->getOption( 'repoid', $wgLocalisationUpdateRepository ); + if ( !isset( $wgLocalisationUpdateRepositories[$repoid] ) ) { + $known = implode( ', ', array_keys( $wgLocalisationUpdateRepositories ) ); + $this->error( "Unknown repoid $repoid; known: $known", true ); + return; + } + $repos = $wgLocalisationUpdateRepositories[$repoid]; -LocalisationUpdate::updateMessages( $options ); + // Do it ;) + $updater = new LU_Updater(); + $updatedMessages = $updater->execute( + $finder, + $readerFactory, + $fetcherFactory, + $repos + ); + + // Store it ;) + $count = array_sum( array_map( 'count', $updatedMessages ) ); + if ( !$count ) { + $this->output( "Found no new translations\n" ); + return; + } + + foreach ( $updatedMessages as $language => $messages ) { + $filename = "$dir/" . LocalisationUpdate::getFilename( $language ); + file_put_contents( $filename, FormatJson::encode( $messages, true ) ); + } + $this->output( "Saved $count new translations\n" ); + } +} -$endtime = microtime( true ); -$totaltime = ( $endtime - $starttime ); -print "All done in " . $totaltime . " seconds\n"; +$maintClass = 'LU'; +require_once RUN_MAINTENANCE_IF_MAIN; |