public function execute() { $params = $this->extractRequestParams(); $title = Title::newFromText($params['title']); if (!$title) { $this->dieUsage('Invalid title', 'invalidtitle'); } $handle = new MessageHandle($title); if (!$handle->isValid()) { $this->dieUsage('Title does not correspond to a translatable message', 'nomessagefortitle'); } if (strval($params['group']) !== '') { $group = MessageGroups::getGroup($params['group']); } else { $group = $handle->getGroup(); } if (!$group) { $this->dieUsage('Invalid group', 'invalidgroup'); } $data = array(); $times = array(); $props = $params['prop']; $aggregator = new QueryAggregator(); // Figure out the intersection of supported and requested aids $types = $group->getTranslationAids(); $props = array_intersect($props, array_keys($types)); $result = $this->getResult(); // Create list of aids, populate web services queries $aids = array(); foreach ($props as $type) { $class = $types[$type]; $obj = new $class($group, $handle, $this); if ($obj instanceof QueryAggregatorAware) { $obj->setQueryAggregator($aggregator); $obj->populateQueries(); } $aids[$type] = $obj; } // Execute all web service queries asynchronously to save time $start = microtime(true); $aggregator->run(); $times['query_aggregator'] = round(microtime(true) - $start, 3); // Construct the result data structure foreach ($aids as $type => $obj) { $start = microtime(true); try { $aid = $obj->getData(); } catch (TranslationHelperException $e) { $aid = array('error' => $e->getMessage()); } if (isset($aid['**'])) { $result->setIndexedTagName($aid, $aid['**']); unset($aid['**']); } $data[$type] = $aid; $times[$type] = round(microtime(true) - $start, 3); } $result->addValue(null, 'helpers', $data); $result->addValue(null, 'times', $times); }
/** * Shovels the new translation into translation memory. * Hook: Translate:newTranslation * * @param $handle MessageHandle * @param $revision * @param $text string * @param $user User * * @return bool */ public static function update( MessageHandle $handle, $revision, $text, User $user ) { global $wgContLang; $dbw = self::getDatabaseHandle(); // Not in use or misconfigured if ( $dbw === null ) { return true; } // Skip definitions to not slow down mass imports etc. // These will be added when first translation is made if ( $handle->getCode() === 'en' ) { return true; } $group = $handle->getGroup(); $key = $handle->getKey(); $code = $handle->getCode(); $ns_text = $wgContLang->getNsText( $group->getNamespace() ); $definition = $group->getMessage( $key, 'en' ); if ( !is_string( $definition ) || !strlen( $definition ) ) { wfDebugLog( 'tmserver', "Unable to get definition for $ns_text:$key/$code" ); return true; } $tmDefinition = array( 'text' => $definition, 'context' => "$ns_text:$key", 'length' => strlen( $definition ), 'lang' => 'en' ); // Check that the definition exists, add it if not $source_id = $dbw->selectField( '`sources`', 'sid', $tmDefinition, __METHOD__ ); if ( $source_id === false ) { $dbw->insert( '`sources`', $tmDefinition, __METHOD__ ); $source_id = $dbw->insertId(); wfDebugLog( 'tmserver', "Inserted new tm-definition for $ns_text:$key:\n$definition\n----------" ); } $delete = array( 'sid' => $source_id, 'lang' => $code, ); $insert = $delete + array( 'text' => $text, 'time' => wfTimestamp(), ); // Purge old translations for this message $dbw->delete( '`targets`', $delete, __METHOD__ ); // We only do SQlite which does not need to know unique indexes $dbw->replace( '`targets`', null, $insert, __METHOD__ ); wfDebugLog( 'tmserver', "Inserted new tm-translation for $ns_text:$key/$code" ); return true; }
public function getCheckBox() { $this->mustBeKnownMessage(); global $wgTranslateDocumentationLanguageCode; $context = RequestContext::getMain(); $title = $context->getOutput()->getTitle(); list($alias, ) = SpecialPageFactory::resolveAlias($title->getText()); $tux = SpecialTranslate::isBeta($context->getRequest()) && $title->isSpecialPage() && $alias === 'Translate'; $formattedChecks = $tux ? FormatJson::encode(array()) : Html::element('div', array('class' => 'mw-translate-messagechecks')); $page = $this->handle->getKey(); $translation = $this->getTranslation(); $code = $this->handle->getCode(); $en = $this->getDefinition(); if (strval($translation) === '') { return $formattedChecks; } if ($code === $wgTranslateDocumentationLanguageCode) { return $formattedChecks; } // We need to get the primary group of the message. It may differ from // the supplied group (aggregate groups, dynamic groups). $checker = $this->handle->getGroup()->getChecker(); if (!$checker) { return $formattedChecks; } $message = new FatMessage($page, $en); // Take the contents from edit field as a translation $message->setTranslation($translation); $checks = $checker->checkMessage($message, $code); if (!count($checks)) { return $formattedChecks; } $checkMessages = array(); foreach ($checks as $checkParams) { $key = array_shift($checkParams); $checkMessages[] = $context->msg($key, $checkParams)->parse(); } if ($tux) { $formattedChecks = FormatJson::encode($checkMessages); } else { $formattedChecks = Html::rawElement('div', array('class' => 'mw-translate-messagechecks'), TranslateUtils::fieldset($context->msg('translate-edit-warnings')->escaped(), implode('<hr />', $checkMessages), array('class' => 'mw-sp-translate-edit-warnings'))); } return $formattedChecks; }
protected function formatGettextComments() { $this->mustBeKnownMessage(); // 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) { return ''; } $ffs = $group->getFFS(); if ($ffs instanceof GettextFFS) { global $wgContLang; $mykey = $wgContLang->lcfirst($this->handle->getKey()); $data = $ffs->read($this->group->getSourceLanguage()); $help = $data['TEMPLATE'][$mykey]['comments']; // Do not display an empty comment. That's no help and takes up unnecessary space. $conf = $this->group->getConfiguration(); if (isset($conf['BASIC']['codeBrowser'])) { $out = ''; $pattern = $conf['BASIC']['codeBrowser']; $pattern = str_replace('%FILE%', '\\1', $pattern); $pattern = str_replace('%LINE%', '\\2', $pattern); $pattern = "[{$pattern} \\1:\\2]"; 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 "{$out}"; } } return ''; }
public function update(MessageHandle $handle, $targetText) { if (!$handle->isValid() || $handle->getCode() === '') { return false; } $mkey = $handle->getKey(); $group = $handle->getGroup(); $targetLanguage = $handle->getCode(); $sourceLanguage = $group->getSourceLanguage(); // Skip definitions to not slow down mass imports etc. // These will be added when the first translation is made if ($targetLanguage === $sourceLanguage) { return false; } $definition = $group->getMessage($mkey, $sourceLanguage); if (!is_string($definition) || !strlen(trim($definition))) { return false; } $context = Title::makeTitle($handle->getTitle()->getNamespace(), $mkey); $dbw = $this->getDB(DB_MASTER); /* Check that the definition exists and fetch the sid. If not, add * the definition and retrieve the sid. If the definition changes, * we will create a new entry - otherwise we could at some point * get suggestions which do not match the original definition any * longer. The old translations are still kept until purged by * rerunning the bootstrap script. */ $conds = array('tms_context' => $context->getPrefixedText(), 'tms_text' => $definition); $sid = $dbw->selectField('translate_tms', 'tms_sid', $conds, __METHOD__); if ($sid === false) { $sid = $this->insertSource($context, $sourceLanguage, $definition); } // Delete old translations for this message if any. Could also use replace $deleteConds = array('tmt_sid' => $sid, 'tmt_lang' => $targetLanguage); $dbw->delete('translate_tmt', $deleteConds, __METHOD__); // Insert the new translation if ($targetText !== null) { $row = $deleteConds + array('tmt_text' => $targetText); $dbw->insert('translate_tmt', $row, __METHOD__); } return true; }
public function getDefinitions() { global $wgTranslateMessageNamespaces; $db = wfGetDB(DB_SLAVE); $tables = 'recentchanges'; $fields = array('rc_namespace', 'rc_title'); $conds = array('rc_title ' . $db->buildLike($db->anyString(), '/' . $this->language), 'rc_namespace' => $wgTranslateMessageNamespaces, 'rc_type != ' . RC_LOG, 'rc_id > ' . $this->getRCCutoff()); $options = array('ORDER BY' => 'rc_id DESC', 'LIMIT' => 1000); $res = $db->select($tables, $fields, $conds, __METHOD__, $options); $defs = array(); foreach ($res as $row) { $title = Title::makeTitle($row->rc_namespace, $row->rc_title); $handle = new MessageHandle($title); if (!$handle->isValid()) { continue; } $mkey = $row->rc_namespace . ':' . $handle->getKey(); if (!isset($defs[$mkey])) { $group = $handle->getGroup(); $defs[$mkey] = $group->getMessage($handle->getKey(), $this->getSourceLanguage()); } } return $defs; }
/** * Hook to update source and destination translation pages on moving translation units * Hook: TitleMoveComplete * @since 2014.08 */ public static function onMoveTranslationUnits(Title &$ot, Title &$nt, User &$user, $oldid, $newid, $reason) { // Do the update only once. In case running by job queue, the update is not done here if (self::$jobQueueRunning) { return; } $groupLast = null; foreach (array($ot, $nt) as $title) { $handle = new MessageHandle($title); if (!$handle->isValid()) { continue; } // Documentation pages are never translation pages if ($handle->isDoc()) { continue; } $group = $handle->getGroup(); if (!$group instanceof WikiPageMessageGroup) { continue; } $language = $handle->getCode(); // Ignore pages such as Translations:Page/unit without language code if (strval($code) === '') { continue; } // Update the page only once if source and destination units // belong to the same page if ($group !== $groupLast) { $groupLast = $group; $page = TranslatablePage::newFromTitle($group->getTitle()); self::updateTranslationPage($page, $language, $user, 0, $reason); } } }
/** * Adds tag which identifies the revision of source message at that time. * This is used to show diff against current version of source message * when updating a translation. * Hook: Translate:newTranslation * @param MessageHandle $handle * @param int $revision * @param string $text * @param User $user * @return bool */ public static function updateTransverTag(MessageHandle $handle, $revision, $text, User $user) { if ($user->isAllowed('bot')) { return false; } $group = $handle->getGroup(); $title = $handle->getTitle(); $name = $handle->getKey() . '/' . $group->getSourceLanguage(); $definitionTitle = Title::makeTitleSafe($title->getNamespace(), $name); if (!$definitionTitle || !$definitionTitle->exists()) { return true; } $definitionRevision = $definitionTitle->getLatestRevID(); $dbw = wfGetDB(DB_MASTER); $conds = array('rt_page' => $title->getArticleID(), 'rt_type' => RevTag::getType('tp:transver'), 'rt_revision' => $revision, 'rt_value' => $definitionRevision); $index = array('rt_type', 'rt_page', 'rt_revision'); $dbw->replace('revtag', array($index), $conds, __METHOD__); return true; }
public function update(MessageHandle $handle, $targetText) { if ($handle->getCode() === '') { return false; } /* There are various different cases here: * [new or updated] [fuzzy|non-fuzzy] [translation|definition] * 1) We don't distinguish between new or updated here. * 2) Delete old translation, but not definition * 3) Insert new translation or definition, if non-fuzzy * The definition should never be fuzzied anyway. * * These only apply to known messages. */ $update = $this->client->createUpdate(); $title = $handle->getTitle(); $doDelete = true; $sourceLanguage = ''; if ($handle->isValid()) { $sourceLanguage = $handle->getGroup()->getSourceLanguage(); if ($handle->getCode() === $sourceLanguage) { $doDelete = false; } } if ($doDelete) { $base = Title::makeTitle($title->getNamespace(), $handle->getKey()); $conds = array('wiki' => wfWikiId(), 'language' => $handle->getCode(), 'messageid' => $base->getPrefixedText()); foreach ($conds as $key => &$value) { $value = "{$key}:" . $update->getHelper()->escapePhrase($value); } $update->addDeleteQuery(implode(' AND ', $conds)); } if ($targetText !== null) { if ($handle->isValid()) { // Of the message definition page $targetTitle = $handle->getTitle(); $sourceTitle = Title::makeTitle($targetTitle->getNamespace(), $handle->getKey() . '/' . $sourceLanguage); $revId = intval($sourceTitle->getLatestRevID()); /* Note: in some cases the source page might not exist, in this case * we use 0 as message version identifier, to differentiate them from * orphan messages */ } else { $revId = 'orphan'; } $doc = $this->createDocument($handle, $targetText, $revId); // Add document and commit within X seconds. $update->addDocument($doc, null, self::COMMIT_WITHIN); } try { $this->client->update($update); } catch (Solarium_Exception $e) { error_log("SolrTTMServer update-write failed"); return false; } return true; }
/** * Parser function hook */ public static function translationDialogMagicWord( Parser $parser, $title = '', $linktext = '' ) { $title = Title::newFromText( $title ); if ( !$title ) return ''; $handle = new MessageHandle( $title ); if ( !$handle->isValid() ) return ''; $group = $handle->getGroup(); $callParams = array( $title->getPrefixedText(), $group->getId() ); $call = Xml::encodeJsCall( 'mw.translate.openDialog', $callParams ); $js = <<<JAVASCRIPT mw.loader.using( 'ext.translate.quickedit', function() { $call; } ); return false; JAVASCRIPT; $a = array( 'href' => $title->getFullUrl( array( 'action' => 'edit' ) ), 'onclick' => $js, ); if ( $linktext === '' ) { $linktext = wfMessage( 'translate-edit-jsopen' )->text(); } $output = Html::element( 'a', $a, $linktext ); return $parser->insertStripItem( $output, $parser->mStripState ); }
public static function onSectionSave($article, User $user, $text, $summary, $minor, $_, $_, $flags, $revision) { $title = $article->getTitle(); // Some checks $handle = new MessageHandle($title); // We are only interested in the translations namespace if (!$handle->isPageTranslation() || !$handle->isValid()) { return true; } // Do not trigger renders for fuzzy if (strpos($text, TRANSLATE_FUZZY) !== false) { return true; } $group = $handle->getGroup(); if (!$group instanceof WikiPageMessageGroup) { return true; } // Finally we know the title and can construct a Translatable page $page = TranslatablePage::newFromTitle($group->getTitle()); // Add a tracking mark if ($revision !== null) { self::addSectionTag($title, $revision->getId(), $page->getMarkedTag()); } // Update the target translation page if (!$handle->isDoc()) { $code = $handle->getCode(); self::updateTranslationPage($page, $code, $user, $flags, $summary); } return true; }
/** * Filters out messages that should not be translated under normal * conditions. * * @param MessageHandle $handle Handle for the translation target. * @return boolean * @since 2013.10 */ public static function isTranslatableMessage(MessageHandle $handle) { static $cache = array(); if (!$handle->isValid()) { return false; } $group = $handle->getGroup(); $groupId = $group->getId(); $language = $handle->getCode(); $cacheKey = "{$groupId}:{$language}"; if (!isset($cache[$cacheKey])) { $allowed = true; $discouraged = false; $whitelist = $group->getTranslatableLanguages(); if (is_array($whitelist) && !isset($whitelist[$language])) { $allowed = false; } if (self::getPriority($group) === 'discouraged') { $discouraged = true; } else { $priorityLanguages = TranslateMetadata::get($groupId, 'prioritylangs'); if ($priorityLanguages) { $map = array_flip(explode(',', $priorityLanguages)); if (!isset($map[$language])) { $discouraged = true; } } } $cache[$cacheKey] = array('relevant' => $allowed && !$discouraged, 'tags' => array()); $groupTags = $group->getTags(); foreach (array('ignored', 'optional') as $tag) { if (isset($groupTags[$tag])) { foreach ($groupTags[$tag] as $key) { // TODO: ucfirst should not be here $cache[$cacheKey]['tags'][ucfirst($key)] = true; } } } } return $cache[$cacheKey]['relevant'] && !isset($cache[$cacheKey]['tags'][ucfirst($handle->getKey())]); }
public function execute($par) { global $wgLanguageCode; $this->setHeaders(); $this->checkPermissions(); $server = TTMServer::primary(); if (!$server instanceof SearchableTTMServer) { throw new ErrorPageError('tux-sst-nosolr-title', 'tux-sst-nosolr-body'); } $out = $this->getOutput(); $out->addModuleStyles('jquery.uls.grid'); $out->addModuleStyles('ext.translate.special.searchtranslations.styles'); $out->addModuleStyles('ext.translate.special.translate.styles'); $out->addModules('ext.translate.special.searchtranslations'); $out->addModules('ext.translate.special.searchtranslations.operatorsuggest'); TranslateUtils::addSpecialHelpLink($out, 'Help:Extension:Translate#searching'); $this->opts = $opts = new FormOptions(); $opts->add('query', ''); $opts->add('sourcelanguage', $wgLanguageCode); $opts->add('language', ''); $opts->add('group', ''); $opts->add('grouppath', ''); $opts->add('filter', ''); $opts->add('match', ''); $opts->add('case', ''); $opts->add('limit', $this->limit); $opts->add('offset', 0); $opts->fetchValuesFromRequest($this->getRequest()); $queryString = $opts->getValue('query'); if ($queryString === '') { $this->showEmptySearch(); return; } $params = $opts->getAllValues(); $filter = $opts->getValue('filter'); try { $translationSearch = new CrossLanguageTranslationSearchQuery($params, $server); if (in_array($filter, $translationSearch->getAvailableFilters())) { if ($opts->getValue('language') === '') { $params['language'] = $this->getLanguage()->getCode(); $opts->setValue('language', $params['language']); } $documents = $translationSearch->getDocuments(); $total = $translationSearch->getTotalHits(); $resultset = $translationSearch->getResultSet(); } else { $resultset = $server->search($queryString, $params, $this->hl); $documents = $server->getDocuments($resultset); $total = $server->getTotalHits($resultset); } } catch (TTMServerException $e) { error_log('Translation search server unavailable:' . $e->getMessage()); throw new ErrorPageError('tux-sst-solr-offline-title', 'tux-sst-solr-offline-body'); } // Part 1: facets $facets = $server->getFacets($resultset); $facetHtml = ''; if (count($facets['language']) > 0) { if ($filter !== '') { $facets['language'] = array_merge($facets['language'], array($opts->getValue('language') => $total)); } $facetHtml = Html::element('div', array('class' => 'row facet languages', 'data-facets' => FormatJson::encode($this->getLanguages($facets['language'])), 'data-language' => $opts->getValue('language')), $this->msg('tux-sst-facet-language')); } if (count($facets['group']) > 0) { $facetHtml .= Html::element('div', array('class' => 'row facet groups', 'data-facets' => FormatJson::encode($this->getGroups($facets['group'])), 'data-group' => $opts->getValue('group')), $this->msg('tux-sst-facet-group')); } // Part 2: results $resultsHtml = ''; $title = Title::newFromText($queryString); if ($title && !in_array($filter, $translationSearch->getAvailableFilters())) { $handle = new MessageHandle($title); $code = $handle->getCode(); $language = $opts->getValue('language'); if ($handle->isValid() && $code !== '' && $code !== $language) { $groupId = $handle->getGroup()->getId(); $helpers = new TranslationHelpers($title, $groupId); $document['wiki'] = wfWikiId(); $document['localid'] = $handle->getTitleForBase()->getPrefixedText(); $document['content'] = $helpers->getTranslation(); $document['language'] = $handle->getCode(); array_unshift($documents, $document); $total++; } } foreach ($documents as $document) { $text = $document['content']; $text = TranslateUtils::convertWhiteSpaceToHTML($text); list($pre, $post) = $this->hl; $text = str_replace($pre, '<strong class="tux-search-highlight">', $text); $text = str_replace($post, '</strong>', $text); $title = Title::newFromText($document['localid'] . '/' . $document['language']); if (!$title) { // Should not ever happen but who knows... continue; } $resultAttribs = array('class' => 'row tux-message', 'data-title' => $title->getPrefixedText(), 'data-language' => $document['language']); $handle = new MessageHandle($title); if ($handle->isValid()) { $groupId = $handle->getGroup()->getId(); $helpers = new TranslationHelpers($title, $groupId); $resultAttribs['data-definition'] = $helpers->getDefinition(); $resultAttribs['data-translation'] = $helpers->getTranslation(); $resultAttribs['data-group'] = $groupId; $uri = $title->getLocalUrl(array('action' => 'edit')); $link = Html::element('a', array('href' => $uri), $this->msg('tux-sst-edit')->text()); } else { $url = wfParseUrl($document['uri']); $domain = $url['host']; $link = Html::element('a', array('href' => $document['uri']), $this->msg('tux-sst-view-foreign', $domain)->text()); } $access = Html::rawElement('div', array('class' => 'row tux-edit tux-message-item'), $link); $titleText = $title->getPrefixedText(); $titleAttribs = array('class' => 'row tux-title', 'dir' => 'ltr'); $textAttribs = array('class' => 'row tux-text', 'lang' => wfBCP47($document['language']), 'dir' => Language::factory($document['language'])->getDir()); $resultsHtml = $resultsHtml . Html::openElement('div', $resultAttribs) . Html::rawElement('div', $textAttribs, $text) . Html::element('div', $titleAttribs, $titleText) . $access . Html::closeElement('div'); } $resultsHtml .= Html::rawElement('hr', array('class' => 'tux-pagination-line')); $prev = $next = ''; $offset = $this->opts->getValue('offset'); $params = $this->opts->getChangedValues(); if ($total - $offset > $this->limit) { $newParams = array('offset' => $offset + $this->limit) + $params; $attribs = array('class' => 'mw-ui-button pager-next', 'href' => $this->getPageTitle()->getLocalUrl($newParams)); $next = Html::element('a', $attribs, $this->msg('tux-sst-next')->text()); } if ($offset) { $newParams = array('offset' => max(0, $offset - $this->limit)) + $params; $attribs = array('class' => 'mw-ui-button pager-prev', 'href' => $this->getPageTitle()->getLocalUrl($newParams)); $prev = Html::element('a', $attribs, $this->msg('tux-sst-prev')->text()); } $resultsHtml .= Html::rawElement('div', array('class' => 'tux-pagination-links'), "{$prev} {$next}"); $search = $this->getSearchInput($queryString); $count = $this->msg('tux-sst-count')->numParams($total); $this->showSearch($search, $count, $facetHtml, $resultsHtml, $total); }
public function update(MessageHandle $handle, $targetText) { if (!$handle->isValid() || $handle->getCode() === '') { return false; } /* There are various different cases here: * [new or updated] [fuzzy|non-fuzzy] [translation|definition] * 1) We don't distinguish between new or updated here. * 2) Delete old translation, but not definition * 3) Insert new translation or definition, if non-fuzzy * The definition should never be fuzzied anyway. * * These only apply to known messages. */ $title = $handle->getTitle(); $sourceLanguage = $handle->getGroup()->getSourceLanguage(); // Do not delete definitions, because the translations are attached to that if ($handle->getCode() !== $sourceLanguage) { $localid = $handle->getTitleForBase()->getPrefixedText(); $boolQuery = new \Elastica\Query\Bool(); $boolQuery->addMust(new Elastica\Query\Term(array('wiki' => wfWikiId()))); $boolQuery->addMust(new Elastica\Query\Term(array('language' => $handle->getCode()))); $boolQuery->addMust(new Elastica\Query\Term(array('localid' => $localid))); $query = new \Elastica\Query($boolQuery); $this->getType()->deleteByQuery($query); } // If translation was made fuzzy, we do not need to add anything if ($targetText === null) { return true; } $revId = $handle->getTitleForLanguage($sourceLanguage)->getLatestRevID(); $doc = $this->createDocument($handle, $targetText, $revId); $retries = 5; while ($retries-- > 0) { try { $this->getType()->addDocument($doc); break; } catch (\Elastica\Exception\ExceptionInterface $e) { if ($retries === 0) { throw $e; } else { $c = get_class($e); $msg = $e->getMessage(); error_log(__METHOD__ . ": update failed ({$c}: {$msg}); retrying."); sleep(10); } } } return true; }