2declare( strict_types=1 );
4namespace MediaWiki\Extension\Translate;
9use MediaWiki\Api\ApiRawMessage;
10use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
11use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
12use MediaWiki\Config\Config;
13use MediaWiki\Config\ConfigException;
14use MediaWiki\Config\ServiceOptions;
15use MediaWiki\Content\Content;
16use MediaWiki\Content\TextContent;
17use MediaWiki\Context\IContextSource;
18use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
48use MediaWiki\Hook\ParserFirstCallInitHook;
49use MediaWiki\Html\Html;
50use MediaWiki\Language\Language;
51use MediaWiki\Languages\LanguageNameUtils;
52use MediaWiki\MediaWikiServices;
53use MediaWiki\Output\OutputPage;
54use MediaWiki\Parser\Parser;
55use MediaWiki\Parser\ParserOutput;
56use MediaWiki\Registration\ExtensionRegistry;
57use MediaWiki\Revision\Hook\RevisionRecordInsertedHook;
58use MediaWiki\Revision\RevisionLookup;
59use MediaWiki\Settings\SettingsBuilder;
60use MediaWiki\SpecialPage\SpecialPage;
61use MediaWiki\Specials\SpecialSearch;
62use MediaWiki\Status\Status;
63use MediaWiki\StubObject\StubUserLang;
64use MediaWiki\Title\Title;
65use MediaWiki\Title\TitleValue;
66use MediaWiki\User\Hook\UserGetReservedNamesHook;
67use MediaWiki\User\User;
68use MediaWiki\Xml\XmlSelect;
71use Wikimedia\Rdbms\ILoadBalancer;
84 ChangeTagsListActiveHook,
86 ParserFirstCallInitHook,
87 RevisionRecordInsertedHook,
88 UserGetReservedNamesHook
94 private const USER_MERGE_TABLES = [
95 'translate_stash' =>
'ts_user',
96 'translate_reviews' =>
'trr_user',
98 private RevisionLookup $revisionLookup;
99 private ILoadBalancer $loadBalancer;
100 private Config $config;
101 private LanguageNameUtils $languageNameUtils;
103 public function __construct(
104 RevisionLookup $revisionLookup,
105 ILoadBalancer $loadBalancer,
107 LanguageNameUtils $languageNameUtils
109 $this->revisionLookup = $revisionLookup;
110 $this->loadBalancer = $loadBalancer;
111 $this->config = $config;
112 $this->languageNameUtils = $languageNameUtils;
117 global $wgTranslateYamlLibrary, $wgLogTypes;
124 if ( !defined(
'TRANSLATE_FUZZY' ) ) {
125 define(
'TRANSLATE_FUZZY',
'!!FUZZY!!' );
128 $wgTranslateYamlLibrary ??= function_exists(
'yaml_parse' ) ?
'phpyaml' :
'spyc';
130 $hooks[
'PageSaveComplete'][] = [ TranslateEditAddons::class,
'onSaveComplete' ];
131 global $wgJobClasses;
133 $wgJobClasses[
'MessageIndexRebuildJob'] = RebuildMessageIndexJob::class;
134 $wgJobClasses[
'RebuildMessageIndexJob'] = RebuildMessageIndexJob::class;
137 global $wgEnablePageTranslation;
138 if ( $wgEnablePageTranslation ) {
140 global $wgSpecialPages, $wgAvailableRights;
141 $wgSpecialPages[
'PageTranslation'] = [
142 'class' => PageTranslationSpecialPage::class,
148 'Translate:TranslatablePageMarker',
149 'Translate:TranslatablePageParser',
150 'Translate:MessageGroupMetadata',
151 'Translate:TranslatablePageView',
152 'Translate:TranslatablePageStateStore',
156 $wgSpecialPages[
'PageTranslationDeletePage'] = [
157 'class' => DeleteTranslatableBundleSpecialPage::class,
160 'Translate:TranslatableBundleDeleter',
161 'Translate:TranslatableBundleFactory',
166 $wgAvailableRights[] =
'pagetranslation';
168 $wgSpecialPages[
'PageMigration'] = MigrateTranslatablePageSpecialPage::class;
169 $wgSpecialPages[
'PagePreparation'] = PrepareTranslatablePageSpecialPage::class;
171 global $wgActionFilteredLogs, $wgLogActionsHandlers;
180 if ( !in_array(
'pagetranslation', $wgLogTypes ) ) {
181 $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 if ( !in_array(
'messagebundle', $wgLogTypes ) ) {
208 $wgLogTypes[] =
'messagebundle';
210 $wgLogActionsHandlers[
'messagebundle/moveok'] = TranslatableBundleLogFormatter::class;
211 $wgLogActionsHandlers[
'messagebundle/movenok'] = TranslatableBundleLogFormatter::class;
212 $wgLogActionsHandlers[
'messagebundle/deletefok'] = TranslatableBundleLogFormatter::class;
213 $wgLogActionsHandlers[
'messagebundle/deletefnok'] = TranslatableBundleLogFormatter::class;
214 $wgActionFilteredLogs[
'messagebundle'] = [
215 'move' => [
'moveok',
'movenok' ],
216 'delete' => [
'deletefok',
'deletefnok' ],
219 $wgLogActionsHandlers[
'import/translatable-bundle'] = TranslatableBundleLogFormatter::class;
221 $wgJobClasses[
'RenderTranslationPageJob'] = RenderTranslationPageJob::class;
222 $wgJobClasses[
'NonPrioritizedRenderTranslationPageJob'] = RenderTranslationPageJob::class;
223 $wgJobClasses[
'MoveTranslatableBundleJob'] = MoveTranslatableBundleJob::class;
224 $wgJobClasses[
'DeleteTranslatableBundleJob'] = DeleteTranslatableBundleJob::class;
225 $wgJobClasses[
'UpdateTranslatablePageJob'] = UpdateTranslatablePageJob::class;
228 global $wgAPIModules;
229 $wgAPIModules[
'markfortranslation'] = [
230 'class' => MarkForTranslationActionApi::class,
232 'Translate:TranslatablePageMarker',
233 'Translate:MessageGroupMetadata',
238 global $wgNamespacesWithSubpages, $wgNamespaceProtection;
239 global $wgTranslateMessageNamespaces;
241 $wgNamespacesWithSubpages[NS_TRANSLATIONS] =
true;
242 $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] =
true;
245 $wgNamespaceProtection[NS_TRANSLATIONS] = [
'translate' ];
246 $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS;
251 $hooks[
'BeforePageDisplay'][] = [ Hooks::class,
'onBeforePageDisplay' ];
254 $hooks[
'VisualEditorBeforeEditor'][] = [ Hooks::class,
'onVisualEditorBeforeEditor' ];
257 $hooks[
'MultiContentSave'][] = [ Hooks::class,
'tpSyntaxCheck' ];
258 $hooks[
'EditFilterMergedContent'][] =
259 [ Hooks::class,
'tpSyntaxCheckForEditContent' ];
262 $hooks[
'PageSaveComplete'][] = [ Hooks::class,
'addTranstagAfterSave' ];
264 $hooks[
'RevisionRecordInserted'][] = [ Hooks::class,
'updateTranstagOnNullRevisions' ];
267 $hooks[
'ParserFirstCallInit'][] = [ self::class,
'setupParserHooks' ];
268 $hooks[
'LanguageLinks'][] = [ Hooks::class,
'addLanguageLinks' ];
269 $hooks[
'SkinTemplateGetLanguageLink'][] = [ Hooks::class,
'formatLanguageLink' ];
272 $hooks[
'GetMagicVariableIDs'][] = [ Hooks::class,
'onGetMagicVariableIDs' ];
273 $hooks[
'ParserGetVariableValueSwitch'][] = [ Hooks::class,
'onParserGetVariableValueSwitch' ];
276 $hooks[
'ParserBeforeInternalParse'][] = [ Hooks::class,
'renderTagPage' ];
278 $hooks[
'ParserBeforePreprocess'][] = [ Hooks::class,
'preprocessTagPage' ];
279 $hooks[
'ParserOutputPostCacheTransform'][] =
280 [ Hooks::class,
'onParserOutputPostCacheTransform' ];
282 $hooks[
'BeforeParserFetchTemplateRevisionRecord'][] =
283 [ Hooks::class,
'fetchTranslatableTemplateAndTitle' ];
286 $hooks[
'PageContentLanguage'][] = [ Hooks::class,
'onPageContentLanguage' ];
289 $hooks[
'getUserPermissionsErrorsExpensive'][] =
290 [ Hooks::class,
'onGetUserPermissionsErrorsExpensive' ];
292 $hooks[
'getUserPermissionsErrorsExpensive'][] =
293 [ Hooks::class,
'preventDirectEditing' ];
296 $hooks[
'ArticleViewHeader'][] = [ Hooks::class,
'translatablePageHeader' ];
299 $hooks[
'TitleGetEditNotices'][] = [ Hooks::class,
'onTitleGetEditNotices' ];
302 $hooks[
'SpecialPage_initList'][] = [ Hooks::class,
'replaceMovePage' ];
304 $hooks[
'getUserPermissionsErrorsExpensive'][] =
305 [ Hooks::class,
'lockedPagesCheck' ];
307 $hooks[
'ArticleConfirmDelete'][] = [ Hooks::class,
'disableDelete' ];
310 $hooks[
'SkinSubPageSubtitle'][] = [ Hooks::class,
'replaceSubtitle' ];
313 $hooks[
'SkinTemplateNavigation::Universal'][] = [ Hooks::class,
'translateTab' ];
316 $hooks[
'PageMoveComplete'][] = [ Hooks::class,
'onMovePageTranslationUnits' ];
319 $hooks[
'ArticleDeleteComplete'][] = [ Hooks::class,
'onDeleteTranslationUnit' ];
322 $hooks[
'ReplaceTextFilterPageTitlesForEdit'][] = [ Hooks::class,
'onReplaceTextFilterPageTitlesForEdit' ];
324 $hooks[
'ReplaceTextFilterPageTitlesForRename'][] =
325 [ Hooks::class,
'onReplaceTextFilterPageTitlesForRename' ];
328 $hooks[
'LinksUpdateComplete'][] = [ Hooks::class,
'onLinksUpdateComplete' ];
331 global $wgTranslateUseSandbox;
332 if ( $wgTranslateUseSandbox ) {
333 global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions;
335 $wgSpecialPages[
'ManageTranslatorSandbox'] = [
336 'class' => ManageTranslatorSandboxSpecialPage::class,
338 'Translate:TranslationStashReader',
340 'Translate:TranslateSandbox',
344 return new ServiceOptions(
345 ManageTranslatorSandboxSpecialPage::CONSTRUCTOR_OPTIONS,
346 MediaWikiServices::getInstance()->getMainConfig()
351 $wgSpecialPages[
'TranslationStash'] = [
352 'class' => TranslationStashSpecialPage::class,
355 'Translate:TranslationStashReader',
361 return new ServiceOptions(
362 TranslationStashSpecialPage::CONSTRUCTOR_OPTIONS,
363 MediaWikiServices::getInstance()->getMainConfig()
368 $wgDefaultUserOptions[
'translate-sandbox'] =
'';
370 $wgAvailableRights[] =
'translate-sandboxmanage';
372 global $wgLogTypes, $wgLogActionsHandlers;
374 if ( !in_array(
'translatorsandbox', $wgLogTypes ) ) {
375 $wgLogTypes[] =
'translatorsandbox';
378 $wgLogActionsHandlers[
'translatorsandbox/promoted'] = TranslateLogFormatter::class;
379 $wgLogActionsHandlers[
'translatorsandbox/rejected'] = TranslateLogFormatter::class;
383 $wgLogActionsHandlers[
'newusers/tsbpromoted'] = LogFormatter::class;
385 $wgJobClasses[
'TranslateSandboxEmailJob'] = TranslateSandboxEmailJob::class;
387 global $wgAPIModules;
388 $wgAPIModules[
'translationstash'] = [
389 'class' => TranslationStashActionApi::class,
391 'DBLoadBalancerFactory',
393 'Translate:MessageIndex'
396 $wgAPIModules[
'translatesandbox'] = [
397 'class' => TranslatorSandboxActionApi::class,
401 'UserOptionsManager',
404 'Translate:TranslateSandbox',
408 return new ServiceOptions(
409 TranslatorSandboxActionApi::CONSTRUCTOR_OPTIONS,
410 MediaWikiServices::getInstance()->getMainConfig()
417 global $wgNamespaceRobotPolicies;
418 $wgNamespaceRobotPolicies[NS_TRANSLATIONS] =
'noindex';
421 global $wgTranslateTranslationDefaultService,
422 $wgTranslateTranslationServices;
423 if ( $wgTranslateTranslationDefaultService ===
true ) {
424 $wgTranslateTranslationDefaultService =
'TTMServer';
425 if ( !isset( $wgTranslateTranslationServices[
'TTMServer'] ) ) {
426 $wgTranslateTranslationServices[
'TTMServer'] = [
429 'type' =>
'ttmserver',
435 global $wgTranslateEnableMessageGroupSubscription;
436 if ( $wgTranslateEnableMessageGroupSubscription ) {
437 if ( !ExtensionRegistry::getInstance()->isLoaded(
'Echo' ) ) {
438 throw new ConfigException(
439 'Translate: Message group subscriptions (TranslateEnableMessageGroupSubscription) are ' .
440 'enabled but Echo extension is not installed'
443 MessageGroupSubscriptionHookHandler::registerHooks( $hooks );
444 $wgJobClasses[
'MessageGroupSubscriptionNotificationJob'] = MessageGroupSubscriptionNotificationJob::class;
447 global $wgTranslateEnableEventLogging;
448 if ( $wgTranslateEnableEventLogging ) {
449 if ( !ExtensionRegistry::getInstance()->isLoaded(
'EventLogging' ) ) {
450 throw new ConfigException(
451 'Translate: Event logging (TranslateEnableEventLogging) is ' .
452 'enabled but EventLogging extension is not installed'
457 global $wgTranslateEnableLuaIntegration;
458 if ( $wgTranslateEnableLuaIntegration ) {
459 if ( ExtensionRegistry::getInstance()->isLoaded(
'Scribunto' ) ) {
460 $hooks[
'ScribuntoExternalLibraries' ][] =
static function (
string $engine, array &$extraLibraries ) {
461 $scribuntoHookHandler =
new ScribuntoHookHandler();
462 $scribuntoHookHandler->onScribuntoExternalLibraries( $engine, $extraLibraries );
466 'Translate: Lua integration (TranslateEnableLuaIntegration) is ' .
467 'enabled but Scribunto extension is not installed'
472 static::registerHookHandlers( $hooks );
475 private static function registerHookHandlers( array $hooks ): void {
476 if ( defined(
'MW_PHPUNIT_TEST' ) && MediaWikiServices::hasInstance() ) {
479 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
480 foreach ( $hooks as $name => $handlers ) {
481 foreach ( $handlers as $h ) {
482 $hookContainer->register( $name, $h );
486 $settingsBuilder = SettingsBuilder::getInstance();
487 $settingsBuilder->registerHookHandlers( $hooks );
496 $reservedUsernames[] =
FuzzyBot::getName();
497 $reservedUsernames[] = TranslateUserManager::getName();
502 VariableHolder &$vars, Title $title, User $user
508 if ( $handle->isMessageNamespace() ) {
509 $vars->setLazyLoadVar(
510 'translate_source_text',
511 'translate-get-source',
512 [
'handle' => $handle ]
514 $vars->setLazyLoadVar(
515 'translate_target_language',
516 'translate-get-target-language',
517 [
'handle' => $handle ]
525 VariableHolder $vars,
529 if ( $method !==
'translate-get-source' && $method !==
'translate-get-target-language' ) {
533 $handle = $parameters[
'handle'];
535 if ( $handle->isValid() ) {
536 if ( $method ===
'translate-get-source' ) {
537 $group = $handle->getGroup();
538 $value = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
540 $value = $handle->getCode();
554 $builderValues[
'vars'][
'translate_source_text'] =
'translate-source-text';
555 $builderValues[
'vars'][
'translate_target_language'] =
'translate-target-language';
564 $parser->setHook(
'languages', [
Hooks::class,
'languages' ] );
575 if ( $handle->isMessageNamespace() ) {
585 global $wgTranslateDocumentationLanguageCode;
586 if ( $wgTranslateDocumentationLanguageCode ) {
589 $wgTranslateDocumentationLanguageCode === $code ||
595 $names[$wgTranslateDocumentationLanguageCode] =
596 wfMessage(
'translate-documentation-language' )->inLanguage( $code )->plain();
602 global $wgTranslateMessageNamespaces;
604 $insert[
'translation'] = [
605 'message' =>
'translate-searchprofile',
606 'tooltip' =>
'translate-searchprofile-tooltip',
607 'namespaces' => $wgTranslateMessageNamespaces,
611 $index = array_search(
'all', array_keys( $profiles ) );
614 if ( $index ===
false ) {
615 wfWarn(
'"all" not found in search profiles' );
616 $index = count( $profiles );
619 $profiles = array_merge(
620 array_slice( $profiles, 0, $index ),
622 array_slice( $profiles, $index )
628 SpecialSearch $search,
634 if ( $profile !==
'translation' ) {
638 if ( Services::getInstance()->getTtmServerFactory()->getDefaultForQuerying() instanceof
SearchableTtmServer ) {
639 $href = SpecialPage::getTitleFor(
'SearchTranslations' )
640 ->getFullUrl( [
'query' => $term ] );
641 $form = Html::successBox(
642 $search->msg(
'translate-searchprofile-note', $href )->parse(),
649 if ( !$search->getSearchEngine()->supports(
'title-suffix-filter' ) ) {
654 foreach ( $opts as $key => $value ) {
655 $hidden .= Html::hidden( $key, $value );
658 $context = $search->getContext();
659 $code = $context->getLanguage()->getCode();
660 $selected = $context->getRequest()->getVal(
'languagefilter' );
662 $languages = Utilities::getLanguageNames( $code );
665 $selector =
new XmlSelect(
'languagefilter',
'languagefilter' );
666 $selector->setDefault( $selected );
667 $selector->addOption( wfMessage(
'translate-search-nofilter' )->text(),
'-' );
668 foreach ( $languages as $code => $name ) {
669 $selector->addOption(
"$code - $name", $code );
672 $selector = $selector->getHTML();
674 $label = Html::label(
675 wfMessage(
'translate-search-languagefilter' )->text(),
679 $form .= Html::rawElement(
681 [
'id' =>
'mw-searchoptions' ],
682 $hidden . $label . $selector
688 SpecialSearch $search,
692 if ( $profile !==
'translation' ) {
696 $context = $search->getContext();
697 $selected = $context->getRequest()->getVal(
'languagefilter' );
698 if ( $selected !==
'-' && $selected ) {
699 $engine->setFeatureData(
'title-suffix-filter',
"/$selected" );
700 $search->setExtraParam(
'languagefilter', $selected );
706 if ( $parser->getOptions()->getInterfaceMessage() ) {
709 $pageReference = $parser->getPage();
710 if ( !$pageReference ) {
714 $linkTarget = TitleValue::newFromPage( $pageReference );
715 $handle =
new MessageHandle( $linkTarget );
716 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
717 $parserOutput = $parser->getOutput();
718 $names = $parserOutput->getCategoryNames();
719 $parserCategories = [];
720 foreach ( $names as $name ) {
721 $parserCategories[$name] = $parserOutput->getCategorySortKey( $name );
723 $parserOutput->setExtensionData(
'translate-fake-categories', $parserCategories );
724 $parserOutput->setCategories( [] );
729 public static function showFakeCategories( OutputPage $outputPage, ParserOutput $parserOutput ): void {
730 $fakeCategories = $parserOutput->getExtensionData(
'translate-fake-categories' );
731 if ( $fakeCategories ) {
732 $outputPage->addCategoryLinks( $fakeCategories );
741 public static function addConfig( array &$vars, OutputPage $out ): void {
742 global $wgTranslateDocumentationLanguageCode,
743 $wgTranslatePermissionUrl,
744 $wgTranslateUseSandbox;
746 $title = $out->getTitle();
747 if ( $title->isSpecial(
'Translate' ) ||
748 $title->isSpecial(
'TranslationStash' ) ||
749 $title->isSpecial(
'SearchTranslations' )
751 $user = $out->getUser();
752 $vars[
'TranslateRight'] = $user->isAllowed(
'translate' );
753 $vars[
'TranslateMessageReviewRight'] = $user->isAllowed(
'translate-messagereview' );
754 $vars[
'DeleteRight'] = $user->isAllowed(
'delete' );
755 $vars[
'TranslateManageRight'] = $user->isAllowed(
'translate-manage' );
756 $vars[
'wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode;
757 $vars[
'wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl;
758 $vars[
'wgTranslateUseSandbox'] = $wgTranslateUseSandbox;
764 global $wgTranslateUseSandbox;
766 if ( $wgTranslateUseSandbox ) {
767 $sectionLabel = wfMessage(
'adminlinks_users' )->text();
768 $row = $tree->getSection( $sectionLabel )->getRow(
'main' );
769 $row->addItem( ALItem::newFromSpecialPage(
'TranslateSandbox' ) );
778 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
782 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
783 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
784 $dbw->newUpdateQueryBuilder()
787 ->set( [ $field => $newUser->getId() ] )
788 ->where( [ $field => $oldUser->getId() ] )
789 ->caller( __METHOD__ )
800 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
803 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
804 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
805 $dbw->newDeleteQueryBuilder()
806 ->deleteFrom( $table )
807 ->where( [ $field => $oldUser->getId() ] )
808 ->caller( __METHOD__ )
820 return $rc->getAttribute(
'rc_log_type' ) !==
'translationreview';
832 if ( !$target->inNamespace( NS_SPECIAL ) ) {
836 [ $name, $subpage ] = MediaWikiServices::getInstance()
837 ->getSpecialPageFactory()->resolveAlias( $target->getDBkey() );
838 if ( $name !==
'MyLanguage' || (
string)$subpage ===
'' ) {
842 $realTarget = Title::newFromText( $subpage );
843 if ( !$realTarget || !$realTarget->exists() ) {
852 public function onParserFirstCallInit( $parser ) {
853 $parser->setFunctionHook(
'translation', [ $this,
'translateRenderParserFunction' ] );
856 public function translateRenderParserFunction( Parser $parser ): string {
857 if ( $parser->getOptions()->getInterfaceMessage() ) {
860 $pageReference = $parser->getPage();
861 if ( !$pageReference ) {
864 $linkTarget = TitleValue::newFromPage( $pageReference );
865 $handle =
new MessageHandle( $linkTarget );
866 $code = $handle->getCode();
867 if ( $this->languageNameUtils->isKnownLanguageTag( $code ) ) {
879 IContextSource $context,
885 if ( !$content instanceof TextContent ) {
890 $text = $content->getText();
891 $title = $context->getTitle();
894 if ( !$handle->isValid() ) {
899 if ( $user->isAllowed(
'translate-manage' ) || $user->equals( FuzzyBot::getUser() ) ) {
904 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
905 $group = $handle->getGroup();
907 if ( method_exists( $group,
'getMessageContent' ) ) {
909 $definition = $group->getMessageContent( $handle );
911 $definition = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
914 $message =
new FatMessage( $handle->getKey(), $definition );
915 $message->setTranslation( $text );
917 $messageValidator = $group->getValidator();
918 if ( !$messageValidator ) {
922 $validationResponse = $messageValidator->validateMessage( $message, $handle->getCode() );
923 if ( $validationResponse->hasErrors() ) {
924 $status->fatal(
new ApiRawMessage(
925 $context->msg(
'translate-syntax-error' )->parse(),
926 'translate-validation-failed',
929 'errors' => $validationResponse->getDescriptiveErrors( $context ),
930 'warnings' => $validationResponse->getDescriptiveWarnings( $context )
943 $parentId = $revisionRecord->getParentId();
944 if ( $parentId === 0 || $parentId ===
null ) {
949 $prevRev = $this->revisionLookup->getRevisionById( $parentId );
950 if ( !$prevRev || !$revisionRecord->hasSameContent( $prevRev ) ) {
957 $tagsToCopy = [ RevTagStore::FUZZY_TAG, RevTagStore::TRANSVER_PROP ];
959 $db = $this->loadBalancer->getConnection( DB_PRIMARY );
964 'rt_type' =>
'rt_type',
965 'rt_page' =>
'rt_page',
966 'rt_revision' => $revisionRecord->getId(),
967 'rt_value' =>
'rt_value',
971 'rt_type' => $tagsToCopy,
972 'rt_revision' => $parentId,
980 $tags[] =
'translate-translation-pages';
985 if ( $this->config->get(
'EnablePageTranslation' ) ) {
986 $tags[] =
'translate-translation-pages';
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, '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(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), 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:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, '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->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $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(LogNames::GROUP_SUBSCRIPTION), 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->getConnectionProvider());}, '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(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), 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->getConnectionProvider(), $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->getConnectionProvider());}, '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->getConnectionProvider());}, '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(), $services->getFormatterFactory());}, '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->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $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'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory());}, '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->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $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 { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, '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.