summaryrefslogtreecommitdiff
path: root/extensions/LocalisationUpdate
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/LocalisationUpdate')
-rw-r--r--extensions/LocalisationUpdate/Autoload.php31
-rw-r--r--extensions/LocalisationUpdate/KNOWN_ISSUES.txt11
-rw-r--r--extensions/LocalisationUpdate/LocalisationUpdate.class.php605
-rw-r--r--extensions/LocalisationUpdate/LocalisationUpdate.i18n.php555
-rw-r--r--extensions/LocalisationUpdate/LocalisationUpdate.php53
-rw-r--r--extensions/LocalisationUpdate/QuickArrayReader.php203
-rw-r--r--extensions/LocalisationUpdate/README34
-rw-r--r--extensions/LocalisationUpdate/README_FIRST.txt8
-rw-r--r--extensions/LocalisationUpdate/Updater.php194
-rw-r--r--extensions/LocalisationUpdate/fetcher/Fetcher.php26
-rw-r--r--extensions/LocalisationUpdate/fetcher/FetcherFactory.php24
-rw-r--r--extensions/LocalisationUpdate/fetcher/FileSystemFetcher.php35
-rw-r--r--extensions/LocalisationUpdate/fetcher/GitHubFetcher.php38
-rw-r--r--extensions/LocalisationUpdate/fetcher/HttpFetcher.php40
-rw-r--r--extensions/LocalisationUpdate/finder/Finder.php78
-rw-r--r--extensions/LocalisationUpdate/i18n/af.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ar.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ast.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ba.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/bar.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/be-tarask.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/bg.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/bn.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/br.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/bs.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ca.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ce.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/cs.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/cy.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/da.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/de.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/dsb.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/el.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/en.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/eo.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/es.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/et.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/eu.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/fa.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/fi.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/fr.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/gl.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/gsw.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/he.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/hil.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/hr.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/hsb.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/hu.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ia.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/id.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ilo.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/it.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ja.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/km.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ko.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/ksh.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/lb.json9
-rw-r--r--extensions/LocalisationUpdate/i18n/mk.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ml.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ms.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/nb.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/nl.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/nn.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/oc.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/pl.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/pms.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/pt-br.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/pt.json10
-rw-r--r--extensions/LocalisationUpdate/i18n/qqq.json10
-rw-r--r--extensions/LocalisationUpdate/i18n/ro.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/roa-tara.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ru.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/sk.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/sr-ec.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/sr-el.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/su.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/sv.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/ta.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/te.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/tl.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/tr.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/uk.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/vep.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/vi.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/wa.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/yi.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/yue.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/zh-hans.json8
-rw-r--r--extensions/LocalisationUpdate/i18n/zh-hant.json9
-rw-r--r--extensions/LocalisationUpdate/reader/JSONReader.php30
-rw-r--r--extensions/LocalisationUpdate/reader/PHPReader.php54
-rw-r--r--extensions/LocalisationUpdate/reader/Reader.php19
-rw-r--r--extensions/LocalisationUpdate/reader/ReaderFactory.php36
-rw-r--r--extensions/LocalisationUpdate/update.php91
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;