2declare( strict_types=1 );
4namespace MediaWiki\Extension\Translate;
16use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
17use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
18use MediaWiki\Config\ServiceOptions;
19use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
49use MediaWiki\Hook\ParserFirstCallInitHook;
50use MediaWiki\Html\Html;
51use MediaWiki\Languages\LanguageNameUtils;
52use MediaWiki\MediaWikiServices;
53use MediaWiki\Revision\Hook\RevisionRecordInsertedHook;
54use MediaWiki\Revision\RevisionLookup;
55use MediaWiki\Settings\SettingsBuilder;
56use MediaWiki\SpecialPage\SpecialPage;
57use MediaWiki\Specials\SpecialSearch;
58use MediaWiki\Status\Status;
59use MediaWiki\StubObject\StubUserLang;
60use MediaWiki\Title\Title;
61use MediaWiki\User\Hook\UserGetReservedNamesHook;
62use MediaWiki\User\User;
70use Wikimedia\Rdbms\ILoadBalancer;
85 ChangeTagsListActiveHook,
87 ParserFirstCallInitHook,
88 RevisionRecordInsertedHook,
89 UserGetReservedNamesHook
95 private const USER_MERGE_TABLES = [
96 'translate_stash' =>
'ts_user',
97 'translate_reviews' =>
'trr_user',
99 private RevisionLookup $revisionLookup;
100 private ILoadBalancer $loadBalancer;
101 private Config $config;
102 private LanguageNameUtils $languageNameUtils;
104 public function __construct(
105 RevisionLookup $revisionLookup,
106 ILoadBalancer $loadBalancer,
108 LanguageNameUtils $languageNameUtils
110 $this->revisionLookup = $revisionLookup;
111 $this->loadBalancer = $loadBalancer;
112 $this->config = $config;
113 $this->languageNameUtils = $languageNameUtils;
118 global $wgTranslateYamlLibrary;
125 if ( !defined(
'TRANSLATE_FUZZY' ) ) {
126 define(
'TRANSLATE_FUZZY',
'!!FUZZY!!' );
129 if ( $wgTranslateYamlLibrary ===
null ) {
130 $wgTranslateYamlLibrary = function_exists(
'yaml_parse' ) ?
'phpyaml' :
'spyc';
133 $hooks[
'PageSaveComplete'][] = [ TranslateEditAddons::class,
'onSaveComplete' ];
134 global $wgJobClasses;
136 $wgJobClasses[
'MessageIndexRebuildJob'] = RebuildMessageIndexJob::class;
137 $wgJobClasses[
'RebuildMessageIndexJob'] = RebuildMessageIndexJob::class;
140 global $wgEnablePageTranslation;
141 if ( $wgEnablePageTranslation ) {
143 global $wgSpecialPages, $wgAvailableRights;
144 $wgSpecialPages[
'PageTranslation'] = [
145 'class' => PageTranslationSpecialPage::class,
151 'Translate:TranslatablePageMarker',
152 'Translate:TranslatablePageParser',
153 'Translate:MessageGroupMetadata',
154 'Translate:TranslatablePageView',
155 'Translate:TranslatablePageStateStore'
158 $wgSpecialPages[
'PageTranslationDeletePage'] = [
159 'class' => DeleteTranslatableBundleSpecialPage::class,
162 'Translate:TranslatableBundleDeleter',
163 'Translate:TranslatableBundleFactory',
168 $wgAvailableRights[] =
'pagetranslation';
170 $wgSpecialPages[
'PageMigration'] = MigrateTranslatablePageSpecialPage::class;
171 $wgSpecialPages[
'PagePreparation'] = PrepareTranslatablePageSpecialPage::class;
173 global $wgActionFilteredLogs, $wgLogActionsHandlers, $wgLogTypes;
182 $wgLogTypes[] =
'pagetranslation';
183 $wgLogActionsHandlers[
'pagetranslation/mark'] = TranslatableBundleLogFormatter::class;
184 $wgLogActionsHandlers[
'pagetranslation/unmark'] = TranslatableBundleLogFormatter::class;
185 $wgLogActionsHandlers[
'pagetranslation/moveok'] = TranslatableBundleLogFormatter::class;
186 $wgLogActionsHandlers[
'pagetranslation/movenok'] = TranslatableBundleLogFormatter::class;
187 $wgLogActionsHandlers[
'pagetranslation/deletelok'] = TranslatableBundleLogFormatter::class;
188 $wgLogActionsHandlers[
'pagetranslation/deletefok'] = TranslatableBundleLogFormatter::class;
189 $wgLogActionsHandlers[
'pagetranslation/deletelnok'] = TranslatableBundleLogFormatter::class;
190 $wgLogActionsHandlers[
'pagetranslation/deletefnok'] = TranslatableBundleLogFormatter::class;
191 $wgLogActionsHandlers[
'pagetranslation/encourage'] = TranslatableBundleLogFormatter::class;
192 $wgLogActionsHandlers[
'pagetranslation/discourage'] = TranslatableBundleLogFormatter::class;
193 $wgLogActionsHandlers[
'pagetranslation/prioritylanguages'] = TranslatableBundleLogFormatter::class;
194 $wgLogActionsHandlers[
'pagetranslation/associate'] = TranslatableBundleLogFormatter::class;
195 $wgLogActionsHandlers[
'pagetranslation/dissociate'] = TranslatableBundleLogFormatter::class;
196 $wgActionFilteredLogs[
'pagetranslation'] = [
197 'mark' => [
'mark' ],
198 'unmark' => [
'unmark' ],
199 'move' => [
'moveok',
'movenok' ],
200 'delete' => [
'deletefok',
'deletefnok',
'deletelok',
'deletelnok' ],
201 'encourage' => [
'encourage' ],
202 'discourage' => [
'discourage' ],
203 'prioritylanguages' => [
'prioritylanguages' ],
204 'aggregategroups' => [
'associate',
'dissociate' ],
207 $wgLogTypes[] =
'messagebundle';
208 $wgLogActionsHandlers[
'messagebundle/moveok'] = TranslatableBundleLogFormatter::class;
209 $wgLogActionsHandlers[
'messagebundle/movenok'] = TranslatableBundleLogFormatter::class;
210 $wgLogActionsHandlers[
'messagebundle/deletefok'] = TranslatableBundleLogFormatter::class;
211 $wgLogActionsHandlers[
'messagebundle/deletefnok'] = TranslatableBundleLogFormatter::class;
212 $wgActionFilteredLogs[
'messagebundle'] = [
213 'move' => [
'moveok',
'movenok' ],
214 'delete' => [
'deletefok',
'deletefnok' ],
217 $wgLogActionsHandlers[
'import/translatable-bundle'] = TranslatableBundleLogFormatter::class;
219 $wgJobClasses[
'RenderTranslationPageJob'] = RenderTranslationPageJob::class;
220 $wgJobClasses[
'NonPrioritizedRenderTranslationPageJob'] = RenderTranslationPageJob::class;
221 $wgJobClasses[
'MoveTranslatableBundleJob'] = MoveTranslatableBundleJob::class;
222 $wgJobClasses[
'DeleteTranslatableBundleJob'] = DeleteTranslatableBundleJob::class;
223 $wgJobClasses[
'UpdateTranslatablePageJob'] = UpdateTranslatablePageJob::class;
226 global $wgAPIModules;
227 $wgAPIModules[
'markfortranslation'] = [
228 'class' => MarkForTranslationActionApi::class,
230 'Translate:TranslatablePageMarker',
231 'Translate:MessageGroupMetadata',
236 global $wgNamespacesWithSubpages, $wgNamespaceProtection;
237 global $wgTranslateMessageNamespaces;
239 $wgNamespacesWithSubpages[NS_TRANSLATIONS] =
true;
240 $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] =
true;
243 $wgNamespaceProtection[NS_TRANSLATIONS] = [
'translate' ];
244 $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS;
249 $hooks[
'BeforePageDisplay'][] = [ Hooks::class,
'onBeforePageDisplay' ];
252 $hooks[
'VisualEditorBeforeEditor'][] = [ Hooks::class,
'onVisualEditorBeforeEditor' ];
255 $hooks[
'MultiContentSave'][] = [ Hooks::class,
'tpSyntaxCheck' ];
256 $hooks[
'EditFilterMergedContent'][] =
257 [ Hooks::class,
'tpSyntaxCheckForEditContent' ];
260 $hooks[
'PageSaveComplete'][] = [ Hooks::class,
'addTranstagAfterSave' ];
262 $hooks[
'RevisionRecordInserted'][] = [ Hooks::class,
'updateTranstagOnNullRevisions' ];
265 $hooks[
'ParserFirstCallInit'][] = [ self::class,
'setupParserHooks' ];
266 $hooks[
'LanguageLinks'][] = [ Hooks::class,
'addLanguageLinks' ];
267 $hooks[
'SkinTemplateGetLanguageLink'][] = [ Hooks::class,
'formatLanguageLink' ];
270 $hooks[
'GetMagicVariableIDs'][] = [ Hooks::class,
'onGetMagicVariableIDs' ];
271 $hooks[
'ParserGetVariableValueSwitch'][] = [ Hooks::class,
'onParserGetVariableValueSwitch' ];
274 $hooks[
'ParserBeforeInternalParse'][] = [ Hooks::class,
'renderTagPage' ];
276 $hooks[
'ParserBeforePreprocess'][] = [ Hooks::class,
'preprocessTagPage' ];
277 $hooks[
'ParserOutputPostCacheTransform'][] =
278 [ Hooks::class,
'onParserOutputPostCacheTransform' ];
280 $hooks[
'BeforeParserFetchTemplateRevisionRecord'][] =
281 [ Hooks::class,
'fetchTranslatableTemplateAndTitle' ];
284 $hooks[
'PageContentLanguage'][] = [ Hooks::class,
'onPageContentLanguage' ];
287 $hooks[
'getUserPermissionsErrorsExpensive'][] =
288 [ Hooks::class,
'onGetUserPermissionsErrorsExpensive' ];
290 $hooks[
'getUserPermissionsErrorsExpensive'][] =
291 [ Hooks::class,
'preventDirectEditing' ];
294 $hooks[
'ArticleViewHeader'][] = [ Hooks::class,
'translatablePageHeader' ];
297 $hooks[
'TitleGetEditNotices'][] = [ Hooks::class,
'onTitleGetEditNotices' ];
300 $hooks[
'SpecialPage_initList'][] = [ Hooks::class,
'replaceMovePage' ];
302 $hooks[
'getUserPermissionsErrorsExpensive'][] =
303 [ Hooks::class,
'lockedPagesCheck' ];
305 $hooks[
'ArticleConfirmDelete'][] = [ Hooks::class,
'disableDelete' ];
308 $hooks[
'SkinSubPageSubtitle'][] = [ Hooks::class,
'replaceSubtitle' ];
311 $hooks[
'SkinTemplateNavigation::Universal'][] = [ Hooks::class,
'translateTab' ];
314 $hooks[
'PageMoveComplete'][] = [ Hooks::class,
'onMovePageTranslationUnits' ];
317 $hooks[
'ArticleDeleteComplete'][] = [ Hooks::class,
'onDeleteTranslationUnit' ];
320 $hooks[
'ReplaceTextFilterPageTitlesForEdit'][] = [ Hooks::class,
'onReplaceTextFilterPageTitlesForEdit' ];
322 $hooks[
'ReplaceTextFilterPageTitlesForRename'][] =
323 [ Hooks::class,
'onReplaceTextFilterPageTitlesForRename' ];
326 global $wgTranslateUseSandbox;
327 if ( $wgTranslateUseSandbox ) {
328 global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions;
330 $wgSpecialPages[
'ManageTranslatorSandbox'] = [
331 'class' => ManageTranslatorSandboxSpecialPage::class,
333 'Translate:TranslationStashReader',
335 'Translate:TranslateSandbox',
339 return new ServiceOptions(
340 ManageTranslatorSandboxSpecialPage::CONSTRUCTOR_OPTIONS,
341 MediaWikiServices::getInstance()->getMainConfig()
346 $wgSpecialPages[
'TranslationStash'] = [
347 'class' => TranslationStashSpecialPage::class,
350 'Translate:TranslationStashReader',
356 return new ServiceOptions(
357 TranslationStashSpecialPage::CONSTRUCTOR_OPTIONS,
358 MediaWikiServices::getInstance()->getMainConfig()
363 $wgDefaultUserOptions[
'translate-sandbox'] =
'';
365 $wgAvailableRights[] =
'translate-sandboxmanage';
367 global $wgLogTypes, $wgLogActionsHandlers;
369 $wgLogTypes[] =
'translatorsandbox';
371 $wgLogActionsHandlers[
'translatorsandbox/promoted'] = TranslateLogFormatter::class;
372 $wgLogActionsHandlers[
'translatorsandbox/rejected'] = TranslateLogFormatter::class;
376 $wgLogActionsHandlers[
'newusers/tsbpromoted'] = LogFormatter::class;
378 $wgJobClasses[
'TranslateSandboxEmailJob'] = TranslateSandboxEmailJob::class;
380 global $wgAPIModules;
381 $wgAPIModules[
'translationstash'] = [
382 'class' => TranslationStashActionApi::class,
384 'DBLoadBalancerFactory',
386 'Translate:MessageIndex'
389 $wgAPIModules[
'translatesandbox'] = [
390 'class' => TranslatorSandboxActionApi::class,
394 'UserOptionsManager',
397 'Translate:TranslateSandbox',
401 return new ServiceOptions(
402 TranslatorSandboxActionApi::CONSTRUCTOR_OPTIONS,
403 MediaWikiServices::getInstance()->getMainConfig()
410 global $wgNamespaceRobotPolicies;
411 $wgNamespaceRobotPolicies[NS_TRANSLATIONS] =
'noindex';
414 global $wgTranslateTranslationDefaultService,
415 $wgTranslateTranslationServices;
416 if ( $wgTranslateTranslationDefaultService ===
true ) {
417 $wgTranslateTranslationDefaultService =
'TTMServer';
418 if ( !isset( $wgTranslateTranslationServices[
'TTMServer'] ) ) {
419 $wgTranslateTranslationServices[
'TTMServer'] = [
422 'type' =>
'ttmserver',
428 global $wgTranslateEnableMessageGroupSubscription;
429 if ( $wgTranslateEnableMessageGroupSubscription ) {
430 if ( !ExtensionRegistry::getInstance()->isLoaded(
'Echo' ) ) {
431 throw new ConfigException(
432 'Translate: Message group subscriptions (TranslateEnableMessageGroupSubscription) are ' .
433 'enabled but Echo extension is not installed'
436 MessageGroupSubscriptionHookHandler::registerHooks( $hooks );
437 $wgJobClasses[
'MessageGroupSubscriptionNotificationJob'] = MessageGroupSubscriptionNotificationJob::class;
440 global $wgTranslateEnableLuaIntegration;
441 if ( $wgTranslateEnableLuaIntegration ) {
442 if ( ExtensionRegistry::getInstance()->isLoaded(
'Scribunto' ) ) {
443 $hooks[
'ScribuntoExternalLibraries' ][] =
static function (
string $engine, array &$extraLibraries ) {
444 $scribuntoHookHandler =
new ScribuntoHookHandler();
445 $scribuntoHookHandler->onScribuntoExternalLibraries( $engine, $extraLibraries );
449 'Translate: Lua integration (TranslateEnableLuaIntegration) is ' .
450 'enabled but Scribunto extension is not installed'
455 static::registerHookHandlers( $hooks );
458 private static function registerHookHandlers( array $hooks ): void {
459 if ( defined(
'MW_PHPUNIT_TEST' ) && MediaWikiServices::hasInstance() ) {
462 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
463 foreach ( $hooks as $name => $handlers ) {
464 foreach ( $handlers as $h ) {
465 $hookContainer->register( $name, $h );
469 $settingsBuilder = SettingsBuilder::getInstance();
470 $settingsBuilder->registerHookHandlers( $hooks );
479 $reservedUsernames[] =
FuzzyBot::getName();
480 $reservedUsernames[] = TranslateUserManager::getName();
485 VariableHolder &$vars, Title $title, User $user
491 if ( $handle->isMessageNamespace() ) {
492 $vars->setLazyLoadVar(
493 'translate_source_text',
494 'translate-get-source',
495 [
'handle' => $handle ]
497 $vars->setLazyLoadVar(
498 'translate_target_language',
499 'translate-get-target-language',
500 [
'handle' => $handle ]
508 VariableHolder $vars,
512 if ( $method !==
'translate-get-source' && $method !==
'translate-get-target-language' ) {
516 $handle = $parameters[
'handle'];
518 if ( $handle->isValid() ) {
519 if ( $method ===
'translate-get-source' ) {
520 $group = $handle->getGroup();
521 $value = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
523 $value = $handle->getCode();
537 $builderValues[
'vars'][
'translate_source_text'] =
'translate-source-text';
538 $builderValues[
'vars'][
'translate_target_language'] =
'translate-target-language';
547 $parser->setHook(
'languages', [
Hooks::class,
'languages' ] );
558 if ( $handle->isMessageNamespace() ) {
568 global $wgTranslateDocumentationLanguageCode;
569 if ( $wgTranslateDocumentationLanguageCode ) {
572 $wgTranslateDocumentationLanguageCode === $code ||
578 $names[$wgTranslateDocumentationLanguageCode] =
579 wfMessage(
'translate-documentation-language' )->inLanguage( $code )->plain();
585 global $wgTranslateMessageNamespaces;
587 $insert[
'translation'] = [
588 'message' =>
'translate-searchprofile',
589 'tooltip' =>
'translate-searchprofile-tooltip',
590 'namespaces' => $wgTranslateMessageNamespaces,
594 $index = array_search(
'all', array_keys( $profiles ) );
597 if ( $index ===
false ) {
598 wfWarn(
'"all" not found in search profiles' );
599 $index = count( $profiles );
602 $profiles = array_merge(
603 array_slice( $profiles, 0, $index ),
605 array_slice( $profiles, $index )
611 SpecialSearch $search,
617 if ( $profile !==
'translation' ) {
621 if ( Services::getInstance()->getTtmServerFactory()->getDefaultForQuerying() instanceof
SearchableTtmServer ) {
622 $href = SpecialPage::getTitleFor(
'SearchTranslations' )
623 ->getFullUrl( [
'query' => $term ] );
624 $form = Html::successBox(
625 $search->msg(
'translate-searchprofile-note', $href )->parse(),
632 if ( !$search->getSearchEngine()->supports(
'title-suffix-filter' ) ) {
637 foreach ( $opts as $key => $value ) {
638 $hidden .= Html::hidden( $key, $value );
641 $context = $search->getContext();
642 $code = $context->getLanguage()->getCode();
643 $selected = $context->getRequest()->getVal(
'languagefilter' );
645 $languages = Utilities::getLanguageNames( $code );
648 $selector =
new XmlSelect(
'languagefilter',
'languagefilter' );
649 $selector->setDefault( $selected );
650 $selector->addOption( wfMessage(
'translate-search-nofilter' )->text(),
'-' );
651 foreach ( $languages as $code => $name ) {
652 $selector->addOption(
"$code - $name", $code );
655 $selector = $selector->getHTML();
658 wfMessage(
'translate-search-languagefilter' )->text(),
661 $params = [
'id' =>
'mw-searchoptions' ];
663 $form = Xml::fieldset(
false,
false, $params ) .
664 $hidden . $label . $selector .
665 Html::closeElement(
'fieldset' );
672 SpecialSearch $search,
676 if ( $profile !==
'translation' ) {
680 $context = $search->getContext();
681 $selected = $context->getRequest()->getVal(
'languagefilter' );
682 if ( $selected !==
'-' && $selected ) {
683 $engine->setFeatureData(
'title-suffix-filter',
"/$selected" );
684 $search->setExtraParam(
'languagefilter', $selected );
690 $pageReference = $parser->getPage();
691 if ( !$pageReference ) {
695 $linkTarget = TitleValue::newFromPage( $pageReference );
697 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
698 $parserOutput = $parser->getOutput();
699 $names = $parserOutput->getCategoryNames();
700 $parserCategories = [];
701 foreach ( $names as $name ) {
702 $parserCategories[$name] = $parserOutput->getCategorySortKey( $name );
704 $parserOutput->setExtensionData(
'translate-fake-categories', $parserCategories );
705 $parserOutput->setCategories( [] );
710 public static function showFakeCategories( OutputPage $outputPage, ParserOutput $parserOutput ): void {
711 $fakeCategories = $parserOutput->getExtensionData(
'translate-fake-categories' );
712 if ( $fakeCategories ) {
713 $outputPage->setCategoryLinks( $fakeCategories );
722 public static function addConfig( array &$vars, OutputPage $out ): void {
723 global $wgTranslateDocumentationLanguageCode,
724 $wgTranslatePermissionUrl,
725 $wgTranslateUseSandbox;
727 $title = $out->getTitle();
728 if ( $title->isSpecial(
'Translate' ) ||
729 $title->isSpecial(
'TranslationStash' ) ||
730 $title->isSpecial(
'SearchTranslations' )
732 $user = $out->getUser();
733 $vars[
'TranslateRight'] = $user->isAllowed(
'translate' );
734 $vars[
'TranslateMessageReviewRight'] = $user->isAllowed(
'translate-messagereview' );
735 $vars[
'DeleteRight'] = $user->isAllowed(
'delete' );
736 $vars[
'TranslateManageRight'] = $user->isAllowed(
'translate-manage' );
737 $vars[
'wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode;
738 $vars[
'wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl;
739 $vars[
'wgTranslateUseSandbox'] = $wgTranslateUseSandbox;
745 global $wgTranslateUseSandbox;
747 if ( $wgTranslateUseSandbox ) {
748 $sectionLabel = wfMessage(
'adminlinks_users' )->text();
749 $row = $tree->getSection( $sectionLabel )->getRow(
'main' );
750 $row->addItem( ALItem::newFromSpecialPage(
'TranslateSandbox' ) );
759 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
763 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
764 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
767 [ $field => $newUser->getId() ],
768 [ $field => $oldUser->getId() ],
781 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
784 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
785 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
788 [ $field => $oldUser->getId() ],
801 return $rc->getAttribute(
'rc_log_type' ) !==
'translationreview';
813 if ( !$target->inNamespace( NS_SPECIAL ) ) {
817 [ $name, $subpage ] = MediaWikiServices::getInstance()
818 ->getSpecialPageFactory()->resolveAlias( $target->getDBkey() );
819 if ( $name !==
'MyLanguage' || (
string)$subpage ===
'' ) {
823 $realTarget = Title::newFromText( $subpage );
824 if ( !$realTarget || !$realTarget->exists() ) {
833 public function onParserFirstCallInit( $parser ) {
834 $parser->setFunctionHook(
'translation', [ $this,
'translateRenderParserFunction' ] );
837 public function translateRenderParserFunction( Parser $parser ): string {
838 $pageReference = $parser->getPage();
839 if ( !$pageReference ) {
842 $linkTarget = TitleValue::newFromPage( $pageReference );
843 $handle =
new MessageHandle( $linkTarget );
844 $code = $handle->getCode();
845 if ( $this->languageNameUtils->isKnownLanguageTag( $code ) ) {
857 IContextSource $context,
863 if ( !$content instanceof TextContent ) {
868 $text = $content->getText();
869 $title = $context->getTitle();
872 if ( !$handle->isValid() ) {
877 if ( $user->isAllowed(
'translate-manage' ) || $user->equals( FuzzyBot::getUser() ) ) {
882 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
883 $group = $handle->getGroup();
885 if ( method_exists( $group,
'getMessageContent' ) ) {
887 $definition = $group->getMessageContent( $handle );
889 $definition = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
892 $message =
new FatMessage( $handle->getKey(), $definition );
893 $message->setTranslation( $text );
895 $messageValidator = $group->getValidator();
896 if ( !$messageValidator ) {
900 $validationResponse = $messageValidator->validateMessage( $message, $handle->getCode() );
901 if ( $validationResponse->hasErrors() ) {
902 $status->fatal(
new ApiRawMessage(
903 $context->msg(
'translate-syntax-error' )->parse(),
904 'translate-validation-failed',
907 'errors' => $validationResponse->getDescriptiveErrors( $context ),
908 'warnings' => $validationResponse->getDescriptiveWarnings( $context )
921 $parentId = $revisionRecord->getParentId();
922 if ( $parentId === 0 || $parentId ===
null ) {
927 $prevRev = $this->revisionLookup->getRevisionById( $parentId );
928 if ( !$prevRev || !$revisionRecord->hasSameContent( $prevRev ) ) {
935 $tagsToCopy = [ RevTagStore::FUZZY_TAG, RevTagStore::TRANSVER_PROP ];
937 $db = $this->loadBalancer->getConnection( DB_PRIMARY );
942 'rt_type' =>
'rt_type',
943 'rt_page' =>
'rt_page',
944 'rt_revision' => $revisionRecord->getId(),
945 'rt_value' =>
'rt_value',
949 'rt_type' => $tagsToCopy,
950 'rt_revision' => $parentId,
958 $tags[] =
'translate-translation-pages';
963 if ( $this->config->get(
'EnablePageTranslation' ) ) {
964 $tags[] =
'translate-translation-pages';
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager( $services->getTitleFactory());}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getDBLoadBalancer());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance( 'Translate.MessageGroupSubscription'), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):MessageGroupSubscriptionHookHandler { return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getDBLoadBalancerFactory());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance( 'Translate'), $services->getMainObjectStash(), $services->getDBLoadBalancerFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getDBLoadBalancer());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getDBLoadBalancerFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getDBLoadBalancer(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'));}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getDBLoadBalancerFactory(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getDBLoadBalancer(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Special page which enables deleting translations of translatable bundles and translation pages.
Hooks for page translation.
API module to mark a page for translation.
Contains code for Special:PageMigration to migrate to page transation.
A special page for marking revisions of pages for translation.
Contains code to prepare a page for translation.
Job for updating translation pages when translation or template changes.
Job for updating translation units and translation pages when a translatable page is marked for trans...
Special page for managing sandboxed users.
Special page for new users to translate example messages.