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