'ts_user', 'translate_reviews' => 'trr_user', ]; /** * Hook: ResourceLoaderTestModules * @param array &$modules */ public static function onResourceLoaderTestModules( array &$modules ) { $modules['qunit']['ext.translate.parsers.test'] = [ 'scripts' => [ 'tests/qunit/ext.translate.parsers.test.js' ], 'dependencies' => [ 'ext.translate.parsers' ], 'localBasePath' => __DIR__, 'remoteExtPath' => 'Translate', ]; $modules['qunit']['ext.translate.special.pagemigration.test'] = [ 'scripts' => [ 'tests/qunit/ext.translate.special.pagemigration.test.js' ], 'dependencies' => [ 'ext.translate.special.pagemigration' ], 'localBasePath' => __DIR__, 'remoteExtPath' => 'Translate', ]; } /** * Initialises the extension. * Does late-initialization that is not possible at file level, * because it depends on user configuration. */ public static function setupTranslate() { global $wgPageTranslationNamespace; if ( isset( $wgPageTranslationNamespace ) && $wgPageTranslationNamespace !== NS_TRANSLATIONS ) { throw new MWException( '$wgPageTranslationNamespace is no longer supported. Instead, define ' . 'NS_TRANSLATIONS and NS_TRANSLATIONS_TALK in LocalSettings.php before loading ' . 'Translate.' ); } global $wgTranslatePHPlot, $wgAutoloadClasses, $wgHooks; if ( $wgTranslatePHPlot ) { $wgAutoloadClasses['PHPlot'] = $wgTranslatePHPlot; } // Page translation setup check and init if enabled. global $wgEnablePageTranslation; if ( $wgEnablePageTranslation ) { // Special page and the right to use it global $wgSpecialPages, $wgAvailableRights; $wgSpecialPages['PageTranslation'] = 'SpecialPageTranslation'; $wgSpecialPages['PageTranslationDeletePage'] = 'SpecialPageTranslationDeletePage'; // right-pagetranslation action-pagetranslation $wgAvailableRights[] = 'pagetranslation'; $wgSpecialPages['PageMigration'] = 'SpecialPageMigration'; $wgSpecialPages['PagePreparation'] = 'SpecialPagePreparation'; global $wgActionFilteredLogs, $wgLogActionsHandlers, $wgLogTypes; // log-description-pagetranslation log-name-pagetranslation logentry-pagetranslation-mark // logentry-pagetranslation-unmark logentry-pagetranslation-moveok // logentry-pagetranslation-movenok logentry-pagetranslation-deletefok // logentry-pagetranslation-deletefnok logentry-pagetranslation-deletelok // logentry-pagetranslation-deletelnok logentry-pagetranslation-encourage // logentry-pagetranslation-discourage logentry-pagetranslation-prioritylanguages // logentry-pagetranslation-associate logentry-pagetranslation-dissociate $wgLogTypes[] = 'pagetranslation'; $wgLogActionsHandlers['pagetranslation/mark'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/unmark'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/moveok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/movenok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/deletelok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/deletefok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/deletelnok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/deletefnok'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/encourage'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/discourage'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/prioritylanguages'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/associate'] = 'PageTranslationLogFormatter'; $wgLogActionsHandlers['pagetranslation/dissociate'] = 'PageTranslationLogFormatter'; $wgActionFilteredLogs['pagetranslation'] = [ 'mark' => [ 'mark' ], 'unmark' => [ 'unmark' ], 'move' => [ 'moveok', 'movenok' ], 'delete' => [ 'deletefok', 'deletefnok', 'deletelok', 'deletelnok' ], 'encourage' => [ 'encourage' ], 'discourage' => [ 'discourage' ], 'prioritylanguages' => [ 'prioritylanguages' ], 'aggregategroups' => [ 'associate', 'dissociate' ], ]; global $wgJobClasses; $wgJobClasses['TranslateRenderJob'] = 'TranslateRenderJob'; $wgJobClasses['RenderJob'] = 'TranslateRenderJob'; $wgJobClasses['TranslatablePageMoveJob'] = 'TranslatablePageMoveJob'; $wgJobClasses['TranslateMoveJob'] = 'TranslateMoveJob'; $wgJobClasses['MoveJob'] = 'TranslateMoveJob'; $wgJobClasses['TranslateDeleteJob'] = 'TranslateDeleteJob'; $wgJobClasses['DeleteJob'] = 'TranslateDeleteJob'; $wgJobClasses['TranslationsUpdateJob'] = 'TranslationsUpdateJob'; // Namespaces global $wgNamespacesWithSubpages, $wgNamespaceProtection; global $wgTranslateMessageNamespaces; $wgNamespacesWithSubpages[NS_TRANSLATIONS] = true; $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] = true; // Standard protection and register it for filtering $wgNamespaceProtection[NS_TRANSLATIONS] = [ 'translate' ]; $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS; /// Page translation hooks /// @todo Register our css, is there a better place for this? $wgHooks['OutputPageBeforeHTML'][] = 'PageTranslationHooks::injectCss'; // Check syntax for \ $wgHooks['PageContentSave'][] = 'PageTranslationHooks::tpSyntaxCheck'; $wgHooks['EditFilterMergedContent'][] = 'PageTranslationHooks::tpSyntaxCheckForEditContent'; // Add transtag to page props for discovery $wgHooks['PageContentSaveComplete'][] = 'PageTranslationHooks::addTranstag'; $wgHooks['RevisionInsertComplete'][] = 'PageTranslationHooks::updateTranstagOnNullRevisions'; // Register different ways to show language links $wgHooks['ParserFirstCallInit'][] = 'TranslateHooks::setupParserHooks'; $wgHooks['LanguageLinks'][] = 'PageTranslationHooks::addLanguageLinks'; $wgHooks['SkinTemplateGetLanguageLink'][] = 'PageTranslationHooks::formatLanguageLink'; // Strip \ tags etc. from source pages when rendering $wgHooks['ParserBeforeStrip'][] = 'PageTranslationHooks::renderTagPage'; $wgHooks['ParserOutputPostCacheTransform'][] = 'PageTranslationHooks::onParserOutputPostCacheTransform'; // Set the page content language $wgHooks['PageContentLanguage'][] = 'PageTranslationHooks::onPageContentLanguage'; // Prevent editing of certain pages in translations namespace $wgHooks['getUserPermissionsErrorsExpensive'][] = 'PageTranslationHooks::onGetUserPermissionsErrorsExpensive'; // Prevent editing of translation pages directly $wgHooks['getUserPermissionsErrorsExpensive'][] = 'PageTranslationHooks::preventDirectEditing'; // Prevent patroling of translation pages $wgHooks['getUserPermissionsErrors'][] = 'PageTranslationHooks::preventPatrolling'; // Our custom header for translation pages $wgHooks['ArticleViewHeader'][] = 'PageTranslationHooks::translatablePageHeader'; // Edit notice shown on translatable pages $wgHooks['TitleGetEditNotices'][] = 'PageTranslationHooks::onTitleGetEditNotices'; // Custom move page that can move all the associated pages too $wgHooks['SpecialPage_initList'][] = 'PageTranslationHooks::replaceMovePage'; // Locking during page moves $wgHooks['getUserPermissionsErrorsExpensive'][] = 'PageTranslationHooks::lockedPagesCheck'; // Disable action=delete $wgHooks['ArticleConfirmDelete'][] = 'PageTranslationHooks::disableDelete'; // Replace subpage logic behavior $wgHooks['SkinSubPageSubtitle'][] = 'PageTranslationHooks::replaceSubtitle'; // Replaced edit tab with translation tab for translation pages $wgHooks['SkinTemplateNavigation'][] = 'PageTranslationHooks::translateTab'; // Update translated page when translation unit is moved $wgHooks['TitleMoveComplete'][] = 'PageTranslationHooks::onMoveTranslationUnits'; // Update translated page when translation unit is deleted $wgHooks['ArticleDeleteComplete'][] = 'PageTranslationHooks::onDeleteTranslationUnit'; } global $wgTranslateUseSandbox; if ( $wgTranslateUseSandbox ) { global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions; $wgSpecialPages['ManageTranslatorSandbox'] = 'SpecialManageTranslatorSandbox'; $wgSpecialPages['TranslationStash'] = 'SpecialTranslationStash'; $wgDefaultUserOptions['translate-sandbox'] = ''; // right-translate-sandboxmanage action-translate-sandboxmanage $wgAvailableRights[] = 'translate-sandboxmanage'; $wgHooks['GetPreferences'][] = 'TranslateSandbox::onGetPreferences'; $wgHooks['UserGetRights'][] = 'TranslateSandbox::enforcePermissions'; $wgHooks['ApiCheckCanExecute'][] = 'TranslateSandbox::onApiCheckCanExecute'; $wgHooks['UserGetRights'][] = 'TranslateSandbox::allowAccountCreation'; global $wgLogTypes, $wgLogActionsHandlers; // log-name-translatorsandbox log-description-translatorsandbox $wgLogTypes[] = 'translatorsandbox'; // logentry-translatorsandbox-promoted logentry-translatorsandbox-rejected $wgLogActionsHandlers['translatorsandbox/promoted'] = 'TranslateLogFormatter'; $wgLogActionsHandlers['translatorsandbox/rejected'] = 'TranslateLogFormatter'; // This is no longer used for new entries since 2016.07. // logentry-newusers-tsbpromoted $wgLogActionsHandlers['newusers/tsbpromoted'] = 'LogFormatter'; global $wgJobClasses; $wgJobClasses['TranslateSandboxEmailJob'] = 'TranslateSandboxEmailJob'; global $wgAPIModules; $wgAPIModules['translationstash'] = 'ApiTranslationStash'; $wgAPIModules['translatesandbox'] = 'ApiTranslateSandbox'; } // Back compatibility for MediaWiki <= 1.31 global $wgVersion, $wgResourceModules; if ( version_compare( $wgVersion, '1.32', '<' ) ) { $wgResourceModules['ext.translate.editor']['dependencies'][] = 'mediawiki.api.parse'; $wgResourceModules['ext.translate.special.translate']['dependencies'][] = 'mediawiki.api.parse'; } global $wgNamespaceRobotPolicies; $wgNamespaceRobotPolicies[NS_TRANSLATIONS] = 'noindex'; } /** * Hook: UserGetReservedNames * Prevents anyone from registering or logging in as FuzzyBot * * @param array &$names */ public static function onUserGetReservedNames( array &$names ) { global $wgTranslateFuzzyBotName; $names[] = $wgTranslateFuzzyBotName; } /** * Used for setting an AbuseFilter variable. * * @param AbuseFilterVariableHolder &$vars * @param Title|null $title */ public static function onAbuseFilterFilterAction( &$vars, $title ) { if ( !$title instanceof Title ) { wfDebugLog( 'T143073', 'Got non-Title in ' . wfGetAllCallers( 5 ) ); return; } $handle = new MessageHandle( $title ); // Only set this variable if we are in a proper namespace to avoid // unnecessary overhead in non-translation pages if ( $handle->isMessageNamespace() ) { $vars->setLazyLoadVar( 'translate_source_text', 'translate-get-source', [ 'handle' => $handle ] ); } } /** * Computes the translate_source_text AbuseFilter variable * @param string $method * @param AbuseFilterVariableHolder $vars * @param array $parameters * @param null &$result * @return bool */ public static function onAbuseFilterComputeVariable( $method, $vars, $parameters, &$result ) { if ( $method !== 'translate-get-source' ) { return true; } $handle = $parameters['handle']; $source = ''; if ( $handle->isValid() ) { $group = $handle->getGroup(); $source = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() ); } $result = $source; return false; } /** * Register AbuseFilter variables provided by Translate. * @param array &$builderValues */ public static function onAbuseFilterBuilder( array &$builderValues ) { // Uses: 'abusefilter-edit-builder-vars-translate-source-text' $builderValues['vars']['translate_source_text'] = 'translate-source-text'; } /** * Hook: ParserFirstCallInit * Registers \ tag with the parser. * * @param Parser $parser */ public static function setupParserHooks( Parser $parser ) { // For nice language list in-page $parser->setHook( 'languages', [ 'PageTranslationHooks', 'languages' ] ); } /** * Hook: UnitTestsList * * @param array &$files */ public static function setupUnitTests( array &$files ) { $dir = __DIR__ . '/tests/phpunit'; $directoryIterator = new RecursiveDirectoryIterator( $dir ); $fileIterator = new RecursiveIteratorIterator( $directoryIterator ); /** @var SplFileInfo $fileInfo */ foreach ( $fileIterator as $fileInfo ) { if ( substr( $fileInfo->getFilename(), -8 ) === 'Test.php' ) { $files[] = $fileInfo->getPathname(); } } } /** * Hook: LoadExtensionSchemaUpdates * * @param DatabaseUpdater $updater */ public static function schemaUpdates( DatabaseUpdater $updater ) { $dir = __DIR__ . '/sql'; $updater->addExtensionUpdate( [ 'addTable', 'translate_sections', "$dir/translate_sections.sql", true ] ); $updater->addExtensionUpdate( [ 'addField', 'translate_sections', 'trs_order', "$dir/translate_sections-trs_order.patch.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'revtag', "$dir/revtag.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_groupstats', "$dir/translate_groupstats.sql", true ] ); $updater->addExtensionUpdate( [ 'addIndex', 'translate_sections', 'trs_page_order', "$dir/translate_sections-indexchange.sql", true ] ); $updater->addExtensionUpdate( [ 'dropIndex', 'translate_sections', 'trs_page', "$dir/translate_sections-indexchange2.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_reviews', "$dir/translate_reviews.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_groupreviews', "$dir/translate_groupreviews.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_tms', "$dir/translate_tm.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_metadata', "$dir/translate_metadata.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_messageindex', "$dir/translate_messageindex.sql", true ] ); $updater->addExtensionUpdate( [ 'addIndex', 'translate_groupstats', 'tgs_lang', "$dir/translate_groupstats-indexchange.sql", true ] ); $updater->addExtensionUpdate( [ 'addField', 'translate_groupstats', 'tgs_proofread', "$dir/translate_groupstats-proofread.sql", true ] ); $updater->addExtensionUpdate( [ 'addTable', 'translate_stash', "$dir/translate_stash.sql", true ] ); // This also adds a PRIMARY KEY $updater->addExtensionUpdate( [ 'renameIndex', 'translate_reviews', 'trr_user_page_revision', 'PRIMARY', false, "$dir/translate_reviews-patch-01-primary-key.sql", true ] ); } /** * Hook: ParserTestTables * @param array &$tables */ public static function parserTestTables( array &$tables ) { $tables[] = 'revtag'; $tables[] = 'translate_groupstats'; $tables[] = 'translate_messageindex'; $tables[] = 'translate_stash'; } /** * Hook: PageContentLanguage * Set the correct page content language for translation units. * * @param Title $title * @param Language|StubUserLang|string &$pageLang */ public static function onPageContentLanguage( Title $title, &$pageLang ) { $handle = new MessageHandle( $title ); if ( $handle->isMessageNamespace() ) { $pageLang = $handle->getEffectiveLanguage(); } } /** * Hook: LanguageGetTranslatedLanguageNames * Hook: TranslateSupportedLanguages * @param array &$names * @param string $code */ public static function translateMessageDocumentationLanguage( array &$names, $code ) { global $wgTranslateDocumentationLanguageCode; if ( $wgTranslateDocumentationLanguageCode ) { // Special case the autonyms if ( $wgTranslateDocumentationLanguageCode === $code || $code === null ) { $code = 'en'; } $names[$wgTranslateDocumentationLanguageCode] = wfMessage( 'translate-documentation-language' )->inLanguage( $code )->plain(); } } /** * Hook: SpecialSearchProfiles * @param array &$profiles */ public static function searchProfile( array &$profiles ) { global $wgTranslateMessageNamespaces; $insert = []; $insert['translation'] = [ 'message' => 'translate-searchprofile', 'tooltip' => 'translate-searchprofile-tooltip', 'namespaces' => $wgTranslateMessageNamespaces, ]; // Insert translations before 'all' $index = array_search( 'all', array_keys( $profiles ) ); // Or just at the end if all is not found if ( $index === false ) { wfWarn( '"all" not found in search profiles' ); $index = count( $profiles ); } $profiles = array_merge( array_slice( $profiles, 0, $index ), $insert, array_slice( $profiles, $index ) ); } /** * Hook: SpecialSearchProfileForm * @param SpecialSearch $search * @param string &$form * @param string $profile * @param string $term * @param array $opts * @return bool */ public static function searchProfileForm( SpecialSearch $search, &$form, $profile, $term, array $opts ) { if ( $profile !== 'translation' ) { return true; } $server = TTMServer::primary(); if ( TTMServer::primary() instanceof SearchableTTMServer ) { $href = SpecialPage::getTitleFor( 'SearchTranslations' ) ->getFullUrl( [ 'query' => $term ] ); $wrapper = new RawMessage( '' ); $form = $wrapper ->params( $search->msg( 'translate-searchprofile-note', $href )->plain() ) ->parse(); return false; } if ( !$search->getSearchEngine()->supports( 'title-suffix-filter' ) ) { return false; } $hidden = ''; foreach ( $opts as $key => $value ) { $hidden .= Html::hidden( $key, $value ); } $context = $search->getContext(); $code = $context->getLanguage()->getCode(); $selected = $context->getRequest()->getVal( 'languagefilter' ); $languages = TranslateUtils::getLanguageNames( $code ); ksort( $languages ); $selector = new XmlSelect( 'languagefilter', 'languagefilter' ); $selector->setDefault( $selected ); $selector->addOption( wfMessage( 'translate-search-nofilter' )->text(), '-' ); foreach ( $languages as $code => $name ) { $selector->addOption( "$code - $name", $code ); } $selector = $selector->getHTML(); $label = Xml::label( wfMessage( 'translate-search-languagefilter' )->text(), 'languagefilter' ) . ' '; $params = [ 'id' => 'mw-searchoptions' ]; $form = Xml::fieldset( false, false, $params ) . $hidden . $label . $selector . Html::closeElement( 'fieldset' ); return false; } /** * Hook: SpecialSearchSetupEngine * @param SpecialSearch $search * @param string $profile * @param SearchEngine $engine */ public static function searchProfileSetupEngine( SpecialSearch $search, $profile, SearchEngine $engine ) { if ( $profile !== 'translation' ) { return; } $context = $search->getContext(); $selected = $context->getRequest()->getVal( 'languagefilter' ); if ( $selected !== '-' && $selected ) { $engine->setFeatureData( 'title-suffix-filter', "/$selected" ); $search->setExtraParam( 'languagefilter', $selected ); } } /** * Hook: Translate:MessageGroupStats:isIncluded * @param int $id * @param string $code * @return bool */ public static function hideDiscouragedFromStats( $id, $code ) { // Return true to keep, false to exclude return MessageGroups::getPriority( $id ) !== 'discouraged'; } /** * Hook: Translate:MessageGroupStats:isIncluded * @param int $id * @param string $code * @return false */ public static function hideRestrictedFromStats( $id, $code ) { $filterLangs = TranslateMetadata::get( $id, 'prioritylangs' ); if ( strlen( $filterLangs ) === 0 ) { // No restrictions, keep everything return true; } $filter = array_flip( explode( ',', $filterLangs ) ); // If the language is in the list, return true to not hide it return isset( $filter[$code] ); } /** * Hook: LinksUpdate * @param LinksUpdate $updater */ public static function preventCategorization( LinksUpdate $updater ) { $handle = new MessageHandle( $updater->getTitle() ); if ( $handle->isMessageNamespace() && !$handle->isDoc() ) { $updater->mCategories = []; } } /** * Hook: MakeGlobalVariablesScript * * Adds $wgTranslateDocumentationLanguageCode to ResourceLoader configuration * when Special:Translate is shown. * @param array &$vars * @param OutputPage $out */ public static function addConfig( array &$vars, OutputPage $out ) { $request = $out->getRequest(); $title = $out->getTitle(); list( $alias, ) = TranslateUtils::resolveSpecialPageAlias( $title->getText() ); if ( $title->isSpecialPage() && ( $alias === 'Translate' || $alias === 'TranslationStash' || $alias === 'SearchTranslations' ) ) { global $wgTranslateDocumentationLanguageCode, $wgTranslatePermissionUrl, $wgTranslateUseSandbox; $vars['TranslateRight'] = $out->getUser()->isAllowed( 'translate' ); $vars['TranslateMessageReviewRight'] = $out->getUser()->isAllowed( 'translate-messagereview' ); $vars['DeleteRight'] = $out->getUser()->isAllowed( 'delete' ); $vars['wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode; $vars['wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl; $vars['wgTranslateUseSandbox'] = $wgTranslateUseSandbox; } } /** * Hook: AdminLinks * @param ALTree $tree */ public static function onAdminLinks( ALTree $tree ) { global $wgTranslateUseSandbox; if ( $wgTranslateUseSandbox ) { $sectionLabel = wfMessage( 'adminlinks_users' )->text(); $row = $tree->getSection( $sectionLabel )->getRow( 'main' ); $row->addItem( ALItem::newFromSpecialPage( 'TranslateSandbox' ) ); } } /** * Hook: MergeAccountFromTo * For UserMerge extension. * * @param User $oldUser * @param User $newUser */ public static function onMergeAccountFromTo( User $oldUser, User $newUser ) { $dbw = wfGetDB( DB_MASTER ); // Update the non-duplicate rows, we'll just delete // the duplicate ones later foreach ( self::$userMergeTables as $table => $field ) { if ( $dbw->tableExists( $table ) ) { $dbw->update( $table, [ $field => $newUser->getId() ], [ $field => $oldUser->getId() ], __METHOD__, [ 'IGNORE' ] ); } } } /** * Hook: DeleteAccount * For UserMerge extension. * * @param User $oldUser */ public static function onDeleteAccount( User $oldUser ) { $dbw = wfGetDB( DB_MASTER ); // Delete any remaining rows that didn't get merged foreach ( self::$userMergeTables as $table => $field ) { if ( $dbw->tableExists( $table ) ) { $dbw->delete( $table, [ $field => $oldUser->getId() ], __METHOD__ ); } } } /** * Hook: AbortEmailNotification * * False aborts the email. * @param User $editor * @param Title $title * @param RecentChange $rc * @return bool */ public static function onAbortEmailNotificationReview( User $editor, Title $title, RecentChange $rc ) { if ( $rc->getAttribute( 'rc_log_type' ) === 'translationreview' ) { return false; } } /** * Hook: TitleIsAlwaysKnown * Make Special:MyLanguage links red if the target page doesn't exist. * A bit hacky because the core code is not so flexible. * * @param Title $target * @param bool &$isKnown * @return bool */ public static function onTitleIsAlwaysKnown( Title $target, &$isKnown ) { if ( !$target->inNamespace( NS_SPECIAL ) ) { return true; } list( $name, $subpage ) = TranslateUtils::resolveSpecialPageAlias( $target->getDBkey() ); if ( $name !== 'MyLanguage' ) { return true; } if ( (string)$subpage === '' ) { return true; } $realTarget = Title::newFromText( $subpage ); if ( !$realTarget || !$realTarget->exists() ) { $isKnown = false; return false; } return true; } /** * Hook: ParserFirstCallInit * @param Parser $parser */ public static function setupTranslateParserFunction( Parser $parser ) { $parser->setFunctionHook( 'translation', 'TranslateHooks::translateRenderParserFunction' ); } /** * @param Parser $parser * @return string */ public static function translateRenderParserFunction( Parser $parser ) { $pageTitle = $parser->getTitle(); $handle = new MessageHandle( $pageTitle ); $code = $handle->getCode(); if ( Language::isKnownLanguageTag( $code ) ) { return '/' . $code; } return ''; } /** * @param ResourceLoader $resourceLoader */ public static function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ) { $modules = []; $modules['ext.translate.recentgroups'] = [ 'scripts' => 'resources/js/ext.translate.recentgroups.js', 'dependencies' => [ 'mediawiki.storage' ], 'localBasePath' => __DIR__, 'remoteExtPath' => 'Translate', 'targets' => [ 'desktop', 'mobile' ], ]; $resourceLoader->register( $modules ); } }