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