50 public const DISPLAY_TITLE_UNIT_ID =
'Page display title';
55 protected $revTagStore;
63 protected $pageDisplayTitle;
65 private $targetLanguage;
69 $this->title = $title;
78 public static function newFromText( Title $title,
string $text ): self {
79 $obj = new self( $title );
81 $obj->source =
'text';
93 $rev = MediaWikiServices::getInstance()
95 ->getRevisionByTitle( $title, $revision );
96 if ( $rev ===
null ) {
97 throw new MWException(
'Revision is null' );
100 $obj =
new self( $title );
101 $obj->source =
'revision';
102 $obj->revision = $revision;
112 $obj = new self( $title );
113 $obj->source =
'title';
125 if ( $this->text !== null ) {
129 $page = $this->getTitle()->getPrefixedDBkey();
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"
138 $this->revision = $revision;
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;
150 if ( !is_string( $text ) ) {
151 throw new RuntimeException(
"Failed to load text for $page" );
173 return $this->getTitle()->getPageLanguage()->getCode();
178 return self::getMessageGroupIdFromTitle( $this->getTitle() );
183 return 'page-' . $title->getPrefixedText();
191 $groupId = $this->getMessageGroupId();
192 $group = MessageGroups::getGroup( $groupId );
197 throw new RuntimeException(
198 "Expected $groupId to be of type WikiPageMessageGroup; got " .
206 if ( $this->pageDisplayTitle !== null ) {
207 return $this->pageDisplayTitle;
211 $factory = Services::getInstance()->getTranslationUnitStoreFactory();
212 $store = $factory->getReader( $this->getTitle() );
213 $this->pageDisplayTitle = in_array( self::DISPLAY_TITLE_UNIT_ID, $store->getNames() );
215 return $this->pageDisplayTitle;
221 if ( !$this->hasPageDisplayTitle() ) {
226 $section = str_replace(
' ',
'_', self::DISPLAY_TITLE_UNIT_ID );
227 $page = $this->getTitle()->getPrefixedDBkey();
230 $group = $this->getMessageGroup();
231 }
catch ( RuntimeException $e ) {
240 return $group->getMessage(
"$page/$section", $languageCode, $group::READ_NORMAL );
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 );
251 public static function getTranslationPageFromTitle( Title $title ): ?TranslationPage {
252 $self = self::isTranslationPage( $title );
257 return $self->getTranslationPage( $self->targetLanguage );
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();
269 return new TranslationPage(
271 $this->getMessageGroup(),
272 $languageFactory->getLanguage( $targetLanguage ),
273 $languageFactory->getLanguage( $this->getSourceLanguageCode() ),
274 $config->get(
'TranslateKeepOutdatedTranslations' ),
280 protected static $tagCache = [];
284 $this->revTagStore->replaceTag( $this->getTitle(), RevTagStore::TP_MARK_TAG, $revision, $value );
285 self::clearSourcePageCache();
290 $this->revTagStore->replaceTag( $this->getTitle(),
RevTagStore::TP_READY_TAG, $revision );
291 if ( !self::isSourcePage( $this->getTitle() ) ) {
292 self::clearSourcePageCache();
298 return $this->revTagStore->getLatestRevisionWithTag( $this->getTitle(),
RevTagStore::TP_MARK_TAG );
303 return $this->revTagStore->getLatestRevisionWithTag( $this->getTitle(),
RevTagStore::TP_READY_TAG );
311 $tpPageStore =
Services::getInstance()->getTranslatablePageStore();
312 $tpPageStore->unmark( $this->getTitle() );
322 'group' => $this->getMessageGroupId(),
328 $translate = SpecialPage::getTitleFor(
'Translate' );
330 return $translate->getLocalURL( $params );
333 public function getMarkedRevs(): IResultWrapper {
336 $fields = [
'rt_revision',
'rt_value' ];
338 'rt_page' => $this->getTitle()->getArticleID(),
339 'rt_type' => RevTagStore::TP_MARK_TAG,
341 $options = [
'ORDER BY' =>
'rt_revision DESC' ];
343 return $db->select(
'revtag', $fields, $conds, __METHOD__, $options );
348 $mwServices = MediaWikiServices::getInstance();
350 $messageGroup = $this->getMessageGroup();
351 $knownLanguageCodes = $messageGroup ? $messageGroup->getTranslatableLanguages() :
null;
352 $knownLanguageCodes = $knownLanguageCodes ?? Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS );
354 $prefixedDbTitleKey = $this->getTitle()->getDBkey() .
'/';
355 $baseNamespace = $this->getTitle()->getNamespace();
358 $linkBatch = $mwServices->getLinkBatchFactory()->newLinkBatch();
359 foreach ( array_keys( $knownLanguageCodes ) as $code ) {
360 $linkBatch->add( $baseNamespace, $prefixedDbTitleKey . $code );
363 $translationPages = [];
364 foreach ( $linkBatch->getPageIdentities() as $pageIdentity ) {
365 if ( $pageIdentity->exists() ) {
366 $translationPages[] = Title::castFromPageIdentity( $pageIdentity );
370 return $translationPages;
375 return $this->getTranslationUnitPagesByTitle( $this->title, $code );
378 public function getTranslationPercentages(): array {
381 $group = $this->getMessageGroup();
382 }
catch ( RuntimeException $e ) {
390 $titles = $this->getTranslationPages();
391 $temp = MessageGroupStats::forGroup( $this->getMessageGroupId() );
394 foreach ( $titles as $t ) {
396 $code = $handle->getCode();
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 );
410 $stats[$this->getSourceLanguageCode()] = 1.00;
415 public function getTransRev(
string $suffix ) {
416 $title = Title::makeTitle( NS_TRANSLATIONS, $suffix );
418 $db = Utilities::getSafeReadDB();
419 $fields =
'rt_value';
421 'rt_page' => $title->getArticleID(),
422 'rt_type' => RevTagStore::TRANSVER_PROP,
424 $options = [
'ORDER BY' =>
'rt_revision DESC' ];
426 return $db->selectField(
'revtag', $fields, $conds, __METHOD__, $options );
429 public function supportsTransclusion(): ?bool {
430 $transclusion =
TranslateMetadata::get( $this->getMessageGroupId(),
'transclusion' );
431 if ( $transclusion ===
false ) {
435 return $transclusion ===
'1';
438 public function setTransclusion(
bool $supportsTransclusion ): void {
440 $this->getMessageGroupId(),
442 $supportsTransclusion ?
'1' :
'0'
446 public function getRevisionRecordWithFallback(): ?RevisionRecord {
447 $title = $this->getTitle();
448 $store = MediaWikiServices::getInstance()->getRevisionStore();
449 $revRecord = $store->getRevisionByTitle( $title->getSubpage( $this->targetLanguage ) );
455 $messageGroup = $this->getMessageGroup();
456 if ( !$messageGroup ) {
460 $sourceLanguage = $messageGroup->getSourceLanguage();
461 return $store->getRevisionByTitle( $title->getSubpage( $sourceLanguage ) );
466 return $this->getMarkedTag() !== null;
471 return $this->getMarkedTag() !== null;
477 $key = $handle->getKey();
478 $code = $handle->getCode();
480 if ( $key ===
'' || $code ===
'' ) {
484 $codes = MediaWikiServices::getInstance()->getLanguageNameUtils()->getLanguageNames();
485 global $wgTranslateDocumentationLanguageCode;
486 unset( $codes[$wgTranslateDocumentationLanguageCode] );
488 if ( !isset( $codes[$code] ) ) {
492 $newtitle = self::changeTitleText( $title, $key );
498 $page = self::newFromTitle( $newtitle );
500 if ( $page->getMarkedTag() ===
null ) {
504 $page->targetLanguage = $code;
509 private static function changeTitleText( Title $title,
string $text ): ?Title {
510 return Title::makeTitleSafe( $title->getNamespace(), $text );
517 $parts = explode(
'/', $translationUnit->getText() );
520 $language = array_pop( $parts );
521 $section = array_pop( $parts );
522 $sourcepage = implode(
'/', $parts );
525 'sourcepage' => $sourcepage,
526 'section' => $section,
527 'language' => $language
531 public static function isSourcePage( Title $title ): bool {
532 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
533 $cacheKey = $cache->makeKey(
'pagetranslation',
'sourcepages' );
535 $translatablePageIds = $cache->getWithSetCallback(
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 );
542 return RevTagStore::getTranslatableBundleIds(
543 RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG
547 'checkKeys' => [ $cacheKey ],
548 'pcTTL' => $cache::TTL_PROC_SHORT,
549 'pcGroup' => __CLASS__ .
':1'
553 return in_array( $title->getArticleID(), $translatablePageIds );
558 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
559 $cache->touchCheckKey( $cache->makeKey(
'pagetranslation',
'sourcepages' ) );
562 public static function determineStatus(
563 ?
int $readyRevisionId,
564 ?
int $markRevisionId,
565 int $latestRevisionId
568 if ( $markRevisionId ===
null ) {
570 if ( $readyRevisionId === $latestRevisionId ) {
571 $status = TranslatablePageStatus::PROPOSED;
576 } elseif ( $readyRevisionId === $latestRevisionId ) {
577 if ( $markRevisionId === $readyRevisionId ) {
579 $status = TranslatablePageStatus::ACTIVE;
581 $status = TranslatablePageStatus::OUTDATED;
585 $status = TranslatablePageStatus::BROKEN;
588 return new TranslatablePageStatus( $status );
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