2declare( strict_types=1 );
4namespace MediaWiki\Extension\Translate;
14use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
15use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
16use MediaWiki\Config\ServiceOptions;
17use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
40use MediaWiki\Html\Html;
41use MediaWiki\MediaWikiServices;
42use MediaWiki\Revision\Hook\RevisionRecordInsertedHook;
43use MediaWiki\Revision\RevisionLookup;
44use MediaWiki\Settings\SettingsBuilder;
45use MediaWiki\StubObject\StubUserLang;
61use Wikimedia\Rdbms\ILoadBalancer;
75class HookHandler implements RevisionRecordInsertedHook, ListDefinedTagsHook, ChangeTagsListActiveHook {
80 private const USER_MERGE_TABLES = [
81 'translate_stash' =>
'ts_user',
82 'translate_reviews' =>
'trr_user',
84 private RevisionLookup $revisionLookup;
85 private ILoadBalancer $loadBalancer;
86 private Config $config;
88 public function __construct(
89 RevisionLookup $revisionLookup,
90 ILoadBalancer $loadBalancer,
93 $this->revisionLookup = $revisionLookup;
94 $this->loadBalancer = $loadBalancer;
95 $this->config = $config;
100 global $wgTranslateYamlLibrary;
107 if ( !defined(
'TRANSLATE_FUZZY' ) ) {
108 define(
'TRANSLATE_FUZZY',
'!!FUZZY!!' );
111 if ( $wgTranslateYamlLibrary ===
null ) {
112 $wgTranslateYamlLibrary = function_exists(
'yaml_parse' ) ?
'phpyaml' :
'spyc';
115 $hooks[
'PageSaveComplete'][] = [ TranslateEditAddons::class,
'onSaveComplete' ];
118 global $wgEnablePageTranslation;
119 if ( $wgEnablePageTranslation ) {
121 global $wgSpecialPages, $wgAvailableRights;
122 $wgSpecialPages[
'PageTranslation'] = [
123 'class' => PageTranslationSpecialPage::class,
127 'Translate:TranslationUnitStoreFactory',
128 'Translate:TranslatablePageParser',
132 'Translate:MessageIndex',
136 $wgSpecialPages[
'PageTranslationDeletePage'] = [
137 'class' => DeleteTranslatableBundleSpecialPage::class,
141 'Translate:TranslatableBundleFactory',
142 'Translate:SubpageListBuilder',
148 $wgAvailableRights[] =
'pagetranslation';
150 $wgSpecialPages[
'PageMigration'] = MigrateTranslatablePageSpecialPage::class;
151 $wgSpecialPages[
'PagePreparation'] = PrepareTranslatablePageSpecialPage::class;
153 global $wgActionFilteredLogs, $wgLogActionsHandlers, $wgLogTypes;
162 $wgLogTypes[] =
'pagetranslation';
163 $wgLogActionsHandlers[
'pagetranslation/mark'] = TranslatableBundleLogFormatter::class;
164 $wgLogActionsHandlers[
'pagetranslation/unmark'] = TranslatableBundleLogFormatter::class;
165 $wgLogActionsHandlers[
'pagetranslation/moveok'] = TranslatableBundleLogFormatter::class;
166 $wgLogActionsHandlers[
'pagetranslation/movenok'] = TranslatableBundleLogFormatter::class;
167 $wgLogActionsHandlers[
'pagetranslation/deletelok'] = TranslatableBundleLogFormatter::class;
168 $wgLogActionsHandlers[
'pagetranslation/deletefok'] = TranslatableBundleLogFormatter::class;
169 $wgLogActionsHandlers[
'pagetranslation/deletelnok'] = TranslatableBundleLogFormatter::class;
170 $wgLogActionsHandlers[
'pagetranslation/deletefnok'] = TranslatableBundleLogFormatter::class;
171 $wgLogActionsHandlers[
'pagetranslation/encourage'] = TranslatableBundleLogFormatter::class;
172 $wgLogActionsHandlers[
'pagetranslation/discourage'] = TranslatableBundleLogFormatter::class;
173 $wgLogActionsHandlers[
'pagetranslation/prioritylanguages'] = TranslatableBundleLogFormatter::class;
174 $wgLogActionsHandlers[
'pagetranslation/associate'] = TranslatableBundleLogFormatter::class;
175 $wgLogActionsHandlers[
'pagetranslation/dissociate'] = TranslatableBundleLogFormatter::class;
176 $wgActionFilteredLogs[
'pagetranslation'] = [
177 'mark' => [
'mark' ],
178 'unmark' => [
'unmark' ],
179 'move' => [
'moveok',
'movenok' ],
180 'delete' => [
'deletefok',
'deletefnok',
'deletelok',
'deletelnok' ],
181 'encourage' => [
'encourage' ],
182 'discourage' => [
'discourage' ],
183 'prioritylanguages' => [
'prioritylanguages' ],
184 'aggregategroups' => [
'associate',
'dissociate' ],
187 $wgLogTypes[] =
'messagebundle';
188 $wgLogActionsHandlers[
'messagebundle/moveok'] = TranslatableBundleLogFormatter::class;
189 $wgLogActionsHandlers[
'messagebundle/movenok'] = TranslatableBundleLogFormatter::class;
190 $wgLogActionsHandlers[
'messagebundle/deletefok'] = TranslatableBundleLogFormatter::class;
191 $wgLogActionsHandlers[
'messagebundle/deletefnok'] = TranslatableBundleLogFormatter::class;
192 $wgActionFilteredLogs[
'messagebundle'] = [
193 'move' => [
'moveok',
'movenok' ],
194 'delete' => [
'deletefok',
'deletefnok' ],
197 $wgLogActionsHandlers[
'import/translatable-bundle'] = TranslatableBundleLogFormatter::class;
199 global $wgJobClasses;
200 $wgJobClasses[
'RenderTranslationPageJob'] = RenderTranslationPageJob::class;
201 $wgJobClasses[
'MoveTranslatableBundleJob'] = MoveTranslatableBundleJob::class;
202 $wgJobClasses[
'DeleteTranslatableBundleJob'] = DeleteTranslatableBundleJob::class;
203 $wgJobClasses[
'UpdateTranslatablePageJob'] = UpdateTranslatablePageJob::class;
206 global $wgNamespacesWithSubpages, $wgNamespaceProtection;
207 global $wgTranslateMessageNamespaces;
209 $wgNamespacesWithSubpages[NS_TRANSLATIONS] =
true;
210 $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] =
true;
213 $wgNamespaceProtection[NS_TRANSLATIONS] = [
'translate' ];
214 $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS;
219 $hooks[
'BeforePageDisplay'][] = [ Hooks::class,
'onBeforePageDisplay' ];
222 $hooks[
'VisualEditorBeforeEditor'][] = [ Hooks::class,
'onVisualEditorBeforeEditor' ];
225 $hooks[
'MultiContentSave'][] = [ Hooks::class,
'tpSyntaxCheck' ];
226 $hooks[
'EditFilterMergedContent'][] =
227 [ Hooks::class,
'tpSyntaxCheckForEditContent' ];
230 $hooks[
'PageSaveComplete'][] = [ Hooks::class,
'addTranstagAfterSave' ];
232 $hooks[
'RevisionRecordInserted'][] = [ Hooks::class,
'updateTranstagOnNullRevisions' ];
235 $hooks[
'ParserFirstCallInit'][] = [ self::class,
'setupParserHooks' ];
236 $hooks[
'LanguageLinks'][] = [ Hooks::class,
'addLanguageLinks' ];
237 $hooks[
'SkinTemplateGetLanguageLink'][] = [ Hooks::class,
'formatLanguageLink' ];
240 $hooks[
'GetMagicVariableIDs'][] = [ Hooks::class,
'onGetMagicVariableIDs' ];
241 $hooks[
'ParserGetVariableValueSwitch'][] = [ Hooks::class,
'onParserGetVariableValueSwitch' ];
244 $hooks[
'ParserBeforeInternalParse'][] = [ Hooks::class,
'renderTagPage' ];
246 $hooks[
'ParserBeforePreprocess'][] = [ Hooks::class,
'preprocessTagPage' ];
247 $hooks[
'ParserOutputPostCacheTransform'][] =
248 [ Hooks::class,
'onParserOutputPostCacheTransform' ];
250 $hooks[
'BeforeParserFetchTemplateRevisionRecord'][] =
251 [ Hooks::class,
'fetchTranslatableTemplateAndTitle' ];
254 $hooks[
'PageContentLanguage'][] = [ Hooks::class,
'onPageContentLanguage' ];
257 $hooks[
'getUserPermissionsErrorsExpensive'][] =
258 [ Hooks::class,
'onGetUserPermissionsErrorsExpensive' ];
260 $hooks[
'getUserPermissionsErrorsExpensive'][] =
261 [ Hooks::class,
'preventDirectEditing' ];
264 $hooks[
'ArticleViewHeader'][] = [ Hooks::class,
'translatablePageHeader' ];
267 $hooks[
'TitleGetEditNotices'][] = [ Hooks::class,
'onTitleGetEditNotices' ];
270 $hooks[
'SpecialPage_initList'][] = [ Hooks::class,
'replaceMovePage' ];
272 $hooks[
'getUserPermissionsErrorsExpensive'][] =
273 [ Hooks::class,
'lockedPagesCheck' ];
275 $hooks[
'ArticleConfirmDelete'][] = [ Hooks::class,
'disableDelete' ];
278 $hooks[
'SkinSubPageSubtitle'][] = [ Hooks::class,
'replaceSubtitle' ];
281 $hooks[
'SkinTemplateNavigation::Universal'][] = [ Hooks::class,
'translateTab' ];
284 $hooks[
'PageMoveComplete'][] = [ Hooks::class,
'onMovePageTranslationUnits' ];
287 $hooks[
'ArticleDeleteComplete'][] = [ Hooks::class,
'onDeleteTranslationUnit' ];
290 $hooks[
'ReplaceTextFilterPageTitlesForEdit'][] = [ Hooks::class,
'onReplaceTextFilterPageTitlesForEdit' ];
292 $hooks[
'ReplaceTextFilterPageTitlesForRename'][] =
293 [ Hooks::class,
'onReplaceTextFilterPageTitlesForRename' ];
296 global $wgTranslateUseSandbox;
297 if ( $wgTranslateUseSandbox ) {
298 global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions;
300 $wgSpecialPages[
'ManageTranslatorSandbox'] = [
301 'class' => ManageTranslatorSandboxSpecialPage::class,
303 'Translate:TranslationStashReader',
308 return new ServiceOptions(
309 ManageTranslatorSandboxSpecialPage::CONSTRUCTOR_OPTIONS,
310 MediaWikiServices::getInstance()->getMainConfig()
315 $wgSpecialPages[
'TranslationStash'] = [
316 'class' => TranslationStashSpecialPage::class,
319 'Translate:TranslationStashReader',
325 return new ServiceOptions(
326 TranslationStashSpecialPage::CONSTRUCTOR_OPTIONS,
327 MediaWikiServices::getInstance()->getMainConfig()
332 $wgDefaultUserOptions[
'translate-sandbox'] =
'';
334 $wgAvailableRights[] =
'translate-sandboxmanage';
336 $hooks[
'GetPreferences'][] = [ TranslateSandbox::class,
'onGetPreferences' ];
337 $hooks[
'UserGetRights'][] = [ TranslateSandbox::class,
'enforcePermissions' ];
338 $hooks[
'ApiCheckCanExecute'][] = [ TranslateSandbox::class,
'onApiCheckCanExecute' ];
340 global $wgLogTypes, $wgLogActionsHandlers;
342 $wgLogTypes[] =
'translatorsandbox';
344 $wgLogActionsHandlers[
'translatorsandbox/promoted'] =
'TranslateLogFormatter';
345 $wgLogActionsHandlers[
'translatorsandbox/rejected'] =
'TranslateLogFormatter';
349 $wgLogActionsHandlers[
'newusers/tsbpromoted'] =
'LogFormatter';
351 global $wgJobClasses;
352 $wgJobClasses[
'TranslateSandboxEmailJob'] =
'TranslateSandboxEmailJob';
354 global $wgAPIModules;
355 $wgAPIModules[
'translationstash'] = [
356 'class' => TranslationStashActionApi::class,
362 $wgAPIModules[
'translatesandbox'] = [
363 'class' => TranslatorSandboxActionApi::class,
367 'UserOptionsManager',
373 return new ServiceOptions(
374 TranslatorSandboxActionApi::CONSTRUCTOR_OPTIONS,
375 MediaWikiServices::getInstance()->getMainConfig()
382 global $wgNamespaceRobotPolicies;
383 $wgNamespaceRobotPolicies[NS_TRANSLATIONS] =
'noindex';
386 global $wgTranslateTranslationDefaultService,
387 $wgTranslateTranslationServices;
388 if ( $wgTranslateTranslationDefaultService ===
true ) {
389 $wgTranslateTranslationDefaultService =
'TTMServer';
390 if ( !isset( $wgTranslateTranslationServices[
'TTMServer'] ) ) {
391 $wgTranslateTranslationServices[
'TTMServer'] = [
394 'type' =>
'ttmserver',
400 $hooks[
'SidebarBeforeOutput'][] = [ TranslateToolbox::class,
'toolboxAllTranslations' ];
402 static::registerHookHandlers( $hooks );
405 private static function registerHookHandlers( array $hooks ): void {
406 if ( defined(
'MW_PHPUNIT_TEST' ) && MediaWikiServices::hasInstance() ) {
409 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
410 foreach ( $hooks as $name => $handlers ) {
411 foreach ( $handlers as $h ) {
412 $hookContainer->register( $name, $h );
415 } elseif ( method_exists( SettingsBuilder::class,
'registerHookHandlers' ) ) {
418 $settingsBuilder = SettingsBuilder::getInstance();
419 $settingsBuilder->registerHookHandlers( $hooks );
422 foreach ( $hooks as $name => $handlers ) {
423 $GLOBALS[
'wgHooks'][$name] = array_merge(
424 $GLOBALS[
'wgHooks'][$name] ?? [],
437 $names[] = TranslateUserManager::getName();
442 VariableHolder &$vars, Title $title, User $user
448 if ( $handle->isMessageNamespace() ) {
449 $vars->setLazyLoadVar(
450 'translate_source_text',
451 'translate-get-source',
452 [
'handle' => $handle ]
454 $vars->setLazyLoadVar(
455 'translate_target_language',
456 'translate-get-target-language',
457 [
'handle' => $handle ]
465 VariableHolder $vars,
469 if ( $method !==
'translate-get-source' && $method !==
'translate-get-target-language' ) {
473 $handle = $parameters[
'handle'];
475 if ( $handle->isValid() ) {
476 if ( $method ===
'translate-get-source' ) {
477 $group = $handle->getGroup();
478 $value = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
480 $value = $handle->getCode();
493 $builderValues[
'vars'][
'translate_source_text'] =
'translate-source-text';
494 $builderValues[
'vars'][
'translate_target_language'] =
'translate-target-language';
503 $parser->setHook(
'languages', [
Hooks::class,
'languages' ] );
508 $dir = dirname( __DIR__, 1 ) .
'/sql';
509 $dbType = $updater->getDB()->getType();
511 if ( $dbType ===
'mysql' || $dbType ===
'sqlite' ) {
512 $updater->addExtensionTable(
513 'translate_sections',
514 "{$dir}/{$dbType}/translate_sections.sql"
516 $updater->addExtensionTable(
518 "{$dir}/{$dbType}/revtag.sql"
520 $updater->addExtensionTable(
521 'translate_groupstats',
522 "{$dir}/{$dbType}/translate_groupstats.sql"
524 $updater->addExtensionTable(
526 "{$dir}/{$dbType}/translate_reviews.sql"
528 $updater->addExtensionTable(
529 'translate_groupreviews',
530 "{$dir}/{$dbType}/translate_groupreviews.sql"
532 $updater->addExtensionTable(
534 "{$dir}/{$dbType}/translate_tm.sql"
536 $updater->addExtensionTable(
537 'translate_metadata',
538 "{$dir}/{$dbType}/translate_metadata.sql"
540 $updater->addExtensionTable(
541 'translate_messageindex',
542 "{$dir}/{$dbType}/translate_messageindex.sql"
544 $updater->addExtensionTable(
546 "{$dir}/{$dbType}/translate_stash.sql"
548 $updater->addExtensionTable(
549 'translate_translatable_bundles',
550 "{$dir}/{$dbType}/translate_translatable_bundles.sql"
554 $updater->addExtensionUpdate( [
557 'trr_user_page_revision',
560 "$dir/translate_reviews-patch-01-primary-key.sql",
564 $updater->addExtensionTable(
566 "{$dir}/{$dbType}/translate_cache.sql"
569 if ( $dbType ===
'mysql' ) {
571 $updater->modifyExtensionField(
574 "{$dir}/{$dbType}/translate_cache-alter-varbinary.sql"
577 } elseif ( $dbType ===
'postgres' ) {
578 $updater->addExtensionTable(
579 'translate_sections',
580 "{$dir}/{$dbType}/tables-generated.sql"
582 $updater->addExtensionUpdate( [
583 'changeField',
'translate_cache',
'tc_exptime',
'TIMESTAMPTZ',
'th_timestamp::timestamp with time zone'
588 $updater->dropExtensionIndex(
589 'translate_messageindex',
591 "{$dir}/{$dbType}/patch-translate_messageindex-unique-to-pk.sql"
593 $updater->dropExtensionIndex(
596 "{$dir}/{$dbType}/patch-translate_tmt-unique-to-pk.sql"
598 $updater->dropExtensionIndex(
600 'rt_type_page_revision',
601 "{$dir}/{$dbType}/patch-revtag-unique-to-pk.sql"
604 $updater->addPostDatabaseUpdateMaintenance( SyncTranslatableBundleStatusMaintenanceScript::class );
615 if ( $handle->isMessageNamespace() ) {
625 global $wgTranslateDocumentationLanguageCode;
626 if ( $wgTranslateDocumentationLanguageCode ) {
629 $wgTranslateDocumentationLanguageCode === $code ||
635 $names[$wgTranslateDocumentationLanguageCode] =
636 wfMessage(
'translate-documentation-language' )->inLanguage( $code )->plain();
642 global $wgTranslateMessageNamespaces;
644 $insert[
'translation'] = [
645 'message' =>
'translate-searchprofile',
646 'tooltip' =>
'translate-searchprofile-tooltip',
647 'namespaces' => $wgTranslateMessageNamespaces,
651 $index = array_search(
'all', array_keys( $profiles ) );
654 if ( $index ===
false ) {
655 wfWarn(
'"all" not found in search profiles' );
656 $index = count( $profiles );
659 $profiles = array_merge(
660 array_slice( $profiles, 0, $index ),
662 array_slice( $profiles, $index )
668 SpecialSearch $search,
674 if ( $profile !==
'translation' ) {
678 if ( Services::getInstance()->getTtmServerFactory()->getDefaultForQuerying() instanceof
SearchableTtmServer ) {
679 $href = SpecialPage::getTitleFor(
'SearchTranslations' )
680 ->getFullUrl( [
'query' => $term ] );
681 $form = Html::successBox(
682 $search->msg(
'translate-searchprofile-note', $href )->parse(),
689 if ( !$search->getSearchEngine()->supports(
'title-suffix-filter' ) ) {
694 foreach ( $opts as $key => $value ) {
695 $hidden .= Html::hidden( $key, $value );
698 $context = $search->getContext();
699 $code = $context->getLanguage()->getCode();
700 $selected = $context->getRequest()->getVal(
'languagefilter' );
702 $languages = Utilities::getLanguageNames( $code );
705 $selector =
new XmlSelect(
'languagefilter',
'languagefilter' );
706 $selector->setDefault( $selected );
707 $selector->addOption( wfMessage(
'translate-search-nofilter' )->text(),
'-' );
708 foreach ( $languages as $code => $name ) {
709 $selector->addOption(
"$code - $name", $code );
712 $selector = $selector->getHTML();
715 wfMessage(
'translate-search-languagefilter' )->text(),
718 $params = [
'id' =>
'mw-searchoptions' ];
720 $form = Xml::fieldset(
false,
false, $params ) .
721 $hidden . $label . $selector .
722 Html::closeElement(
'fieldset' );
729 SpecialSearch $search,
733 if ( $profile !==
'translation' ) {
737 $context = $search->getContext();
738 $selected = $context->getRequest()->getVal(
'languagefilter' );
739 if ( $selected !==
'-' && $selected ) {
740 $engine->setFeatureData(
'title-suffix-filter',
"/$selected" );
741 $search->setExtraParam(
'languagefilter', $selected );
747 $pageReference = $parser->getPage();
748 if ( !$pageReference ) {
752 $linkTarget = TitleValue::newFromPage( $pageReference );
754 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
755 $parserOutput = $parser->getOutput();
757 if ( method_exists( $parserOutput,
'getCategorySortKey' ) ) {
758 $names = $parserOutput->getCategoryNames();
759 $parserCategories = [];
760 foreach ( $names as $name ) {
761 $parserCategories[$name] = $parserOutput->getCategorySortKey( $name );
763 $parserOutput->setExtensionData(
'translate-fake-categories', $parserCategories );
765 $parserOutput->setExtensionData(
'translate-fake-categories',
766 $parserOutput->getCategories() );
768 $parserOutput->setCategories( [] );
773 public static function showFakeCategories( OutputPage $outputPage, ParserOutput $parserOutput ): void {
774 $fakeCategories = $parserOutput->getExtensionData(
'translate-fake-categories' );
775 if ( $fakeCategories ) {
776 $outputPage->setCategoryLinks( $fakeCategories );
785 public static function addConfig( array &$vars, OutputPage $out ): void {
786 global $wgTranslateDocumentationLanguageCode,
787 $wgTranslatePermissionUrl,
788 $wgTranslateUseSandbox;
790 $title = $out->getTitle();
791 if ( $title->isSpecial(
'Translate' ) ||
792 $title->isSpecial(
'TranslationStash' ) ||
793 $title->isSpecial(
'SearchTranslations' )
795 $user = $out->getUser();
796 $vars[
'TranslateRight'] = $user->isAllowed(
'translate' );
797 $vars[
'TranslateMessageReviewRight'] = $user->isAllowed(
'translate-messagereview' );
798 $vars[
'DeleteRight'] = $user->isAllowed(
'delete' );
799 $vars[
'TranslateManageRight'] = $user->isAllowed(
'translate-manage' );
800 $vars[
'wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode;
801 $vars[
'wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl;
802 $vars[
'wgTranslateUseSandbox'] = $wgTranslateUseSandbox;
808 global $wgTranslateUseSandbox;
810 if ( $wgTranslateUseSandbox ) {
811 $sectionLabel = wfMessage(
'adminlinks_users' )->text();
812 $row = $tree->getSection( $sectionLabel )->getRow(
'main' );
813 $row->addItem( ALItem::newFromSpecialPage(
'TranslateSandbox' ) );
822 $dbw = MediawikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
826 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
827 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
830 [ $field => $newUser->getId() ],
831 [ $field => $oldUser->getId() ],
844 $dbw = MediawikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
847 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
848 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
851 [ $field => $oldUser->getId() ],
864 return $rc->getAttribute(
'rc_log_type' ) !==
'translationreview';
876 if ( !$target->inNamespace( NS_SPECIAL ) ) {
880 [ $name, $subpage ] = MediaWikiServices::getInstance()
881 ->getSpecialPageFactory()->resolveAlias( $target->getDBkey() );
882 if ( $name !==
'MyLanguage' || (
string)$subpage ===
'' ) {
886 $realTarget = Title::newFromText( $subpage );
887 if ( !$realTarget || !$realTarget->exists() ) {
898 $parser->setFunctionHook(
'translation', [ self::class,
'translateRenderParserFunction' ] );
901 public static function translateRenderParserFunction( Parser $parser ): string {
902 $pageReference = $parser->getPage();
903 if ( !$pageReference ) {
906 $linkTarget = TitleValue::newFromPage( $pageReference );
908 $code = $handle->getCode();
909 if ( MediaWikiServices::getInstance()->getLanguageNameUtils()->isKnownLanguageTag( $code ) ) {
921 IContextSource $context,
927 if ( !$content instanceof TextContent ) {
932 $text = $content->getText();
933 $title = $context->getTitle();
936 if ( !$handle->isValid() ) {
941 if ( $user->isAllowed(
'translate-manage' ) || $user->equals( FuzzyBot::getUser() ) ) {
946 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
947 $group = $handle->getGroup();
949 if ( method_exists( $group,
'getMessageContent' ) ) {
951 $definition = $group->getMessageContent( $handle );
953 $definition = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
956 $message =
new FatMessage( $handle->getKey(), $definition );
957 $message->setTranslation( $text );
959 $messageValidator = $group->getValidator();
960 if ( !$messageValidator ) {
964 $validationResponse = $messageValidator->validateMessage( $message, $handle->getCode() );
965 if ( $validationResponse->hasErrors() ) {
966 $status->fatal(
new ApiRawMessage(
967 $context->msg(
'translate-syntax-error' )->parse(),
968 'translate-validation-failed',
971 'errors' => $validationResponse->getDescriptiveErrors( $context ),
972 'warnings' => $validationResponse->getDescriptiveWarnings( $context )
985 $parentId = $revisionRecord->getParentId();
986 if ( $parentId === 0 || $parentId ===
null ) {
991 $prevRev = $this->revisionLookup->getRevisionById( $parentId );
992 if ( !$prevRev || !$revisionRecord->hasSameContent( $prevRev ) ) {
999 $tagsToCopy = [ RevTagStore::FUZZY_TAG, RevTagStore::TRANSVER_PROP ];
1001 $db = $this->loadBalancer->getConnection( DB_PRIMARY );
1006 'rt_type' =>
'rt_type',
1007 'rt_page' =>
'rt_page',
1008 'rt_revision' => $revisionRecord->getId(),
1009 'rt_value' =>
'rt_value',
1013 'rt_type' => $tagsToCopy,
1014 'rt_revision' => $parentId,
1022 $tags[] =
'translate-translation-pages';
1027 if ( $this->config->get(
'EnablePageTranslation' ) ) {
1028 $tags[] =
'translate-translation-pages';
return[ '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:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, '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:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, '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->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, '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'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, '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());}, '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->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:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, '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'),);}, '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
Script to identify the status of the translatable bundles in the rev_tag table and update them in the...
Special page which enables deleting translations of translatable bundles and translation pages.
Hooks for page 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.
Class for pointing to messages, like Title class is for titles.
getEffectiveLanguage()
Return the Language object for the assumed language of the content, which might be different from the...
Utility class for the sandbox feature of Translate.