Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
HookHandler.php
1<?php
2declare( strict_types=1 );
3
4namespace MediaWiki\Extension\Translate;
5
7use ALItem;
8use ALTree;
9use LogFormatter;
10use MediaWiki\Api\ApiRawMessage;
11use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
12use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
13use MediaWiki\Config\Config;
14use MediaWiki\Config\ConfigException;
15use MediaWiki\Config\ServiceOptions;
16use MediaWiki\Content\Content;
17use MediaWiki\Content\TextContent;
18use MediaWiki\Context\IContextSource;
19use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
20use MediaWiki\Extension\Translate\LogFormatter as TranslateLogFormatter;
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\ResourceLoader\Context;
58use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook;
59use MediaWiki\ResourceLoader\ResourceLoader;
60use MediaWiki\Revision\Hook\RevisionRecordInsertedHook;
61use MediaWiki\Revision\RevisionLookup;
62use MediaWiki\Settings\SettingsBuilder;
63use MediaWiki\SpecialPage\SpecialPage;
64use MediaWiki\Specials\SpecialSearch;
65use MediaWiki\Status\Status;
66use MediaWiki\StubObject\StubUserLang;
67use MediaWiki\Title\Title;
68use MediaWiki\Title\TitleValue;
69use MediaWiki\User\Hook\UserGetReservedNamesHook;
70use MediaWiki\User\User;
71use MediaWiki\Xml\XmlSelect;
72use SearchEngine;
73use Wikimedia\Rdbms\ILoadBalancer;
74
85class HookHandler implements
86 ChangeTagsListActiveHook,
87 ListDefinedTagsHook,
88 ParserFirstCallInitHook,
89 RevisionRecordInsertedHook,
90 UserGetReservedNamesHook,
91 ResourceLoaderRegisterModulesHook
92{
97 private const USER_MERGE_TABLES = [
98 'translate_stash' => 'ts_user',
99 'translate_reviews' => 'trr_user',
100 ];
101 private RevisionLookup $revisionLookup;
102 private ILoadBalancer $loadBalancer;
103 private Config $config;
104 private LanguageNameUtils $languageNameUtils;
105
106 public function __construct(
107 RevisionLookup $revisionLookup,
108 ILoadBalancer $loadBalancer,
109 Config $config,
110 LanguageNameUtils $languageNameUtils
111 ) {
112 $this->revisionLookup = $revisionLookup;
113 $this->loadBalancer = $loadBalancer;
114 $this->config = $config;
115 $this->languageNameUtils = $languageNameUtils;
116 }
117
119 public static function setupTranslate(): void {
120 global $wgTranslateYamlLibrary, $wgLogTypes;
121 $hooks = [];
122
123 /*
124 * Text that will be shown in translations if the translation is outdated.
125 * Must be something that does not conflict with actual content.
126 */
127 if ( !defined( 'TRANSLATE_FUZZY' ) ) {
128 define( 'TRANSLATE_FUZZY', '!!FUZZY!!' );
129 }
130
131 $wgTranslateYamlLibrary ??= function_exists( 'yaml_parse' ) ? 'phpyaml' : 'spyc';
132
133 $hooks['PageSaveComplete'][] = [ TranslateEditAddons::class, 'onSaveComplete' ];
134
135 // Page translation setup check and init if enabled.
136 global $wgEnablePageTranslation;
137 if ( $wgEnablePageTranslation ) {
138 // Special page and the right to use it
139 global $wgSpecialPages, $wgAvailableRights;
140 $wgSpecialPages['PageTranslation'] = [
141 'class' => PageTranslationSpecialPage::class,
142 'services' => [
143 'LanguageFactory',
144 'LinkBatchFactory',
145 'JobQueueGroup',
146 'PermissionManager',
147 'Translate:TranslatablePageMarker',
148 'Translate:TranslatablePageParser',
149 'Translate:MessageGroupMetadata',
150 'Translate:TranslatablePageView',
151 'Translate:TranslatablePageStateStore',
152 'FormatterFactory'
153 ]
154 ];
155 $wgSpecialPages['PageTranslationDeletePage'] = [
156 'class' => DeleteTranslatableBundleSpecialPage::class,
157 'services' => [
158 'PermissionManager',
159 'Translate:TranslatableBundleDeleter',
160 'Translate:TranslatableBundleFactory',
161 ]
162 ];
163
164 // right-pagetranslation action-pagetranslation
165 $wgAvailableRights[] = 'pagetranslation';
166
167 $wgSpecialPages['PageMigration'] = MigrateTranslatablePageSpecialPage::class;
168 $wgSpecialPages['PagePreparation'] = PrepareTranslatablePageSpecialPage::class;
169
170 global $wgActionFilteredLogs, $wgLogActionsHandlers;
171
172 // log-description-pagetranslation log-name-pagetranslation logentry-pagetranslation-mark
173 // logentry-pagetranslation-unmark logentry-pagetranslation-moveok
174 // logentry-pagetranslation-movenok logentry-pagetranslation-deletefok
175 // logentry-pagetranslation-deletefnok logentry-pagetranslation-deletelok
176 // logentry-pagetranslation-deletelnok logentry-pagetranslation-encourage
177 // logentry-pagetranslation-discourage logentry-pagetranslation-prioritylanguages
178 // logentry-pagetranslation-associate logentry-pagetranslation-dissociate
179 if ( !in_array( 'pagetranslation', $wgLogTypes ) ) {
180 $wgLogTypes[] = 'pagetranslation';
181 }
182 $wgLogActionsHandlers['pagetranslation/mark'] = TranslatableBundleLogFormatter::class;
183 $wgLogActionsHandlers['pagetranslation/unmark'] = TranslatableBundleLogFormatter::class;
184 $wgLogActionsHandlers['pagetranslation/moveok'] = TranslatableBundleLogFormatter::class;
185 $wgLogActionsHandlers['pagetranslation/movenok'] = TranslatableBundleLogFormatter::class;
186 $wgLogActionsHandlers['pagetranslation/deletelok'] = TranslatableBundleLogFormatter::class;
187 $wgLogActionsHandlers['pagetranslation/deletefok'] = TranslatableBundleLogFormatter::class;
188 $wgLogActionsHandlers['pagetranslation/deletelnok'] = TranslatableBundleLogFormatter::class;
189 $wgLogActionsHandlers['pagetranslation/deletefnok'] = TranslatableBundleLogFormatter::class;
190 $wgLogActionsHandlers['pagetranslation/encourage'] = TranslatableBundleLogFormatter::class;
191 $wgLogActionsHandlers['pagetranslation/discourage'] = TranslatableBundleLogFormatter::class;
192 $wgLogActionsHandlers['pagetranslation/prioritylanguages'] = TranslatableBundleLogFormatter::class;
193 $wgLogActionsHandlers['pagetranslation/associate'] = TranslatableBundleLogFormatter::class;
194 $wgLogActionsHandlers['pagetranslation/dissociate'] = TranslatableBundleLogFormatter::class;
195 $wgActionFilteredLogs['pagetranslation'] = [
196 'mark' => [ 'mark' ],
197 'unmark' => [ 'unmark' ],
198 'move' => [ 'moveok', 'movenok' ],
199 'delete' => [ 'deletefok', 'deletefnok', 'deletelok', 'deletelnok' ],
200 'encourage' => [ 'encourage' ],
201 'discourage' => [ 'discourage' ],
202 'prioritylanguages' => [ 'prioritylanguages' ],
203 'aggregategroups' => [ 'associate', 'dissociate' ],
204 ];
205
206 if ( !in_array( 'messagebundle', $wgLogTypes ) ) {
207 $wgLogTypes[] = 'messagebundle';
208 }
209 $wgLogActionsHandlers['messagebundle/moveok'] = TranslatableBundleLogFormatter::class;
210 $wgLogActionsHandlers['messagebundle/movenok'] = TranslatableBundleLogFormatter::class;
211 $wgLogActionsHandlers['messagebundle/deletefok'] = TranslatableBundleLogFormatter::class;
212 $wgLogActionsHandlers['messagebundle/deletefnok'] = TranslatableBundleLogFormatter::class;
213 $wgActionFilteredLogs['messagebundle'] = [
214 'move' => [ 'moveok', 'movenok' ],
215 'delete' => [ 'deletefok', 'deletefnok' ],
216 ];
217
218 $wgLogActionsHandlers['import/translatable-bundle'] = TranslatableBundleLogFormatter::class;
219
220 global $wgJobClasses;
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;
226
227 // API modules
228 global $wgAPIModules;
229 $wgAPIModules['markfortranslation'] = [
230 'class' => MarkForTranslationActionApi::class,
231 'services' => [
232 'Translate:TranslatablePageMarker',
233 'Translate:MessageGroupMetadata',
234 ]
235 ];
236
237 // Namespaces
238 global $wgNamespacesWithSubpages, $wgNamespaceProtection;
239 global $wgTranslateMessageNamespaces;
240
241 $wgNamespacesWithSubpages[NS_TRANSLATIONS] = true;
242 $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] = true;
243
244 // Standard protection and register it for filtering
245 $wgNamespaceProtection[NS_TRANSLATIONS] = [ 'translate' ];
246 $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS;
247
249
251 $hooks['BeforePageDisplay'][] = [ Hooks::class, 'onBeforePageDisplay' ];
252
253 // Disable VE
254 $hooks['VisualEditorBeforeEditor'][] = [ Hooks::class, 'onVisualEditorBeforeEditor' ];
255
256 // Check syntax for <translate>
257 $hooks['MultiContentSave'][] = [ Hooks::class, 'tpSyntaxCheck' ];
258 $hooks['EditFilterMergedContent'][] =
259 [ Hooks::class, 'tpSyntaxCheckForEditContent' ];
260
261 // Add transtag to page props for discovery
262 $hooks['PageSaveComplete'][] = [ Hooks::class, 'addTranstagAfterSave' ];
263
264 $hooks['RevisionRecordInserted'][] = [ Hooks::class, 'updateTranstagOnNullRevisions' ];
265
266 // Register different ways to show language links
267 $hooks['ParserFirstCallInit'][] = [ self::class, 'setupParserHooks' ];
268 $hooks['LanguageLinks'][] = [ Hooks::class, 'addLanguageLinks' ];
269 $hooks['SkinTemplateGetLanguageLink'][] = [ Hooks::class, 'formatLanguageLink' ];
270
271 // Allow templates to query whether they are transcluded in a translatable/translated page
272 $hooks['GetMagicVariableIDs'][] = [ Hooks::class, 'onGetMagicVariableIDs' ];
273 $hooks['ParserGetVariableValueSwitch'][] = [ Hooks::class, 'onParserGetVariableValueSwitch' ];
274
275 // Strip <translate> tags etc. from source pages when rendering
276 $hooks['ParserBeforeInternalParse'][] = [ Hooks::class, 'renderTagPage' ];
277 // Strip <translate> tags etc. from source pages when preprocessing
278 $hooks['ParserBeforePreprocess'][] = [ Hooks::class, 'preprocessTagPage' ];
279
280 $hooks['BeforeParserFetchTemplateRevisionRecord'][] =
281 [ Hooks::class, 'fetchTranslatableTemplateAndTitle' ];
282
283 // Set the page content language
284 $hooks['PageContentLanguage'][] = [ Hooks::class, 'onPageContentLanguage' ];
285
286 // Prevent editing of certain pages in translations namespace
287 $hooks['getUserPermissionsErrorsExpensive'][] =
288 [ Hooks::class, 'onGetUserPermissionsErrorsExpensive' ];
289 // Prevent editing of translation pages directly
290 $hooks['getUserPermissionsErrorsExpensive'][] =
291 [ Hooks::class, 'preventDirectEditing' ];
292
293 // Our custom header for translation pages
294 $hooks['ArticleViewHeader'][] = [ Hooks::class, 'translatablePageHeader' ];
295
296 // Edit notice shown on translatable pages
297 $hooks['TitleGetEditNotices'][] = [ Hooks::class, 'onTitleGetEditNotices' ];
298
299 // Custom move page that can move all the associated pages too
300 $hooks['SpecialPage_initList'][] = [ Hooks::class, 'replaceMovePage' ];
301 // Locking during page moves
302 $hooks['getUserPermissionsErrorsExpensive'][] =
303 [ Hooks::class, 'lockedPagesCheck' ];
304 // Disable action=delete
305 $hooks['ArticleConfirmDelete'][] = [ Hooks::class, 'disableDelete' ];
306
307 // Replace subpage logic behavior
308 $hooks['SkinSubPageSubtitle'][] = [ Hooks::class, 'replaceSubtitle' ];
309
310 // Replace edit tab with translation tab for translation pages, and add
311 // "mark for translation" tab/link on source translatable pages
312 $hooks['SkinTemplateNavigation::Universal'][] = [ Hooks::class, 'onSkinTemplateNavigation__Universal' ];
313
314 // Update translated page when translation unit is moved
315 $hooks['PageMoveComplete'][] = [ Hooks::class, 'onMovePageTranslationUnits' ];
316
317 // Update translated page when translation unit is deleted
318 $hooks['ArticleDeleteComplete'][] = [ Hooks::class, 'onDeleteTranslationUnit' ];
319
320 // Prevent editing of translation pages.
321 $hooks['ReplaceTextFilterPageTitlesForEdit'][] = [ Hooks::class, 'onReplaceTextFilterPageTitlesForEdit' ];
322 // Prevent renaming of translatable pages and their translation and translation units
323 $hooks['ReplaceTextFilterPageTitlesForRename'][] =
324 [ Hooks::class, 'onReplaceTextFilterPageTitlesForRename' ];
325
326 // Auto-create translated categories when not empty
327 $hooks['LinksUpdateComplete'][] = [ Hooks::class, 'onLinksUpdateComplete' ];
328 }
329
330 global $wgTranslateUseSandbox;
331 if ( $wgTranslateUseSandbox ) {
332 global $wgSpecialPages, $wgAvailableRights, $wgDefaultUserOptions;
333
334 $wgSpecialPages['ManageTranslatorSandbox'] = [
335 'class' => ManageTranslatorSandboxSpecialPage::class,
336 'services' => [
337 'Translate:TranslationStashReader',
338 'UserOptionsLookup',
339 'Translate:TranslateSandbox',
340 ],
341 'args' => [
342 static function () {
343 return new ServiceOptions(
344 ManageTranslatorSandboxSpecialPage::CONSTRUCTOR_OPTIONS,
345 MediaWikiServices::getInstance()->getMainConfig()
346 );
347 }
348 ]
349 ];
350 $wgSpecialPages['TranslationStash'] = [
351 'class' => TranslationStashSpecialPage::class,
352 'services' => [
353 'LanguageNameUtils',
354 'Translate:TranslationStashReader',
355 'UserOptionsLookup',
356 'LanguageFactory',
357 ],
358 'args' => [
359 static function () {
360 return new ServiceOptions(
361 TranslationStashSpecialPage::CONSTRUCTOR_OPTIONS,
362 MediaWikiServices::getInstance()->getMainConfig()
363 );
364 }
365 ]
366 ];
367 $wgDefaultUserOptions['translate-sandbox'] = '';
368 // right-translate-sandboxmanage action-translate-sandboxmanage
369 $wgAvailableRights[] = 'translate-sandboxmanage';
370
371 global $wgLogTypes, $wgLogActionsHandlers;
372 // log-name-translatorsandbox log-description-translatorsandbox
373 if ( !in_array( 'translatorsandbox', $wgLogTypes ) ) {
374 $wgLogTypes[] = 'translatorsandbox';
375 }
376 // logentry-translatorsandbox-promoted logentry-translatorsandbox-rejected
377 $wgLogActionsHandlers['translatorsandbox/promoted'] = TranslateLogFormatter::class;
378 $wgLogActionsHandlers['translatorsandbox/rejected'] = TranslateLogFormatter::class;
379
380 // This is no longer used for new entries since 2016.07.
381 // logentry-newusers-tsbpromoted
382 $wgLogActionsHandlers['newusers/tsbpromoted'] = LogFormatter::class;
383
384 $wgJobClasses['TranslateSandboxEmailJob'] = TranslateSandboxEmailJob::class;
385
386 global $wgAPIModules;
387 $wgAPIModules['translationstash'] = [
388 'class' => TranslationStashActionApi::class,
389 'services' => [
390 'DBLoadBalancerFactory',
391 'UserFactory',
392 'Translate:MessageIndex'
393 ]
394 ];
395 $wgAPIModules['translatesandbox'] = [
396 'class' => TranslatorSandboxActionApi::class,
397 'services' => [
398 'UserFactory',
399 'UserNameUtils',
400 'UserOptionsManager',
401 'WikiPageFactory',
402 'UserOptionsLookup',
403 'Translate:TranslateSandbox',
404 ],
405 'args' => [
406 static function () {
407 return new ServiceOptions(
408 TranslatorSandboxActionApi::CONSTRUCTOR_OPTIONS,
409 MediaWikiServices::getInstance()->getMainConfig()
410 );
411 }
412 ]
413 ];
414 }
415
416 global $wgNamespaceRobotPolicies;
417 $wgNamespaceRobotPolicies[NS_TRANSLATIONS] = 'noindex';
418
419 // If no service has been configured, we use a built-in fallback.
420 global $wgTranslateTranslationDefaultService,
421 $wgTranslateTranslationServices;
422 if ( $wgTranslateTranslationDefaultService === true ) {
423 $wgTranslateTranslationDefaultService = 'TTMServer';
424 if ( !isset( $wgTranslateTranslationServices['TTMServer'] ) ) {
425 $wgTranslateTranslationServices['TTMServer'] = [
426 'database' => false,
427 'cutoff' => 0.75,
428 'type' => 'ttmserver',
429 'public' => false,
430 ];
431 }
432 }
433
434 global $wgTranslateEnableMessageGroupSubscription;
435 if ( $wgTranslateEnableMessageGroupSubscription ) {
436 if ( !ExtensionRegistry::getInstance()->isLoaded( 'Echo' ) ) {
437 throw new ConfigException(
438 'Translate: Message group subscriptions (TranslateEnableMessageGroupSubscription) are ' .
439 'enabled but Echo extension is not installed'
440 );
441 }
442 MessageGroupSubscriptionHookHandler::registerHooks( $hooks );
443 $wgJobClasses['MessageGroupSubscriptionNotificationJob'] = MessageGroupSubscriptionNotificationJob::class;
444 }
445
446 global $wgTranslateEnableEventLogging;
447 if ( $wgTranslateEnableEventLogging ) {
448 if ( !ExtensionRegistry::getInstance()->isLoaded( 'EventLogging' ) ) {
449 throw new ConfigException(
450 'Translate: Event logging (TranslateEnableEventLogging) is ' .
451 'enabled but EventLogging extension is not installed'
452 );
453 }
454 }
455
456 global $wgTranslateEnableLuaIntegration;
457 if ( $wgTranslateEnableLuaIntegration ) {
458 if ( ExtensionRegistry::getInstance()->isLoaded( 'Scribunto' ) ) {
459 $hooks[ 'ScribuntoExternalLibraries' ][] = static function ( string $engine, array &$extraLibraries ) {
460 $scribuntoHookHandler = new ScribuntoHookHandler();
461 $scribuntoHookHandler->onScribuntoExternalLibraries( $engine, $extraLibraries );
462 };
463 } else {
464 wfLogWarning(
465 'Translate: Lua integration (TranslateEnableLuaIntegration) is ' .
466 'enabled but Scribunto extension is not installed'
467 );
468 }
469 }
470
471 static::registerHookHandlers( $hooks );
472 }
473
474 private static function registerHookHandlers( array $hooks ): void {
475 if ( defined( 'MW_PHPUNIT_TEST' ) && MediaWikiServices::hasInstance() ) {
476 // When called from a test case's setUp() method,
477 // we can use HookContainer, but we cannot use SettingsBuilder.
478 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
479 foreach ( $hooks as $name => $handlers ) {
480 foreach ( $handlers as $h ) {
481 $hookContainer->register( $name, $h );
482 }
483 }
484 } else {
485 $settingsBuilder = SettingsBuilder::getInstance();
486 $settingsBuilder->registerHookHandlers( $hooks );
487 }
488 }
489
494 public function onUserGetReservedNames( &$reservedUsernames ): void {
495 $reservedUsernames[] = FuzzyBot::getName();
496 $reservedUsernames[] = TranslateUserManager::getName();
497 }
498
500 public static function onAbuseFilterAlterVariables(
501 VariableHolder &$vars, Title $title, User $user
502 ): void {
503 $handle = new MessageHandle( $title );
504
505 // Only set this variable if we are in a proper namespace to avoid
506 // unnecessary overhead in non-translation pages
507 if ( $handle->isMessageNamespace() ) {
508 $vars->setLazyLoadVar(
509 'translate_source_text',
510 'translate-get-source',
511 [ 'handle' => $handle ]
512 );
513 $vars->setLazyLoadVar(
514 'translate_target_language',
515 'translate-get-target-language',
516 [ 'handle' => $handle ]
517 );
518 }
519 }
520
522 public static function onAbuseFilterComputeVariable(
523 string $method,
524 VariableHolder $vars,
525 array $parameters,
526 ?string &$result
527 ): bool {
528 if ( $method !== 'translate-get-source' && $method !== 'translate-get-target-language' ) {
529 return true;
530 }
531
532 $handle = $parameters['handle'];
533 $value = '';
534 if ( $handle->isValid() ) {
535 if ( $method === 'translate-get-source' ) {
536 $group = $handle->getGroup();
537 $value = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
538 } else {
539 $value = $handle->getCode();
540 }
541 }
542
543 $result = $value;
544
545 return false;
546 }
547
549 public static function onAbuseFilterBuilder( array &$builderValues ): void {
550 // The following messages are generated here:
551 // * abusefilter-edit-builder-vars-translate-source-text
552 // * abusefilter-edit-builder-vars-translate-target-language
553 $builderValues['vars']['translate_source_text'] = 'translate-source-text';
554 $builderValues['vars']['translate_target_language'] = 'translate-target-language';
555 }
556
561 public static function setupParserHooks( Parser $parser ): void {
562 // For nice language list in-page
563 $parser->setHook( 'languages', [ Hooks::class, 'languages' ] );
564 }
565
572 public static function onPageContentLanguage( Title $title, &$pageLang ): void {
573 $handle = new MessageHandle( $title );
574 if ( $handle->isMessageNamespace() ) {
575 $pageLang = $handle->getEffectiveLanguage();
576 }
577 }
578
583 public static function translateMessageDocumentationLanguage( array &$names, ?string $code ): void {
584 global $wgTranslateDocumentationLanguageCode;
585 if ( $wgTranslateDocumentationLanguageCode ) {
586 // Special case the autonyms
587 if (
588 $wgTranslateDocumentationLanguageCode === $code ||
589 $code === null
590 ) {
591 $code = 'en';
592 }
593
594 $names[$wgTranslateDocumentationLanguageCode] =
595 wfMessage( 'translate-documentation-language' )->inLanguage( $code )->plain();
596 }
597 }
598
600 public static function searchProfile( array &$profiles ): void {
601 global $wgTranslateMessageNamespaces;
602 $insert = [];
603 $insert['translation'] = [
604 'message' => 'translate-searchprofile',
605 'tooltip' => 'translate-searchprofile-tooltip',
606 'namespaces' => $wgTranslateMessageNamespaces,
607 ];
608
609 // Insert translations before 'all'
610 $index = array_search( 'all', array_keys( $profiles ) );
611
612 // Or just at the end if all is not found
613 if ( $index === false ) {
614 wfWarn( '"all" not found in search profiles' );
615 $index = count( $profiles );
616 }
617
618 $profiles = array_merge(
619 array_slice( $profiles, 0, $index ),
620 $insert,
621 array_slice( $profiles, $index )
622 );
623 }
624
626 public static function searchProfileForm(
627 SpecialSearch $search,
628 string &$form,
629 string $profile,
630 string $term,
631 array $opts
632 ): void {
633 if ( $profile !== 'translation' ) {
634 return;
635 }
636
637 if ( Services::getInstance()->getTtmServerFactory()->getDefaultForQuerying() instanceof SearchableTtmServer ) {
638 $href = SpecialPage::getTitleFor( 'SearchTranslations' )
639 ->getFullUrl( [ 'query' => $term ] );
640 $form = Html::successBox(
641 $search->msg( 'translate-searchprofile-note', $href )->parse(),
642 'plainlinks'
643 );
644 $search->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
645 return;
646 }
647
648 if ( !$search->getSearchEngine()->supports( 'title-suffix-filter' ) ) {
649 return;
650 }
651
652 $hidden = '';
653 foreach ( $opts as $key => $value ) {
654 $hidden .= Html::hidden( $key, $value );
655 }
656
657 $context = $search->getContext();
658 $code = $context->getLanguage()->getCode();
659 $selected = $context->getRequest()->getVal( 'languagefilter' );
660
661 $languages = Utilities::getLanguageNames( $code );
662 ksort( $languages );
663
664 $selector = new XmlSelect( 'languagefilter', 'languagefilter' );
665 $selector->setDefault( $selected );
666 $selector->addOption( wfMessage( 'translate-search-nofilter' )->text(), '-' );
667 foreach ( $languages as $code => $name ) {
668 $selector->addOption( "$code - $name", $code );
669 }
670
671 $selector = $selector->getHTML();
672
673 $label = Html::label(
674 wfMessage( 'translate-search-languagefilter' )->text(),
675 'languagefilter'
676 ) . "\u{00A0}";
677
678 $form .= Html::rawElement(
679 'fieldset',
680 [ 'id' => 'mw-searchoptions' ],
681 $hidden . $label . $selector
682 );
683 }
684
686 public static function searchProfileSetupEngine(
687 SpecialSearch $search,
688 string $profile,
689 SearchEngine $engine
690 ): void {
691 if ( $profile !== 'translation' ) {
692 return;
693 }
694
695 $context = $search->getContext();
696 $selected = $context->getRequest()->getVal( 'languagefilter' );
697 if ( $selected !== '-' && $selected ) {
698 $engine->setFeatureData( 'title-suffix-filter', "/$selected" );
699 $search->setExtraParam( 'languagefilter', $selected );
700 }
701 }
702
704 public static function preventCategorization( Parser $parser, string &$html ): void {
705 if ( $parser->getOptions()->getInterfaceMessage() ) {
706 return;
707 }
708 $pageReference = $parser->getPage();
709 if ( !$pageReference ) {
710 return;
711 }
712
713 $linkTarget = TitleValue::newFromPage( $pageReference );
714 $handle = new MessageHandle( $linkTarget );
715 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
716 $parserOutput = $parser->getOutput();
717 $names = $parserOutput->getCategoryNames();
718 $parserCategories = [];
719 foreach ( $names as $name ) {
720 $parserCategories[$name] = $parserOutput->getCategorySortKey( $name );
721 }
722 $parserOutput->setExtensionData( 'translate-fake-categories', $parserCategories );
723 $parserOutput->setCategories( [] );
724 }
725 }
726
728 public static function showFakeCategories( OutputPage $outputPage, ParserOutput $parserOutput ): void {
729 $fakeCategories = $parserOutput->getExtensionData( 'translate-fake-categories' );
730 if ( $fakeCategories ) {
731 $outputPage->addCategoryLinks( $fakeCategories );
732 }
733 }
734
740 public static function addConfig( array &$vars, OutputPage $out ): void {
741 global $wgTranslateDocumentationLanguageCode,
742 $wgTranslatePermissionUrl,
743 $wgTranslateUseSandbox;
744
745 $title = $out->getTitle();
746 if ( $title->isSpecial( 'Translate' ) ||
747 $title->isSpecial( 'TranslationStash' ) ||
748 $title->isSpecial( 'SearchTranslations' )
749 ) {
750 $user = $out->getUser();
751 $vars['TranslateRight'] = $user->isAllowedAll( 'translate', 'edit' );
752 $vars['TranslateMessageReviewRight'] = $user->isAllowed( 'translate-messagereview' );
753 $vars['DeleteRight'] = $user->isAllowed( 'delete' );
754 $vars['TranslateManageRight'] = $user->isAllowed( 'translate-manage' );
755 $vars['wgTranslateDocumentationLanguageCode'] = $wgTranslateDocumentationLanguageCode;
756 $vars['wgTranslatePermissionUrl'] = $wgTranslatePermissionUrl;
757 $vars['wgTranslateUseSandbox'] = $wgTranslateUseSandbox;
758 }
759 }
760
762 public static function onAdminLinks( ALTree $tree ): void {
763 global $wgTranslateUseSandbox;
764
765 if ( $wgTranslateUseSandbox ) {
766 $sectionLabel = wfMessage( 'adminlinks_users' )->text();
767 $row = $tree->getSection( $sectionLabel )->getRow( 'main' );
768 $row->addItem( ALItem::newFromSpecialPage( 'TranslateSandbox' ) );
769 }
770 }
771
776 public static function onMergeAccountFromTo( User $oldUser, User $newUser ): void {
777 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
778
779 // Update the non-duplicate rows, we'll just delete
780 // the duplicate ones later
781 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
782 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
783 $dbw->newUpdateQueryBuilder()
784 ->update( $table )
785 ->ignore()
786 ->set( [ $field => $newUser->getId() ] )
787 ->where( [ $field => $oldUser->getId() ] )
788 ->caller( __METHOD__ )
789 ->execute();
790 }
791 }
792 }
793
798 public static function onDeleteAccount( User $oldUser ): void {
799 $dbw = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaintenanceConnectionRef( DB_PRIMARY );
800
801 // Delete any remaining rows that didn't get merged
802 foreach ( self::USER_MERGE_TABLES as $table => $field ) {
803 if ( $dbw->tableExists( $table, __METHOD__ ) ) {
804 $dbw->newDeleteQueryBuilder()
805 ->deleteFrom( $table )
806 ->where( [ $field => $oldUser->getId() ] )
807 ->caller( __METHOD__ )
808 ->execute();
809 }
810 }
811 }
812
821 public static function onTitleIsAlwaysKnown( $target, &$isKnown ): bool {
822 if ( !$target->inNamespace( NS_SPECIAL ) ) {
823 return true;
824 }
825
826 [ $name, $subpage ] = MediaWikiServices::getInstance()
827 ->getSpecialPageFactory()->resolveAlias( $target->getDBkey() );
828 if ( $name !== 'MyLanguage' || $subpage === null || $subpage === '' ) {
829 return true;
830 }
831
832 $realTarget = Title::newFromText( $subpage );
833 if ( !$realTarget || !$realTarget->exists() ) {
834 $isKnown = false;
835
836 return false;
837 }
838
839 return true;
840 }
841
843 public function onParserFirstCallInit( $parser ) {
844 $parser->setFunctionHook( 'translation', [ $this, 'translateRenderParserFunction' ] );
845 }
846
847 public function translateRenderParserFunction( Parser $parser ): string {
848 if ( $parser->getOptions()->getInterfaceMessage() ) {
849 return '';
850 }
851 $pageReference = $parser->getPage();
852 if ( !$pageReference ) {
853 return '';
854 }
855 $linkTarget = TitleValue::newFromPage( $pageReference );
856 $handle = new MessageHandle( $linkTarget );
857 $code = $handle->getCode();
858 if ( $this->languageNameUtils->isKnownLanguageTag( $code ) ) {
859 return '/' . $code;
860 }
861 return '';
862 }
863
869 public static function validateMessage(
870 IContextSource $context,
871 Content $content,
872 Status $status,
873 string $summary,
874 User $user
875 ): bool {
876 if ( !$content instanceof TextContent ) {
877 // Not interested
878 return true;
879 }
880
881 $text = $content->getText();
882 $title = $context->getTitle();
883 $handle = new MessageHandle( $title );
884
885 if ( !$handle->isValid() ) {
886 return true;
887 }
888
889 // Don't bother validating if FuzzyBot or translation admin are saving.
890 if ( $user->isAllowed( 'translate-manage' ) || $user->equals( FuzzyBot::getUser() ) ) {
891 return true;
892 }
893
894 // Check the namespace, and perform validations for all messages excluding documentation.
895 if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
896 $group = $handle->getGroup();
897
898 if ( method_exists( $group, 'getMessageContent' ) ) {
899 // @phan-suppress-next-line PhanUndeclaredMethod
900 $definition = $group->getMessageContent( $handle );
901 } else {
902 $definition = $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
903 }
904
905 $message = new FatMessage( $handle->getKey(), $definition );
906 $message->setTranslation( $text );
907
908 $messageValidator = $group->getValidator();
909 if ( !$messageValidator ) {
910 return true;
911 }
912
913 $validationResponse = $messageValidator->validateMessage( $message, $handle->getCode() );
914 if ( $validationResponse->hasErrors() ) {
915 $status->fatal( new ApiRawMessage(
916 $context->msg( 'translate-syntax-error' )->parse(),
917 'translate-validation-failed',
918 [
919 'validation' => [
920 'errors' => $validationResponse->getDescriptiveErrors( $context ),
921 'warnings' => $validationResponse->getDescriptiveWarnings( $context )
922 ]
923 ]
924 ) );
925 return false;
926 }
927 }
928
929 return true;
930 }
931
933 public function onRevisionRecordInserted( $revisionRecord ): void {
934 $parentId = $revisionRecord->getParentId();
935 if ( $parentId === 0 || $parentId === null ) {
936 // No parent, bail out.
937 return;
938 }
939
940 $prevRev = $this->revisionLookup->getRevisionById( $parentId );
941 if ( !$prevRev || !$revisionRecord->hasSameContent( $prevRev ) ) {
942 // Not a null revision, bail out.
943 return;
944 }
945
946 // List of tags that should be copied over when updating
947 // tp:tag and tp:mark handling is in Hooks::updateTranstagOnNullRevisions.
948 $tagsToCopy = [ RevTagStore::FUZZY_TAG, RevTagStore::TRANSVER_PROP ];
949
950 $db = $this->loadBalancer->getConnection( DB_PRIMARY );
951 $db->insertSelect(
952 'revtag',
953 'revtag',
954 [
955 'rt_type' => 'rt_type',
956 'rt_page' => 'rt_page',
957 'rt_revision' => $revisionRecord->getId(),
958 'rt_value' => 'rt_value',
959
960 ],
961 [
962 'rt_type' => $tagsToCopy,
963 'rt_revision' => $parentId,
964 ],
965 __METHOD__
966 );
967 }
968
970 public function onListDefinedTags( &$tags ): void {
971 $tags[] = 'translate-translation-pages';
972 }
973
975 public function onChangeTagsListActive( &$tags ): void {
976 if ( $this->config->get( 'EnablePageTranslation' ) ) {
977 $tags[] = 'translate-translation-pages';
978 }
979 }
980
981 public static function getLanguageJson( Context $context ): array {
982 return [
983 'supportedLanguages' => Utilities::getLanguageNames( $context->getLanguage() ),
984 'undeterminedLanguageCode' => AggregateMessageGroup::UNDETERMINED_LANGUAGE_CODE
985 ];
986 }
987
988 /* @inheritDoc */
989 public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void {
990 $dir = dirname( __DIR__ );
991
992 $modules = [];
993
994 if ( ExtensionRegistry::getInstance()->isLoaded( 'VisualEditor' ) ) {
995 $modules[ 'ext.translate.ve' ] = [
996 'localBasePath' => $dir,
997 'remoteExtPath' => 'Translate',
998 'scripts' => [
999 'resources/src/ve-translate/ve.ce.MWTranslateAnnotationNode.js',
1000 'resources/src/ve-translate/ve.dm.MWTranslateAnnotationNode.js',
1001 'resources/src/ve-translate/ve.ui.MWTranslateAnnotationContextItem.js',
1002 ],
1003 'dependencies' => [
1004 'ext.visualEditor.mwcore',
1005 ],
1006 'messages' => [
1007 'visualeditor-annotations-translate-start',
1008 'visualeditor-annotations-translate-end',
1009 'visualeditor-annotations-translate-description',
1010 'visualeditor-annotations-tvar-start',
1011 'visualeditor-annotations-tvar-end',
1012 'visualeditor-annotations-tvar-description',
1013 ],
1014 ];
1015 }
1016
1017 if ( $modules ) {
1018 $resourceLoader->register( $modules );
1019 }
1020 }
1021
1022}
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(), $services->getContentLanguageCode() ->toString(), 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 { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} 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(), $services->get( 'Translate:HookRunner'),);}, '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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
Groups multiple message groups together as one group.
Hooks for Translate extension.
static onAbuseFilterAlterVariables(VariableHolder &$vars, Title $title, User $user)
Used for setting an AbuseFilter variable.
onUserGetReservedNames(&$reservedUsernames)
Prevents anyone from registering or logging in as FuzzyBot @inheritDoc.
static onPageContentLanguage(Title $title, &$pageLang)
Hook: PageContentLanguage Set the correct page content language for translation units.
static onAbuseFilterBuilder(array &$builderValues)
Register AbuseFilter variables provided by Translate.
static validateMessage(IContextSource $context, Content $content, Status $status, string $summary, User $user)
Runs the configured validator to ensure that the message meets the required criteria.
static searchProfileForm(SpecialSearch $search, string &$form, string $profile, string $term, array $opts)
Hook: SpecialSearchProfileForm.
static setupTranslate()
Do late setup that depends on configuration.
static onAbuseFilterComputeVariable(string $method, VariableHolder $vars, array $parameters, ?string &$result)
Computes the translate_source_text and translate_target_language AbuseFilter variables.
onRevisionRecordInserted( $revisionRecord)
@inheritDoc
static addConfig(array &$vars, OutputPage $out)
Hook: MakeGlobalVariablesScript Adds $wgTranslateDocumentationLanguageCode to ResourceLoader configur...
static onAdminLinks(ALTree $tree)
Hook: AdminLinks.
static preventCategorization(Parser $parser, string &$html)
Hook: ParserAfterTidy.
static setupParserHooks(Parser $parser)
Hook: ParserFirstCallInit Registers <languages> tag with the parser.
static searchProfile(array &$profiles)
Hook: SpecialSearchProfiles.
static showFakeCategories(OutputPage $outputPage, ParserOutput $parserOutput)
Hook: OutputPageParserOutput.
static onTitleIsAlwaysKnown( $target, &$isKnown)
Hook: TitleIsAlwaysKnown Make Special:MyLanguage links red if the target page doesn't exist.
static searchProfileSetupEngine(SpecialSearch $search, string $profile, SearchEngine $engine)
Hook: SpecialSearchSetupEngine.
static onMergeAccountFromTo(User $oldUser, User $newUser)
Hook: MergeAccountFromTo For UserMerge extension.
static onDeleteAccount(User $oldUser)
Hook: DeleteAccount For UserMerge extension.
static translateMessageDocumentationLanguage(array &$names, ?string $code)
Hook: LanguageGetTranslatedLanguageNames Hook: TranslateSupportedLanguages.
Class for formatting Translate logs.
Class to manage revision tags for translatable bundles.
Message object where you can directly set the translation.
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...
Special page which enables deleting translations of translatable bundles and translation pages.
Contains code for Special:PageMigration to migrate to page transation.
A special page for marking revisions of pages 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...
FuzzyBot - the misunderstood workhorse.
Definition FuzzyBot.php:15
Various editing enhancements to the edit page interface.
WebAPI module for storing translations for users who are in a sandbox.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:29
Interface for TtmServer that can act as backend for translation search.