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;
12use MediaWiki\Languages\LanguageNameUtils;
13use MediaWiki\Linker\LinkTarget;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Revision\RevisionLookup;
16use MediaWiki\Revision\RevisionRecord;
17use MediaWiki\Revision\SlotRecord;
20use MWException;
21use RuntimeException;
22use SpecialPage;
23use TextContent;
24use Title;
26use Wikimedia\Rdbms\Database;
27use Wikimedia\Rdbms\IResultWrapper;
29
41 public const METADATA_KEYS = [
42 'maxid',
43 'priorityforce',
44 'prioritylangs',
45 'priorityreason',
46 'transclusion',
47 'version'
48 ];
50 public const DISPLAY_TITLE_UNIT_ID = 'Page display title';
51
53 protected $title;
55 protected $revTagStore;
57 protected $text;
59 protected $revision;
61 protected $source;
63 protected $pageDisplayTitle;
65 private $targetLanguage;
66
68 protected function __construct( Title $title ) {
69 $this->title = $title;
70 $this->revTagStore = new RevTagStore();
71 }
72
78 public static function newFromText( Title $title, string $text ): self {
79 $obj = new self( $title );
80 $obj->text = $text;
81 $obj->source = 'text';
82
83 return $obj;
84 }
85
92 public static function newFromRevision( Title $title, int $revision ): self {
93 $rev = MediaWikiServices::getInstance()
94 ->getRevisionLookup()
95 ->getRevisionByTitle( $title, $revision );
96 if ( $rev === null ) {
97 throw new MWException( 'Revision is null' );
98 }
99
100 $obj = new self( $title );
101 $obj->source = 'revision';
102 $obj->revision = $revision;
103
104 return $obj;
105 }
106
111 public static function newFromTitle( Title $title ): self {
112 $obj = new self( $title );
113 $obj->source = 'title';
114
115 return $obj;
116 }
117
119 public function getTitle(): Title {
120 return $this->title;
121 }
122
124 public function getText(): string {
125 if ( $this->text !== null ) {
126 return $this->text;
127 }
128
129 $page = $this->getTitle()->getPrefixedDBkey();
130
131 if ( $this->source === 'title' ) {
132 $revision = $this->getMarkedTag();
133 if ( !is_int( $revision ) ) {
134 throw new LogicException(
135 "Trying to load a text for $page which is not marked for translation"
136 );
137 }
138 $this->revision = $revision;
139 }
140
141 $flags = Utilities::shouldReadFromPrimary()
142 ? RevisionLookup::READ_LATEST
143 : RevisionLookup::READ_NORMAL;
144 $rev = MediaWikiServices::getInstance()
145 ->getRevisionLookup()
146 ->getRevisionByTitle( $this->getTitle(), $this->revision, $flags );
147 $content = $rev->getContent( SlotRecord::MAIN );
148 $text = ( $content instanceof TextContent ) ? $content->getText() : null;
149
150 if ( !is_string( $text ) ) {
151 throw new RuntimeException( "Failed to load text for $page" );
152 }
153
154 $this->text = $text;
155
156 return $this->text;
157 }
158
163 public function getRevision(): ?int {
164 return $this->revision;
165 }
166
172 public function getSourceLanguageCode(): string {
173 return $this->getTitle()->getPageLanguage()->getCode();
174 }
175
177 public function getMessageGroupId(): string {
178 return self::getMessageGroupIdFromTitle( $this->getTitle() );
179 }
180
182 public static function getMessageGroupIdFromTitle( Title $title ): string {
183 return 'page-' . $title->getPrefixedText();
184 }
185
191 $groupId = $this->getMessageGroupId();
192 $group = MessageGroups::getGroup( $groupId );
193 if ( !$group || $group instanceof WikiPageMessageGroup ) {
194 return $group;
195 }
196
197 throw new RuntimeException(
198 "Expected $groupId to be of type WikiPageMessageGroup; got " .
199 get_class( $group )
200 );
201 }
202
204 public function hasPageDisplayTitle(): bool {
205 // Cached value
206 if ( $this->pageDisplayTitle !== null ) {
207 return $this->pageDisplayTitle;
208 }
209
210 // Check if title section exists in list of sections
211 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
212 $store = $factory->getReader( $this->getTitle() );
213 $this->pageDisplayTitle = in_array( self::DISPLAY_TITLE_UNIT_ID, $store->getNames() );
214
215 return $this->pageDisplayTitle;
216 }
217
219 public function getPageDisplayTitle( string $languageCode ): ?string {
220 // Return null if title not marked for translation
221 if ( !$this->hasPageDisplayTitle() ) {
222 return null;
223 }
224
225 // Display title from DB
226 $section = str_replace( ' ', '_', self::DISPLAY_TITLE_UNIT_ID );
227 $page = $this->getTitle()->getPrefixedDBkey();
228
229 try {
230 $group = $this->getMessageGroup();
231 } catch ( RuntimeException $e ) {
232 return null;
233 }
234
235 // Sanity check, seems to happen during moves
236 if ( !$group ) {
237 return null;
238 }
239
240 return $group->getMessage( "$page/$section", $languageCode, $group::READ_NORMAL );
241 }
242
243 public function getStrippedSourcePageText(): string {
244 $parser = Services::getInstance()->getTranslatablePageParser();
245 $text = $parser->cleanupTags( $this->getText() );
246 $text = preg_replace( '~<languages\s*/>\n?~s', '', $text );
247
248 return $text;
249 }
250
251 public static function getTranslationPageFromTitle( Title $title ): ?TranslationPage {
252 $self = self::isTranslationPage( $title );
253 if ( !$self ) {
254 return null;
255 }
256
257 return $self->getTranslationPage( $self->targetLanguage );
258 }
259
260 public function getTranslationPage( string $targetLanguage ): TranslationPage {
261 $mwServices = MediaWikiServices::getInstance();
262 $config = $mwServices->getMainConfig();
263 $parser = Services::getInstance()->getTranslatablePageParser();
264 $parserOutput = $parser->parse( $this->getText() );
265 $pageVersion = (int)TranslateMetadata::get( $this->getMessageGroupId(), 'version' );
266 $wrapUntranslated = $pageVersion >= 2;
267 $languageFactory = $mwServices->getLanguageFactory();
268
269 return new TranslationPage(
270 $parserOutput,
271 $this->getMessageGroup(),
272 $languageFactory->getLanguage( $targetLanguage ),
273 $languageFactory->getLanguage( $this->getSourceLanguageCode() ),
274 $config->get( 'TranslateKeepOutdatedTranslations' ),
275 $wrapUntranslated,
276 $this->getTitle()
277 );
278 }
279
280 protected static $tagCache = [];
281
283 public function addMarkedTag( int $revision, array $value = null ) {
284 $this->revTagStore->replaceTag( $this->getTitle(), RevTagStore::TP_MARK_TAG, $revision, $value );
285 self::clearSourcePageCache();
286 }
287
289 public function addReadyTag( int $revision ): void {
290 $this->revTagStore->replaceTag( $this->getTitle(), RevTagStore::TP_READY_TAG, $revision );
291 if ( !self::isSourcePage( $this->getTitle() ) ) {
292 self::clearSourcePageCache();
293 }
294 }
295
297 public function getMarkedTag(): ?int {
298 return $this->revTagStore->getLatestRevisionWithTag( $this->getTitle(), RevTagStore::TP_MARK_TAG );
299 }
300
302 public function getReadyTag(): ?int {
303 return $this->revTagStore->getLatestRevisionWithTag( $this->getTitle(), RevTagStore::TP_READY_TAG );
304 }
305
310 public function unmarkTranslatablePage(): void {
311 $tpPageStore = Services::getInstance()->getTranslatablePageStore();
312 $tpPageStore->unmark( $this->getTitle() );
313 }
314
320 public function getTranslationUrl( $code = false ): string {
321 $params = [
322 'group' => $this->getMessageGroupId(),
323 'action' => 'page',
324 'filter' => '',
325 'language' => $code,
326 ];
327
328 $translate = SpecialPage::getTitleFor( 'Translate' );
329
330 return $translate->getLocalURL( $params );
331 }
332
333 public function getMarkedRevs(): IResultWrapper {
334 $db = Utilities::getSafeReadDB();
335
336 $fields = [ 'rt_revision', 'rt_value' ];
337 $conds = [
338 'rt_page' => $this->getTitle()->getArticleID(),
339 'rt_type' => RevTagStore::TP_MARK_TAG,
340 ];
341 $options = [ 'ORDER BY' => 'rt_revision DESC' ];
342
343 return $db->select( 'revtag', $fields, $conds, __METHOD__, $options );
344 }
345
347 public function getTranslationPages(): array {
348 $mwServices = MediaWikiServices::getInstance();
349
350 $messageGroup = $this->getMessageGroup();
351 $knownLanguageCodes = $messageGroup ? $messageGroup->getTranslatableLanguages() : null;
352 $knownLanguageCodes = $knownLanguageCodes ?? Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS );
353
354 $prefixedDbTitleKey = $this->getTitle()->getDBkey() . '/';
355 $baseNamespace = $this->getTitle()->getNamespace();
356
357 // Build a link batch query for all translation pages
358 $linkBatch = $mwServices->getLinkBatchFactory()->newLinkBatch();
359 foreach ( array_keys( $knownLanguageCodes ) as $code ) {
360 $linkBatch->add( $baseNamespace, $prefixedDbTitleKey . $code );
361 }
362
363 $translationPages = [];
364 foreach ( $linkBatch->getPageIdentities() as $pageIdentity ) {
365 if ( $pageIdentity->exists() ) {
366 $translationPages[] = Title::castFromPageIdentity( $pageIdentity );
367 }
368 }
369
370 return $translationPages;
371 }
372
374 public function getTranslationUnitPages( ?string $code = null ): array {
375 return $this->getTranslationUnitPagesByTitle( $this->title, $code );
376 }
377
378 public function getTranslationPercentages(): array {
379 // Calculate percentages for the available translations
380 try {
381 $group = $this->getMessageGroup();
382 } catch ( RuntimeException $e ) {
383 return [];
384 }
385
386 if ( !$group ) {
387 return [];
388 }
389
390 $titles = $this->getTranslationPages();
391 $temp = MessageGroupStats::forGroup( $this->getMessageGroupId() );
392 $stats = [];
393
394 foreach ( $titles as $t ) {
395 $handle = new MessageHandle( $t );
396 $code = $handle->getCode();
397
398 // Sometimes we want to display 0.00 for pages for which translation
399 // hasn't started yet.
400 $stats[$code] = 0.00;
401 if ( ( $temp[$code][MessageGroupStats::TOTAL] ?? 0 ) > 0 ) {
402 $total = $temp[$code][MessageGroupStats::TOTAL];
403 $translated = $temp[$code][MessageGroupStats::TRANSLATED];
404 $percentage = $translated / $total;
405 $stats[$code] = sprintf( '%.2f', $percentage );
406 }
407 }
408
409 // Content language is always up-to-date
410 $stats[$this->getSourceLanguageCode()] = 1.00;
411
412 return $stats;
413 }
414
415 public function getTransRev( string $suffix ) {
416 $title = Title::makeTitle( NS_TRANSLATIONS, $suffix );
417
418 $db = Utilities::getSafeReadDB();
419 $fields = 'rt_value';
420 $conds = [
421 'rt_page' => $title->getArticleID(),
422 'rt_type' => RevTagStore::TRANSVER_PROP,
423 ];
424 $options = [ 'ORDER BY' => 'rt_revision DESC' ];
425
426 return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options );
427 }
428
429 public function supportsTransclusion(): ?bool {
430 $transclusion = TranslateMetadata::get( $this->getMessageGroupId(), 'transclusion' );
431 if ( $transclusion === false ) {
432 return null;
433 }
434
435 return $transclusion === '1';
436 }
437
438 public function setTransclusion( bool $supportsTransclusion ): void {
440 $this->getMessageGroupId(),
441 'transclusion',
442 $supportsTransclusion ? '1' : '0'
443 );
444 }
445
446 public function getRevisionRecordWithFallback(): ?RevisionRecord {
447 $title = $this->getTitle();
448 $store = MediaWikiServices::getInstance()->getRevisionStore();
449 $revRecord = $store->getRevisionByTitle( $title->getSubpage( $this->targetLanguage ) );
450 if ( $revRecord ) {
451 return $revRecord;
452 }
453
454 // Fetch the source fallback
455 $messageGroup = $this->getMessageGroup();
456 if ( !$messageGroup ) {
457 return null;
458 }
459
460 $sourceLanguage = $messageGroup->getSourceLanguage();
461 return $store->getRevisionByTitle( $title->getSubpage( $sourceLanguage ) );
462 }
463
465 public function isMoveable(): bool {
466 return $this->getMarkedTag() !== null;
467 }
468
470 public function isDeletable(): bool {
471 return $this->getMarkedTag() !== null;
472 }
473
475 public static function isTranslationPage( Title $title ) {
476 $handle = new MessageHandle( $title );
477 $key = $handle->getKey();
478 $code = $handle->getCode();
479
480 if ( $key === '' || $code === '' ) {
481 return false;
482 }
483
484 $codes = MediaWikiServices::getInstance()->getLanguageNameUtils()->getLanguageNames();
485 global $wgTranslateDocumentationLanguageCode;
486 unset( $codes[$wgTranslateDocumentationLanguageCode] );
487
488 if ( !isset( $codes[$code] ) ) {
489 return false;
490 }
491
492 $newtitle = self::changeTitleText( $title, $key );
493
494 if ( !$newtitle ) {
495 return false;
496 }
497
498 $page = self::newFromTitle( $newtitle );
499
500 if ( $page->getMarkedTag() === null ) {
501 return false;
502 }
503
504 $page->targetLanguage = $code;
505
506 return $page;
507 }
508
509 private static function changeTitleText( Title $title, string $text ): ?Title {
510 return Title::makeTitleSafe( $title->getNamespace(), $text );
511 }
512
514 public static function parseTranslationUnit( LinkTarget $translationUnit ): array {
515 // Format is Translations:SourcePageNamespace:SourcePageName/SectionName/LanguageCode.
516 // We will drop the namespace immediately here.
517 $parts = explode( '/', $translationUnit->getText() );
518
519 // LanguageCode and SectionName are guaranteed to not have '/'.
520 $language = array_pop( $parts );
521 $section = array_pop( $parts );
522 $sourcepage = implode( '/', $parts );
523
524 return [
525 'sourcepage' => $sourcepage,
526 'section' => $section,
527 'language' => $language
528 ];
529 }
530
531 public static function isSourcePage( Title $title ): bool {
532 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
533 $cacheKey = $cache->makeKey( 'pagetranslation', 'sourcepages' );
534
535 $translatablePageIds = $cache->getWithSetCallback(
536 $cacheKey,
537 $cache::TTL_HOUR * 2,
538 static function ( $oldValue, &$ttl, array &$setOpts ) {
539 $dbr = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_REPLICA );
540 $setOpts += Database::getCacheSetOptions( $dbr );
541
542 return RevTagStore::getTranslatableBundleIds(
543 RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG
544 );
545 },
546 [
547 'checkKeys' => [ $cacheKey ],
548 'pcTTL' => $cache::TTL_PROC_SHORT,
549 'pcGroup' => __CLASS__ . ':1'
550 ]
551 );
552
553 return in_array( $title->getArticleID(), $translatablePageIds );
554 }
555
557 public static function clearSourcePageCache(): void {
558 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
559 $cache->touchCheckKey( $cache->makeKey( 'pagetranslation', 'sourcepages' ) );
560 }
561
562 public static function determineStatus(
563 ?int $readyRevisionId,
564 ?int $markRevisionId,
565 int $latestRevisionId
567 $status = null;
568 if ( $markRevisionId === null ) {
569 // Never marked, check that the latest version is ready
570 if ( $readyRevisionId === $latestRevisionId ) {
571 $status = TranslatablePageStatus::PROPOSED;
572 } else {
573 // Otherwise, ignore such pages
574 return null;
575 }
576 } elseif ( $readyRevisionId === $latestRevisionId ) {
577 if ( $markRevisionId === $readyRevisionId ) {
578 // Marked and latest version is fine
579 $status = TranslatablePageStatus::ACTIVE;
580 } else {
581 $status = TranslatablePageStatus::OUTDATED;
582 }
583 } else {
584 // Marked but latest version is not fine
585 $status = TranslatablePageStatus::BROKEN;
586 }
587
588 return new TranslatablePageStatus( $status );
589 }
590}
591
592class_alias( TranslatablePage::class, 'TranslatablePage' );
return[ '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:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, '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->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, '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->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@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...
Stores and validates possible statuses for TranslatablePage.
Mixed bag of methods related to translatable pages.
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(Title $title)
Constructs MessageGroup id for any title.
static newFromRevision(Title $title, int $revision)
Constructs a translatable page from given revision.
getReadyTag()
Returns the latest revision which has ready tag, if any.
addMarkedTag(int $revision, array $value=null)
Adds a tag which indicates that this page is suitable for translation.
getRevision()
Revision is null if object was constructed using newFromText.
static newFromTitle(Title $title)
Constructs a translatable page from title.
getSourceLanguageCode()
Returns the source language of this translatable page.
unmarkTranslatablePage()
Removes all page translation feature data from the database.
getTranslationUrl( $code=false)
Produces a link to translation view of a translation page.
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 parseTranslationUnit(LinkTarget $translationUnit)
Helper to guess translation page from translation unit.
static newFromText(Title $title, string $text)
Constructs a translatable page from given text.
getPageDisplayTitle(string $languageCode)
Get translated page title.
Minimal service container.
Definition Services.php:40
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
This class abstract MessageGroup statistics calculation and storing.
Class for pointing to messages, like Title class is for titles.
Wraps the translatable page sections into a message group.