summaryrefslogtreecommitdiff
path: root/extensions/LocalisationUpdate/Updater.php
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/LocalisationUpdate/Updater.php')
-rw-r--r--extensions/LocalisationUpdate/Updater.php194
1 files changed, 194 insertions, 0 deletions
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;
+ }
+}