diff options
Diffstat (limited to 'extensions/LocalisationUpdate/Updater.php')
-rw-r--r-- | extensions/LocalisationUpdate/Updater.php | 194 |
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; + } +} |