Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslatablePage.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\PageTranslation;
5
6use LogicException;
7use MediaWiki\Content\TextContent;
15use MediaWiki\Language\LanguageNameUtils;
16use MediaWiki\Linker\LinkTarget;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Page\PageIdentity;
19use MediaWiki\Page\PageReference;
20use MediaWiki\Revision\RevisionRecord;
21use MediaWiki\Revision\SlotRecord;
22use MediaWiki\SpecialPage\SpecialPage;
23use MediaWiki\Title\Title;
24use RuntimeException;
25use Wikimedia\Rdbms\IDBAccessObject;
27
39 public const METADATA_KEYS = [
40 'maxid',
41 'priorityforce',
42 'prioritylangs',
43 'priorityreason',
44 'transclusion',
45 'version'
46 ];
48 public const DISPLAY_TITLE_UNIT_ID = 'Page display title';
49
50 protected RevTagStore $revTagStore;
52 protected $text;
54 protected $revision;
56 protected $source;
58 protected $pageDisplayTitle;
60 private $targetLanguage;
61
62 protected function __construct(
63 private readonly PageIdentity $page,
64 ) {
65 $this->revTagStore = Services::getInstance()->getRevTagStore();
66 }
67
73 public static function newFromText( PageIdentity $page, string $text ): self {
74 $obj = new self( $page );
75 $obj->text = $text;
76 $obj->source = 'text';
77
78 return $obj;
79 }
80
86 public static function newFromRevision( PageIdentity $page, int $revision ): self {
87 $rev = MediaWikiServices::getInstance()
88 ->getRevisionLookup()
89 ->getRevisionByTitle( $page, $revision );
90 if ( $rev === null ) {
91 throw new RuntimeException( 'Revision is null' );
92 }
93
94 $obj = new self( $page );
95 $obj->source = 'revision';
96 $obj->revision = $revision;
97
98 return $obj;
99 }
100
105 public static function newFromTitle( PageIdentity $page ): self {
106 $obj = new self( $page );
107 $obj->source = 'title';
108
109 return $obj;
110 }
111
113 public function getTitle(): Title {
114 return Title::newFromPageIdentity( $this->page );
115 }
116
117 public function getPageIdentity(): PageIdentity {
118 return $this->page;
119 }
120
122 public function getText(): string {
123 if ( $this->text !== null ) {
124 return $this->text;
125 }
126
127 if ( $this->source === 'title' ) {
128 $revision = $this->getMarkedTag();
129 if ( !is_int( $revision ) ) {
130 throw new LogicException(
131 "Trying to load a text for {$this->getPageIdentity()} which is not marked for translation"
132 );
133 }
134 $this->revision = $revision;
135 }
136
137 $flags = Utilities::shouldReadFromPrimary()
138 ? IDBAccessObject::READ_LATEST
139 : IDBAccessObject::READ_NORMAL;
140 $rev = MediaWikiServices::getInstance()
141 ->getRevisionLookup()
142 ->getRevisionByTitle( $this->getPageIdentity(), $this->revision, $flags );
143 $content = $rev->getContent( SlotRecord::MAIN );
144 $text = ( $content instanceof TextContent ) ? $content->getText() : null;
145
146 if ( !is_string( $text ) ) {
147 throw new RuntimeException( "Failed to load text for {$this->getPageIdentity()}" );
148 }
149
150 $this->text = $text;
151
152 return $this->text;
153 }
154
159 public function getRevision(): ?int {
160 return $this->revision;
161 }
162
168 public function getSourceLanguageCode(): string {
169 return $this->getTitle()->getPageLanguage()->getCode();
170 }
171
173 public function getMessageGroupId(): string {
174 return self::getMessageGroupIdFromTitle( $this->getPageIdentity() );
175 }
176
178 public static function getMessageGroupIdFromTitle( PageReference $page ): string {
179 return 'page-' . MediaWikiServices::getInstance()->getTitleFormatter()->getPrefixedText( $page );
180 }
181
187 $groupId = $this->getMessageGroupId();
188 $group = MessageGroups::getGroup( $groupId );
189 if ( !$group || $group instanceof WikiPageMessageGroup ) {
190 return $group;
191 }
192
193 throw new RuntimeException(
194 "Expected $groupId to be of type WikiPageMessageGroup; got " .
195 get_class( $group )
196 );
197 }
198
200 public function hasPageDisplayTitle(): bool {
201 // Cached value
202 if ( $this->pageDisplayTitle !== null ) {
203 return $this->pageDisplayTitle;
204 }
205
206 // Check if title section exists in list of sections
207 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
208 $store = $factory->getReader( $this->getPageIdentity() );
209 $this->pageDisplayTitle = in_array( self::DISPLAY_TITLE_UNIT_ID, $store->getNames() );
210
211 return $this->pageDisplayTitle;
212 }
213
215 public function getPageDisplayTitle( string $languageCode ): ?string {
216 // Return null if title not marked for translation
217 if ( !$this->hasPageDisplayTitle() ) {
218 return null;
219 }
220
221 // Display title from DB
222 $section = str_replace( ' ', '_', self::DISPLAY_TITLE_UNIT_ID );
223 $page = MediaWikiServices::getInstance()->getTitleFormatter()->getPrefixedDBkey( $this->getPageIdentity() );
224
225 try {
226 $group = $this->getMessageGroup();
227 } catch ( RuntimeException ) {
228 return null;
229 }
230
231 // Sanity check, seems to happen during moves
232 if ( !$group ) {
233 return null;
234 }
235
236 return $group->getMessage( "$page/$section", $languageCode, IDBAccessObject::READ_NORMAL );
237 }
238
239 public function getStrippedSourcePageText(): string {
240 $parser = Services::getInstance()->getTranslatablePageParser();
241 $text = $parser->cleanupTags( $this->getText() );
242 $text = preg_replace( '~<languages\s*/>\n?~s', '', $text );
243
244 return $text;
245 }
246
247 public static function getTranslationPageFromTitle( Title $title ): ?TranslationPage {
248 $self = self::isTranslationPage( $title );
249 return $self ? $self->getTranslationPage( $self->targetLanguage ) : null;
250 }
251
252 public function getTranslationPage( string $targetLanguage ): TranslationPage {
253 $group = $this->getMessageGroup() ?? new WikiPageMessageGroup(
254 $this->getMessageGroupId(),
255 $this->getTitle()
256 );
257
258 $mwServices = MediaWikiServices::getInstance();
259 $config = $mwServices->getMainConfig();
260 $services = Services::getInstance();
261 $parser = $services->getTranslatablePageParser();
262 $parserOutput = $parser->parse( $this->getText() );
263 $pageVersion = (int)$services->getMessageGroupMetadata()
264 ->get( $this->getMessageGroupId(), 'version' );
265 $wrapUntranslated = $pageVersion >= 2;
266 $languageFactory = $mwServices->getLanguageFactory();
267
268 return new TranslationPage(
269 $parserOutput,
270 $group,
271 $languageFactory->getLanguage( $targetLanguage ),
272 $languageFactory->getLanguage( $this->getSourceLanguageCode() ),
273 $config->get( 'TranslateKeepOutdatedTranslations' ),
274 $wrapUntranslated,
275 $this->getTitle()
276 );
277 }
278
280 public function addMarkedTag( int $revision, ?array $value = null ) {
281 $this->revTagStore->replaceTag( $this->getPageIdentity(), RevTagStore::TP_MARK_TAG, $revision, $value );
282 self::clearSourcePageCache();
283 }
284
286 public function addReadyTag( int $revision ): void {
287 $this->revTagStore->replaceTag( $this->getPageIdentity(), RevTagStore::TP_READY_TAG, $revision );
288 if ( !self::isSourcePage( $this->getPageIdentity() ) ) {
289 self::clearSourcePageCache();
290 }
291 }
292
294 public function getMarkedTag(): ?int {
295 return $this->revTagStore->getLatestRevisionWithTag( $this->getPageIdentity(), RevTagStore::TP_MARK_TAG );
296 }
297
299 public function getReadyTag(): ?int {
300 return $this->revTagStore->getLatestRevisionWithTag( $this->getPageIdentity(), RevTagStore::TP_READY_TAG );
301 }
302
308 public function getTranslationUrl( $code = false ): string {
309 $params = [
310 'group' => $this->getMessageGroupId(),
311 'action' => 'page',
312 'filter' => '',
313 'language' => $code,
314 ];
315
316 $translate = SpecialPage::getTitleFor( 'Translate' );
317
318 return $translate->getLocalURL( $params );
319 }
320
322 public function getTranslationPages(): array {
323 $mwServices = MediaWikiServices::getInstance();
324
325 $knownLanguageCodes = $this->getMessageGroup()?->getTranslatableLanguages() ??
326 Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS );
327
328 $prefixedDbTitleKey = $this->getPageIdentity()->getDBkey() . '/';
329 $baseNamespace = $this->getPageIdentity()->getNamespace();
330
331 // Build a link batch query for all translation pages
332 $linkBatch = $mwServices->getLinkBatchFactory()->newLinkBatch();
333 foreach ( array_keys( $knownLanguageCodes ) as $code ) {
334 $linkBatch->add( $baseNamespace, $prefixedDbTitleKey . $code );
335 }
336
337 $translationPages = [];
338 foreach ( $linkBatch->getPageIdentities() as $pageIdentity ) {
339 if ( $pageIdentity->exists() ) {
340 $translationPages[] = Title::newFromPageIdentity( $pageIdentity );
341 }
342 }
343
344 return $translationPages;
345 }
346
348 public function getTranslationUnitPages( ?string $code = null ): array {
349 return $this->getTranslationUnitPagesByTitle( $this->page, $code );
350 }
351
352 public function getTranslationPercentages(): array {
353 // Calculate percentages for the available translations
354 try {
355 $group = $this->getMessageGroup();
356 } catch ( RuntimeException ) {
357 return [];
358 }
359
360 if ( !$group ) {
361 return [];
362 }
363
364 $titles = $this->getTranslationPages();
365 $temp = MessageGroupStats::forGroup( $this->getMessageGroupId(), MessageGroupStats::FLAG_CACHE_ONLY );
366 $stats = [];
367
368 foreach ( $titles as $t ) {
369 $handle = new MessageHandle( $t );
370 $code = $handle->getCode();
371
372 // Sometimes we want to display 0.00 for pages for which translation
373 // hasn't started yet.
374 $stats[$code] = 0.00;
375 if ( ( $temp[$code][MessageGroupStats::TOTAL] ?? 0 ) > 0 ) {
376 $total = $temp[$code][MessageGroupStats::TOTAL];
377 $translated = $temp[$code][MessageGroupStats::TRANSLATED];
378 $percentage = $translated / $total;
379 $stats[$code] = sprintf( '%.2f', $percentage );
380 }
381 }
382
383 // Content language is always up-to-date
384 $stats[$this->getSourceLanguageCode()] = 1.00;
385
386 return $stats;
387 }
388
389 public function supportsTransclusion(): ?bool {
390 $transclusion = Services::getInstance()
391 ->getMessageGroupMetadata()
392 ->get( $this->getMessageGroupId(), 'transclusion' );
393 if ( $transclusion === false ) {
394 return null;
395 }
396
397 return $transclusion === '1';
398 }
399
400 public function getRevisionRecordWithFallback(): ?RevisionRecord {
401 $title = $this->getTitle();
402 $store = MediaWikiServices::getInstance()->getRevisionStore();
403 $revRecord = $store->getRevisionByTitle( $title->getSubpage( $this->targetLanguage ) );
404 if ( $revRecord ) {
405 return $revRecord;
406 }
407
408 // Fetch the source fallback
409 return $store->getRevisionByTitle( $title->getSubpage( $this->getSourceLanguageCode() ) );
410 }
411
413 public function isMoveable(): bool {
414 return $this->getMarkedTag() !== null;
415 }
416
418 public function isDeletable(): bool {
419 return $this->getMarkedTag() !== null;
420 }
421
423 public static function isTranslationPage( Title $title ) {
424 $handle = new MessageHandle( $title );
425 if ( !Utilities::isTranslationPage( $handle ) ) {
426 return false;
427 }
428
429 $languageCode = $handle->getCode();
430 $newTitle = $handle->getTitleForBase();
431
432 $page = self::newFromTitle( $newTitle );
433
434 if ( $page->getMarkedTag() === null ) {
435 return false;
436 }
437
438 $page->targetLanguage = $languageCode;
439
440 return $page;
441 }
442
444 public static function parseTranslationUnit( LinkTarget $translationUnit ): array {
445 // Format is Translations:SourcePageNamespace:SourcePageName/SectionName/LanguageCode.
446 // We will drop the namespace immediately here.
447 $parts = explode( '/', $translationUnit->getText() );
448
449 // LanguageCode and SectionName are guaranteed to not have '/'.
450 $language = array_pop( $parts );
451 $section = array_pop( $parts );
452 $sourcepage = implode( '/', $parts );
453
454 return [
455 'sourcepage' => $sourcepage,
456 'section' => $section,
457 'language' => $language
458 ];
459 }
460
461 public static function isSourcePage( PageIdentity $page ): bool {
462 if ( !$page->exists() ) {
463 // No point in loading all translatable pages if the page
464 // doesn’t exist. This also avoids PreconditionExceptions
465 // if $page is a Title pointing to a non-proper page like
466 // a special page.
467 return false;
468 }
469
470 $localCache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
471 $localKey = $localCache->makeKey( 'pagetranslation', 'sourcepages', 'local' );
472 // Store the value in the local cache for a short duration to reduce the number of
473 // times we hit the WAN cache. See: T366455
474 $translatablePageIds = $localCache->getWithSetCallback(
475 $localKey,
476 $localCache::TTL_SECOND * 8,
477 static function () {
478 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
479 $cacheKey = $cache->makeKey( 'pagetranslation', 'sourcepages' );
480
481 return $cache->getWithSetCallback(
482 $cacheKey,
483 $cache::TTL_HOUR * 2,
484 [ TranslatablePage::class, 'getCacheValue' ],
485 [
486 'checkKeys' => [ $cacheKey ],
487 'pcTTL' => $cache::TTL_PROC_SHORT,
488 'pcGroup' => __CLASS__ . ':1',
489 'version' => 3,
490 ]
491 );
492 },
493 $localCache::READ_LATEST
494 );
495
496 return str_contains( $translatablePageIds, ( ',' . $page->getId() . ',' ) );
497 }
498
499 public function isBroken(): bool {
500 $marked = $this->getMarkedTag();
501 $ready = $this->getReadyTag();
502 $latest = $this->getTitle()->getLatestRevID();
503 return $marked !== null && $marked !== $latest && $ready !== $latest;
504 }
505
507 public static function clearSourcePageCache(): void {
508 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
509 $cache->touchCheckKey( $cache->makeKey( 'pagetranslation', 'sourcepages' ) );
510 }
511
512 public static function determineStatus(
513 ?int $readyRevisionId,
514 ?int $markRevisionId,
515 int $latestRevisionId
517 $status = null;
518 if ( $markRevisionId === null ) {
519 // Never marked, check that the latest version is ready
520 if ( $readyRevisionId === $latestRevisionId ) {
521 $status = TranslatablePageStatus::PROPOSED;
522 } else {
523 // Otherwise, ignore such pages
524 return null;
525 }
526 } elseif ( $readyRevisionId === $latestRevisionId ) {
527 if ( $markRevisionId === $readyRevisionId ) {
528 // Marked and latest version is fine
529 $status = TranslatablePageStatus::ACTIVE;
530 } else {
531 $status = TranslatablePageStatus::OUTDATED;
532 }
533 } else {
534 // Marked but latest version is not fine
535 $status = TranslatablePageStatus::BROKEN;
536 }
537
538 return new TranslatablePageStatus( $status );
539 }
540
546 public static function getCacheValue(): string {
547 $ids = RevTagStore::getTranslatableBundleIds(
548 // Find pages where the latest revision can be marked for translation ...
549 [ RevTagStore::TP_READY_TAG ],
550 // ... or any revision has been marked for translation
551 [ RevTagStore::TP_MARK_TAG ]
552 );
553
554 // Adding a comma at the end and beginning so that we can check for page ID
555 // existence with the "," delimiters
556 return ',' . implode( ',', $ids ) . ',';
557 }
558}
559
560class_alias( TranslatablePage::class, 'TranslatablePage' );
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
Factory class for accessing message groups individually by id or all of them as a list.
Class to manage revision tags for translatable bundles.
Translatable bundle represents a message group where its translatable content is defined on a wiki pa...
Class for pointing to messages, like Title class is for titles.
Stores and validates possible statuses for TranslatablePage.
Mixed bag of methods related to translatable pages.
static newFromTitle(PageIdentity $page)
Constructs a translatable page from title.
addReadyTag(int $revision)
Adds a tag which indicates that this page source is ready for marking for translation.
getMarkedTag()
Returns the latest revision which has marked tag, if any.
getText()
Returns the text for this translatable page.
hasPageDisplayTitle()
Check whether title is marked for translation.
static getMessageGroupIdFromTitle(PageReference $page)
Constructs MessageGroup id for any title.
getReadyTag()
Returns the latest revision which has ready tag, if any.
getRevision()
Revision is null if object was constructed using newFromText.
getSourceLanguageCode()
Returns the source language of this translatable page.
getTranslationUrl( $code=false)
Produces a link to translation view of a translation page.
static newFromText(PageIdentity $page, string $text)
Constructs a translatable page from given text.
getMessageGroup()
Returns MessageGroup used for translating this page.
const METADATA_KEYS
List of keys in the metadata table that need to be handled for moves and deletions @phpcs-require-sor...
static newFromRevision(PageIdentity $page, int $revision)
Constructs a translatable page from given revision.
static parseTranslationUnit(LinkTarget $translationUnit)
Helper to guess translation page from translation unit.
addMarkedTag(int $revision, ?array $value=null)
Adds a tag which indicates that this page is suitable for translation.
static getCacheValue()
Get list of translatable page ids to be stored in the cache.
getPageDisplayTitle(string $languageCode)
Get translated page title.
Minimal service container.
Definition Services.php:60
This class aims to provide efficient mechanism for fetching translation completion stats.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Wraps the translatable page sections into a message group.