summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/translationaids
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/extensions/Translate/translationaids
first commit
Diffstat (limited to 'www/wiki/extensions/Translate/translationaids')
-rw-r--r--www/wiki/extensions/Translate/translationaids/CurrentTranslationAid.php37
-rw-r--r--www/wiki/extensions/Translate/translationaids/DocumentationAid.php37
-rw-r--r--www/wiki/extensions/Translate/translationaids/GettextDocumentationAid.php69
-rw-r--r--www/wiki/extensions/Translate/translationaids/InOtherLanguagesAid.php81
-rw-r--r--www/wiki/extensions/Translate/translationaids/InsertablesAid.php55
-rw-r--r--www/wiki/extensions/Translate/translationaids/MachineTranslationAid.php87
-rw-r--r--www/wiki/extensions/Translate/translationaids/MessageDefinitionAid.php27
-rw-r--r--www/wiki/extensions/Translate/translationaids/QueryAggregatorAwareTranslationAid.php83
-rw-r--r--www/wiki/extensions/Translate/translationaids/SupportAid.php66
-rw-r--r--www/wiki/extensions/Translate/translationaids/TTMServerAid.php102
-rw-r--r--www/wiki/extensions/Translate/translationaids/TranslationAid.php88
-rw-r--r--www/wiki/extensions/Translate/translationaids/TranslationAidDataProvider.php135
-rw-r--r--www/wiki/extensions/Translate/translationaids/UnsupportedTranslationAid.php21
-rw-r--r--www/wiki/extensions/Translate/translationaids/UpdatedDefinitionAid.php84
14 files changed, 972 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/translationaids/CurrentTranslationAid.php b/www/wiki/extensions/Translate/translationaids/CurrentTranslationAid.php
new file mode 100644
index 00000000..df1c38d2
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/CurrentTranslationAid.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives the current saved translation.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class CurrentTranslationAid extends TranslationAid {
+ public function getData() {
+ $translation = null;
+
+ $title = $this->handle->getTitle();
+ $translation = TranslateUtils::getMessageContent(
+ $this->handle->getKey(),
+ $this->handle->getCode(),
+ $title->getNamespace()
+ );
+
+ Hooks::run( 'TranslatePrefillTranslation', [ &$translation, $this->handle ] );
+ $fuzzy = MessageHandle::hasFuzzyString( $translation ) || $this->handle->isFuzzy();
+ $translation = str_replace( TRANSLATE_FUZZY, '', $translation );
+
+ return [
+ 'language' => $this->handle->getCode(),
+ 'fuzzy' => $fuzzy,
+ 'value' => $translation,
+ ];
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/DocumentationAid.php b/www/wiki/extensions/Translate/translationaids/DocumentationAid.php
new file mode 100644
index 00000000..f19c03ba
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/DocumentationAid.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2012-2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives the message documentation.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class DocumentationAid extends TranslationAid {
+ public function getData() {
+ global $wgTranslateDocumentationLanguageCode, $wgContLang;
+ if ( !$wgTranslateDocumentationLanguageCode ) {
+ throw new TranslationHelperException( 'Message documentation is disabled' );
+ }
+
+ $page = $this->handle->getKey();
+ $ns = $this->handle->getTitle()->getNamespace();
+
+ $info = TranslateUtils::getMessageContent( $page, $wgTranslateDocumentationLanguageCode, $ns );
+
+ return [
+ 'language' => $wgContLang->getCode(),
+ 'value' => $info,
+ 'html' => TranslateUtils::parseAsInterface(
+ $this->context->getOutput(), $info
+ ),
+ ];
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/GettextDocumentationAid.php b/www/wiki/extensions/Translate/translationaids/GettextDocumentationAid.php
new file mode 100644
index 00000000..b7cd68cf
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/GettextDocumentationAid.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives Gettext documentation.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class GettextDocumentationAid extends TranslationAid {
+ public function getData() {
+ // We need to get the primary group to get the correct file
+ // So $group can be different from $this->group
+ $group = $this->handle->getGroup();
+ if ( !$group instanceof FileBasedMessageGroup ) {
+ throw new TranslationHelperException( 'Not a Gettext group' );
+ }
+
+ $ffs = $group->getFFS();
+ if ( !$ffs instanceof GettextFFS ) {
+ throw new TranslationHelperException( 'Not a Gettext group' );
+ }
+
+ global $wgContLang;
+ $mykey = $wgContLang->lcfirst( $this->handle->getKey() );
+ $mykey = str_replace( ' ', '_', $mykey );
+ $data = $ffs->read( $group->getSourceLanguage() );
+ $help = $data['TEMPLATE'][$mykey]['comments'];
+
+ $conf = $group->getConfiguration();
+ if ( isset( $conf['BASIC']['codeBrowser'] ) ) {
+ $pattern = $conf['BASIC']['codeBrowser'];
+ $pattern = str_replace( '%FILE%', '\1', $pattern );
+ $pattern = str_replace( '%LINE%', '\2', $pattern );
+ $pattern = "[$pattern \\1:\\2]";
+ } else {
+ $pattern = "\\1:\\2";
+ }
+
+ $out = '';
+ foreach ( $help as $type => $lines ) {
+ if ( $type === ':' ) {
+ $files = '';
+ foreach ( $lines as $line ) {
+ $files .= ' ' . preg_replace( '/([^ :]+):(\d+)/', $pattern, $line );
+ }
+ $out .= "<nowiki>#:</nowiki> $files<br />";
+ } else {
+ foreach ( $lines as $line ) {
+ $out .= "<nowiki>#$type</nowiki> $line<br />";
+ }
+ }
+ }
+
+ return [
+ 'language' => $wgContLang->getCode(),
+ // @todo Provide raw data when possible
+ // 'value' => $help,
+ 'html' => $this->context->getOutput()->parse( $out ),
+ ];
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/InOtherLanguagesAid.php b/www/wiki/extensions/Translate/translationaids/InOtherLanguagesAid.php
new file mode 100644
index 00000000..41570860
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/InOtherLanguagesAid.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2012-2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives the "in other languages" suggestions.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class InOtherLanguagesAid extends TranslationAid {
+ public function getData() {
+ $suggestions = [
+ '**' => 'suggestion',
+ ];
+
+ // Fuzzy translations are not included in these
+ $translations = $this->dataProvider->getGoodTranslations();
+ $code = $this->handle->getCode();
+
+ $sourceLanguage = $this->handle->getGroup()->getSourceLanguage();
+
+ foreach ( $this->getFallbacks( $code ) as $fbcode ) {
+ if ( !isset( $translations[$fbcode] ) ) {
+ continue;
+ }
+
+ if ( $fbcode === $sourceLanguage ) {
+ continue;
+ }
+
+ $suggestions[] = [
+ 'language' => $fbcode,
+ 'value' => $translations[$fbcode],
+ ];
+ }
+
+ return $suggestions;
+ }
+
+ /**
+ * Get the languages for "in other languages". That would be translation
+ * assistant languages with defined language fallbacks additionally.
+ * @param string $code
+ * @return string[] List of language codes
+ */
+ protected function getFallbacks( $code ) {
+ global $wgTranslateLanguageFallbacks;
+
+ // User preference has the final say
+ $preference = $this->context->getUser()->getOption( 'translate-editlangs' );
+ if ( $preference !== 'default' ) {
+ $fallbacks = array_map( 'trim', explode( ',', $preference ) );
+ foreach ( $fallbacks as $k => $v ) {
+ if ( $v === $code ) {
+ unset( $fallbacks[$k] );
+ }
+ }
+
+ return $fallbacks;
+ }
+
+ // Global configuration settings
+ $fallbacks = [];
+ if ( isset( $wgTranslateLanguageFallbacks[$code] ) ) {
+ $fallbacks = (array)$wgTranslateLanguageFallbacks[$code];
+ }
+
+ $list = Language::getFallbacksFor( $code );
+ array_pop( $list ); // Get 'en' away from the end
+ $fallbacks = array_merge( $list, $fallbacks );
+
+ return array_unique( $fallbacks );
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/InsertablesAid.php b/www/wiki/extensions/Translate/translationaids/InsertablesAid.php
new file mode 100644
index 00000000..0bd964f5
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/InsertablesAid.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which suggests insertables. Insertable is a string that
+ * usually does not need translation and is difficult to type manually.
+ *
+ * @ingroup TranslationAids
+ * @since 2013.09
+ */
+class InsertablesAid extends TranslationAid {
+ public function getData() {
+ // We need to get the primary group to get the correct file
+ // So $group can be different from $this->group
+ $group = $this->handle->getGroup();
+
+ // This was added later, so not all classes have it. In addition
+ // the message group class hierarche doesn't lend itself easily
+ // to the user of interfaces for this purpose.
+ if ( !method_exists( $group, 'getInsertablesSuggester' ) ) {
+ throw new TranslationHelperException( 'Group does not have a suggester' );
+ }
+
+ $suggester = $group->getInsertablesSuggester();
+
+ // It is okay to return null suggester
+ if ( !$suggester ) {
+ throw new TranslationHelperException( 'Group does not have a suggester' );
+ }
+
+ $insertables = $suggester->getInsertables( $this->dataProvider->getDefinition() );
+ $blob = [];
+ foreach ( $insertables as $insertable ) {
+ $displayText = $insertable->getDisplayText();
+
+ // The keys are used for de-duplication
+ $blob[$displayText] = [
+ 'display' => $displayText,
+ 'pre' => $insertable->getPreText(),
+ 'post' => $insertable->getPostText(),
+ ];
+ }
+
+ $blob = array_values( $blob );
+ $blob['**'] = 'insertable';
+
+ return $blob;
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/MachineTranslationAid.php b/www/wiki/extensions/Translate/translationaids/MachineTranslationAid.php
new file mode 100644
index 00000000..fa14a13b
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/MachineTranslationAid.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2012-2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives suggestion from machine translation services.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01 | 2015.02 extends QueryAggregatorAwareTranslationAid
+ */
+class MachineTranslationAid extends QueryAggregatorAwareTranslationAid {
+ public function populateQueries() {
+ $definition = $this->dataProvider->getDefinition();
+ $translations = $this->dataProvider->getGoodTranslations();
+ $from = $this->group->getSourceLanguage();
+ $to = $this->handle->getCode();
+
+ if ( trim( $definition ) === '' ) {
+ return;
+ }
+
+ foreach ( $this->getWebServices( 'mt' ) as $service ) {
+ if ( $service->checkTranslationServiceFailure() ) {
+ continue;
+ }
+
+ try {
+ if ( $service->isSupportedLanguagePair( $from, $to ) ) {
+ $this->storeQuery( $service, $from, $to, $definition );
+ continue;
+ }
+
+ // Search for translations which we can use as a source for MT
+ // @todo: Support setting priority of languages like Yandex used to have
+ foreach ( $translations as $from => $text ) {
+ if ( !$service->isSupportedLanguagePair( $from, $to ) ) {
+ continue;
+ }
+
+ $this->storeQuery( $service, $from, $to, $text );
+ break;
+ }
+ } catch ( TranslationWebServiceConfigurationException $e ) {
+ throw new TranslationHelperException( $service->getName() . ': ' . $e->getMessage() );
+ }
+ }
+ }
+
+ public function getData() {
+ $suggestions = [ '**' => 'suggestion' ];
+
+ foreach ( $this->getQueryData() as $queryData ) {
+ $suggestions[] = $this->formatSuggestion( $queryData );
+ }
+
+ return array_filter( $suggestions );
+ }
+
+ /**
+ * @param array $queryData
+ * @return array|null
+ */
+ protected function formatSuggestion( array $queryData ) {
+ $service = $queryData['service'];
+ $response = $queryData['response'];
+ $sourceLanguage = $queryData['language'];
+ $sourceText = $queryData['text'];
+
+ $result = $service->getResultData( $response );
+ if ( $result === null ) {
+ return null;
+ }
+
+ return [
+ 'target' => $result,
+ 'service' => $service->getName(),
+ 'source_language' => $sourceLanguage,
+ 'source' => $sourceText,
+ ];
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/MessageDefinitionAid.php b/www/wiki/extensions/Translate/translationaids/MessageDefinitionAid.php
new file mode 100644
index 00000000..257435c5
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/MessageDefinitionAid.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2012-2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives the message definition.
+ * This usually matches the content of the page ns:key/source_language.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class MessageDefinitionAid extends TranslationAid {
+ public function getData() {
+ $language = $this->group->getSourceLanguage();
+
+ return [
+ 'value' => $this->dataProvider->getDefinition(),
+ 'language' => $language,
+ ];
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/QueryAggregatorAwareTranslationAid.php b/www/wiki/extensions/Translate/translationaids/QueryAggregatorAwareTranslationAid.php
new file mode 100644
index 00000000..11358315
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/QueryAggregatorAwareTranslationAid.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Translation aid helper class.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Helper class for translation aids which use web services.
+ *
+ * @ingroup TranslationAids
+ * @since 2015.02
+ */
+abstract class QueryAggregatorAwareTranslationAid
+ extends TranslationAid
+ implements QueryAggregatorAware
+{
+ private $queries = [];
+ private $aggregator;
+
+ // Interface: QueryAggregatorAware
+ public function setQueryAggregator( QueryAggregator $aggregator ) {
+ $this->aggregator = $aggregator;
+ }
+
+ /**
+ * Stores a web service query for later execution.
+ * @param TranslationWebService $service
+ * @param string $from Source language
+ * @param string $to Target language
+ * @param string $text Source text
+ */
+ protected function storeQuery( TranslationWebService $service, $from, $to, $text ) {
+ $queries = $service->getQueries( $text, $from, $to );
+ foreach ( $queries as $query ) {
+ $this->queries[] = [
+ 'id' => $this->aggregator->addQuery( $query ),
+ 'language' => $from,
+ 'text' => $text,
+ 'service' => $service,
+ ];
+ }
+ }
+
+ /**
+ * Returns all stored queries.
+ * @return array Map of executed queries:
+ * - language: string: source language
+ * - text: string: source text
+ * - response: TranslationQueryResponse
+ */
+ protected function getQueryData() {
+ foreach ( $this->queries as &$queryData ) {
+ $queryData['response'] = $this->aggregator->getResponse( $queryData['id'] );
+ unset( $queryData['id'] );
+ }
+
+ return $this->queries;
+ }
+
+ /**
+ * Returns all web services of given type.
+ * @param string $type
+ * @return TranslationWebService[]
+ */
+ protected function getWebServices( $type ) {
+ global $wgTranslateTranslationServices;
+
+ $services = [];
+ foreach ( $wgTranslateTranslationServices as $name => $config ) {
+ $service = TranslationWebService::factory( $name, $config );
+ if ( !$service || $service->getType() !== $type ) {
+ continue;
+ }
+
+ $services[$name] = $service;
+ }
+
+ return $services;
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/SupportAid.php b/www/wiki/extensions/Translate/translationaids/SupportAid.php
new file mode 100644
index 00000000..b4a65c12
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/SupportAid.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives an url where users can ask for help
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-02
+ */
+class SupportAid extends TranslationAid {
+ public function getData() {
+ return [
+ 'url' => self::getSupportUrl( $this->handle->getTitle() ),
+ ];
+ }
+
+ /**
+ * Target URL for a link provided by a support button/aid.
+ *
+ * @param Title $title Title object for the translation message.
+ * @since 2015.09
+ * @return string
+ * @throws TranslationHelperException
+ */
+ public static function getSupportUrl( Title $title ) {
+ global $wgTranslateSupportUrl, $wgTranslateSupportUrlNamespace;
+ $namespace = $title->getNamespace();
+
+ // Fetch the configuration for this namespace if possible, or the default.
+ if ( isset( $wgTranslateSupportUrlNamespace[$namespace] ) ) {
+ $config = $wgTranslateSupportUrlNamespace[$namespace];
+ } elseif ( $wgTranslateSupportUrl ) {
+ $config = $wgTranslateSupportUrl;
+ } else {
+ throw new TranslationHelperException( 'Support page not configured' );
+ }
+
+ // Preprocess params
+ $params = [];
+ if ( isset( $config['params'] ) ) {
+ foreach ( $config['params'] as $key => $value ) {
+ $params[$key] = str_replace( '%MESSAGE%', $title->getPrefixedText(), $value );
+ }
+ }
+
+ // Return the URL or make one from the page
+ if ( isset( $config['url'] ) ) {
+ return wfAppendQuery( $config['url'], $params );
+ } elseif ( isset( $config['page'] ) ) {
+ $page = Title::newFromText( $config['page'] );
+ if ( !$page ) {
+ throw new TranslationHelperException( 'Support page not configured properly' );
+ }
+ return $page->getFullURL( $params );
+ } else {
+ throw new TranslationHelperException( 'Support page not configured properly' );
+ }
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/TTMServerAid.php b/www/wiki/extensions/Translate/translationaids/TTMServerAid.php
new file mode 100644
index 00000000..6f4f69a5
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/TTMServerAid.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives suggestion from translation memory.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01 | 2015.02 extends QueryAggregatorAwareTranslationAid
+ */
+class TTMServerAid extends QueryAggregatorAwareTranslationAid {
+ public function populateQueries() {
+ $text = $this->dataProvider->getDefinition();
+ $from = $this->group->getSourceLanguage();
+ $to = $this->handle->getCode();
+
+ foreach ( $this->getWebServices( 'ttmserver' ) as $service ) {
+ $this->storeQuery( $service, $from, $to, $text );
+ }
+ }
+
+ public function getData() {
+ $suggestions = [];
+
+ $text = $this->dataProvider->getDefinition();
+ $from = $this->group->getSourceLanguage();
+ $to = $this->handle->getCode();
+
+ if ( trim( $text ) === '' ) {
+ return $suggestions;
+ }
+
+ // "Local" queries using a client can't be run in parallel with web services
+ global $wgTranslateTranslationServices;
+ foreach ( $wgTranslateTranslationServices as $name => $config ) {
+ $server = TTMServer::factory( $config );
+
+ try {
+ if ( $server instanceof ReadableTTMServer ) {
+ // Except if they are public, we can call back via API
+ if ( isset( $config['public'] ) && $config['public'] === true ) {
+ continue;
+ }
+
+ $query = $server->query( $from, $to, $text );
+ } else {
+ continue;
+ }
+ } catch ( Exception $e ) {
+ // Not ideal to catch all exceptions
+ continue;
+ }
+
+ foreach ( $query as $item ) {
+ $item['service'] = $name;
+ $item['source_language'] = $from;
+ $item['local'] = $server->isLocalSuggestion( $item );
+ $item['uri'] = $server->expandLocation( $item );
+ $suggestions[] = $item;
+ }
+ }
+
+ // Results from web services
+ foreach ( $this->getQueryData() as $queryData ) {
+ $sugs = $this->formatSuggestions( $queryData );
+ $suggestions = array_merge( $suggestions, $sugs );
+ }
+
+ $suggestions = TTMServer::sortSuggestions( $suggestions );
+ // Must be here to not mess up the sorting function
+ $suggestions['**'] = 'suggestion';
+
+ return $suggestions;
+ }
+
+ protected function formatSuggestions( array $queryData ) {
+ $service = $queryData['service'];
+ $response = $queryData['response'];
+ $sourceLanguage = $queryData['language'];
+ $sourceText = $queryData['text'];
+
+ // getResultData returns a null on failure instead of throwing an exception
+ $sugs = $service->getResultData( $response );
+ if ( $sugs === null ) {
+ return [];
+ }
+
+ foreach ( $sugs as &$sug ) {
+ $sug += [
+ 'service' => $service->getName(),
+ 'source_language' => $sourceLanguage,
+ 'source' => $sourceText,
+ ];
+ }
+ return $sugs;
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/TranslationAid.php b/www/wiki/extensions/Translate/translationaids/TranslationAid.php
new file mode 100644
index 00000000..a8068a34
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/TranslationAid.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Translation aid code.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Multipurpose class for translation aids:
+ * - interface for translation aid classes
+ * - listing of available translation aids
+ *
+ * @defgroup TranslationAids Translation Aids
+ * @since 2013-01-01
+ */
+abstract class TranslationAid {
+ /**
+ * @var MessageGroup
+ */
+ protected $group;
+
+ /**
+ * @var MessageHandle
+ */
+ protected $handle;
+
+ /**
+ * @var IContextSource
+ */
+ protected $context;
+
+ /**
+ * @var TranslationAidDataProvider
+ */
+ protected $dataProvider;
+
+ public function __construct(
+ MessageGroup $group,
+ MessageHandle $handle,
+ IContextSource $context,
+ TranslationAidDataProvider $dataProvider
+ ) {
+ $this->group = $group;
+ $this->handle = $handle;
+ $this->context = $context;
+ $this->dataProvider = $dataProvider;
+ }
+
+ /**
+ * Translation aid class should implement this function. Return value should
+ * be an array with keys and values. Because these are used in the MediaWiki
+ * API, lists (numeric keys) should have key '**' set to element name that
+ * describes the list values. For example if the translation aid provides
+ * translation suggestions, it would return an array which has key '**' set
+ * to 'suggestion' and then list of arrays, each containing fields for the
+ * information of the suggestions. See InOtherLanguagesAid for example.
+ *
+ * @throws TranslationHelperException Used to signal unexpected errors to aid
+ * debugging
+ * @return array
+ */
+ abstract public function getData();
+
+ /**
+ * List of available message types mapped to the classes
+ * implementing them.
+ *
+ * @return array
+ */
+ public static function getTypes() {
+ $types = [
+ 'definition' => 'MessageDefinitionAid',
+ 'translation' => 'CurrentTranslationAid',
+ 'inotherlanguages' => 'InOtherLanguagesAid',
+ 'documentation' => 'DocumentationAid',
+ 'mt' => 'MachineTranslationAid',
+ 'definitiondiff' => 'UpdatedDefinitionAid',
+ 'ttmserver' => 'TTMServerAid',
+ 'support' => 'SupportAid',
+ 'gettext' => 'GettextDocumentationAid',
+ 'insertables' => 'InsertablesAid',
+ ];
+
+ return $types;
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/TranslationAidDataProvider.php b/www/wiki/extensions/Translate/translationaids/TranslationAidDataProvider.php
new file mode 100644
index 00000000..2f6672ef
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/TranslationAidDataProvider.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Translation aid code.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * @since 2018.01
+ */
+class TranslationAidDataProvider {
+ private $handle;
+ private $group;
+
+ private $definition;
+ private $translations;
+
+ public function __construct( MessageHandle $handle ) {
+ $this->handle = $handle;
+ $this->group = $handle->getGroup();
+ }
+
+ /**
+ * Get the message definition. Cached for performance.
+ *
+ * @return string
+ */
+ public function getDefinition() {
+ if ( $this->definition !== null ) {
+ return $this->definition;
+ }
+
+ // Optional performance optimization
+ if ( method_exists( $this->group, 'getMessageContent' ) ) {
+ $this->definition = $this->group->getMessageContent( $this->handle );
+ } else {
+ $this->definition = $this->group->getMessage(
+ $this->handle->getKey(),
+ $this->group->getSourceLanguage()
+ );
+ }
+
+ return $this->definition;
+ }
+
+ /**
+ * @return Content
+ */
+ public function getDefinitionContent() {
+ return ContentHandler::makeContent( $this->getDefinition(), $this->handle->getTitle() );
+ }
+
+ /**
+ * Get the translations in all languages. Cached for performance.
+ * Fuzzy translation are not included.
+ *
+ * @return array Language code => Translation
+ */
+ public function getGoodTranslations() {
+ if ( $this->translations !== null ) {
+ return $this->translations;
+ }
+
+ $data = self::loadTranslationData( wfGetDB( DB_REPLICA ), $this->handle );
+ $translations = [];
+ $prefixLength = strlen( $this->handle->getTitleForBase()->getDBKey() . '/' );
+
+ foreach ( $data as $page => $translation ) {
+ // Could use MessageHandle here, but that queries the message index.
+ // Instead we can get away with simple string manipulation.
+ $code = substr( $page, $prefixLength );
+ if ( !Language::isKnownLanguageTag( $code ) ) {
+ continue;
+ }
+
+ $translations[ $code ] = $translation;
+ }
+
+ $this->translations = $translations;
+
+ return $translations;
+ }
+
+ private static function loadTranslationData( IDatabase $db, MessageHandle $handle ) {
+ if ( method_exists( 'Revision', 'getQueryInfo' ) ) {
+ $queryInfo = Revision::getQueryInfo( [ 'page', 'text' ] );
+ $tables = $queryInfo[ 'tables' ];
+ $fields = $queryInfo[ 'fields' ];
+ $conds = [];
+ $options = [];
+ $joins = $queryInfo[ 'joins' ];
+ } else {
+ // BC for <= MW 1.31
+ $tables = [ 'page', 'text', 'revision' ];
+ $fields = array_merge(
+ Revision::selectFields(),
+ Revision::selectPageFields(),
+ Revision::selectTextFields()
+ );
+ $conds = [];
+ $options = [];
+ $joins = [
+ 'page' => Revision::pageJoinCond(),
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ]
+ ];
+ }
+
+ // The list of pages we want to select, and their latest versions
+ $conds['page_namespace'] = $handle->getTitle()->getNamespace();
+ $base = $handle->getKey();
+ $conds[] = 'page_title ' . $db->buildLike( "$base/", $db->anyString() );
+ $conds[] = 'rev_id=page_latest';
+
+ // For fuzzy tags we also need:
+ $tables[] = 'revtag';
+ $conds[ 'rt_type' ] = null;
+ $joins[ 'revtag' ] = [
+ 'LEFT JOIN',
+ [ 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => 'fuzzy' ]
+ ];
+
+ $rows = $db->select( $tables, $fields, $conds, __METHOD__, $options, $joins );
+
+ $pages = [];
+ foreach ( $rows as $row ) {
+ $pages[$row->page_title] = Revision::getRevisionText( $row );
+ }
+
+ return $pages;
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/UnsupportedTranslationAid.php b/www/wiki/extensions/Translate/translationaids/UnsupportedTranslationAid.php
new file mode 100644
index 00000000..af118e73
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/UnsupportedTranslationAid.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Harry Burt
+ * @copyright Copyright © 2013, Harry Burt
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Dummy translation aid that always errors
+ *
+ * @ingroup TranslationAids
+ * @since 2013-03-29
+ */
+class UnsupportedTranslationAid extends TranslationAid {
+ public function getData() {
+ throw new TranslationHelperException( 'This translation aid is disabled' );
+ }
+}
diff --git a/www/wiki/extensions/Translate/translationaids/UpdatedDefinitionAid.php b/www/wiki/extensions/Translate/translationaids/UpdatedDefinitionAid.php
new file mode 100644
index 00000000..42ece6b6
--- /dev/null
+++ b/www/wiki/extensions/Translate/translationaids/UpdatedDefinitionAid.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Translation aid provider.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @copyright Copyright © 2012-2013, Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Translation aid which gives the message definition.
+ * This usually matches the content of the page ns:key/source_language.
+ *
+ * @ingroup TranslationAids
+ * @since 2013-01-01
+ */
+class UpdatedDefinitionAid extends TranslationAid {
+ public function getData() {
+ $db = TranslateUtils::getSafeReadDB();
+ $conds = [
+ 'rt_page' => $this->handle->getTitle()->getArticleID(),
+ 'rt_type' => RevTag::getType( 'tp:transver' ),
+ ];
+ $options = [
+ 'ORDER BY' => 'rt_revision DESC',
+ ];
+
+ $translationRevision = $db->selectField( 'revtag', 'rt_value', $conds, __METHOD__, $options );
+ if ( $translationRevision === false ) {
+ throw new TranslationHelperException( 'No definition revision recorded' );
+ }
+
+ $definitionTitle = Title::makeTitleSafe(
+ $this->handle->getTitle()->getNamespace(),
+ $this->handle->getKey() . '/' . $this->group->getSourceLanguage()
+ );
+
+ if ( !$definitionTitle || !$definitionTitle->exists() ) {
+ throw new TranslationHelperException( 'Definition page does not exist' );
+ }
+
+ // Using newFromId instead of newFromTitle, because the page might have been renamed
+ $oldrev = Revision::newFromId( $translationRevision );
+ if ( !$oldrev ) {
+ throw new TranslationHelperException( 'Old definition version does not exist anymore' );
+ }
+
+ $oldContent = $oldrev->getContent();
+ $newContent = $this->dataProvider->getDefinitionContent();
+
+ if ( !$oldContent ) {
+ throw new TranslationHelperException( 'Old definition version does not exist anymore' );
+ }
+
+ if ( !$oldContent instanceof WikitextContent || !$newContent instanceof WikitextContent ) {
+ throw new TranslationHelperException( 'Can only work on Wikitext content' );
+ }
+
+ if ( $oldContent->equals( $newContent ) ) {
+ throw new TranslationHelperException( 'No changes' );
+ }
+
+ $diff = new DifferenceEngine( $this->context );
+ $diff->setTextLanguage( wfGetLangObj( $this->group->getSourceLanguage() ) );
+ $diff->setContent( $oldContent, $newContent );
+ $diff->setReducedLineNumbers();
+ $diff->showDiffStyle();
+
+ $html = $diff->getDiff(
+ $this->context->msg( 'tpt-diff-old' )->escaped(),
+ $this->context->msg( 'tpt-diff-new' )->escaped()
+ );
+
+ return [
+ 'value_old' => $oldContent->getNativeData(),
+ 'value_new' => $newContent->getNativeData(),
+ 'revisionid_old' => $oldrev->getId(),
+ 'revisionid_new' => $definitionTitle->getLatestRevID(),
+ 'language' => $this->group->getSourceLanguage(),
+ 'html' => $html,
+ ];
+ }
+}