2declare( strict_types = 1 );
12use MediaWiki\Languages\LanguageNameUtils;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Revision\RevisionRecord;
15use MediaWiki\Revision\SlotRecord;
21use UnexpectedValueException;
22use Wikimedia\Rdbms\IDatabase;
40 public static function title(
string $message,
string $code,
int $ns = NS_MEDIAWIKI ): string {
43 $key = $ns .
':' . $message;
45 if ( !isset( $cache[$key] ) ) {
46 $cache[$key] = Title::capitalize( $message, $ns );
50 return $cache[$key] .
'/' . $code;
63 $pos = strrpos( $text,
'/' );
64 $code = substr( $text, $pos + 1 );
65 $key = substr( $text, 0, $pos );
67 return [ $key, $code ];
77 public static function getMessageContent(
string $key,
string $language,
int $namespace = NS_MEDIAWIKI ): ?string {
78 $title = self::title( $key, $language, $namespace );
79 $data = self::getContents( [ $title ], $namespace );
81 return $data[$title][0] ??
null;
92 public static function getContents( $titles,
int $namespace ): array {
93 $mwServices = MediaWikiServices::getInstance();
94 $dbr = $mwServices->getDBLoadBalancer()->getConnection( DB_REPLICA );
95 $revStore = $mwServices->getRevisionStore();
98 $query = $revStore->getQueryInfo( [
'page',
'user' ] );
103 'page_namespace' => $namespace,
104 'page_title' => $titles,
105 'page_latest=rev_id',
112 $revisions = $revStore->newRevisionsFromBatch( $rows, [
117 foreach ( $rows as $row ) {
119 $rev = $revisions[$row->rev_id];
122 $content = $rev->getContent( SlotRecord::MAIN );
124 $titleContents[$row->page_title] = [
134 return $titleContents;
144 $store = MediaWikiServices::getInstance()->getRevisionStore();
145 $revision = $store->getRevisionByTitle( $title );
147 if ( $revision ===
null ) {
151 $content = $revision->getContent( SlotRecord::MAIN );
152 $wiki = ( $content instanceof TextContent ) ? $content->getText() :
null;
155 if ( $wiki ===
null ) {
161 if ( $handle->isFuzzy() ) {
162 $wiki = TRANSLATE_FUZZY . str_replace( TRANSLATE_FUZZY,
'', $wiki );
177 public static function getLanguageName(
string $code, ?
string $language =
'en' ): string {
178 $languages = self::getLanguageNames( $language );
179 return $languages[$code] ?? $code;
191 $selector = self::getLanguageSelector( $language );
192 $selector->setDefault( $selectedId );
193 $selector->setAttribute(
'id',
'language' );
194 $selector->setAttribute(
'name',
'language' );
196 return $selector->getHTML();
206 $languages = self::getLanguageNames( $language );
209 $selector =
new XmlSelect();
210 if ( $labelOption !==
null ) {
211 $selector->addOption( $labelOption,
'-' );
214 foreach ( $languages as $code => $name ) {
215 $selector->addOption(
"$code - $name", $code );
229 $mwServices = MediaWikiServices::getInstance();
230 $languageNames = $mwServices->getLanguageNameUtils()->getLanguageNames( $code );
232 $deprecatedCodes = LanguageCode::getDeprecatedCodeMapping();
233 foreach ( array_keys( $deprecatedCodes ) as $deprecatedCode ) {
234 unset( $languageNames[ $deprecatedCode ] );
236 Services::getInstance()->getHookRunner()->onTranslateSupportedLanguages( $languageNames, $code );
238 return $languageNames;
248 $groups = self::messageKeyToGroups( $namespace, $key );
250 return count( $groups ) ? $groups[0] :
null;
258 $mi =
Services::getInstance()->getMessageIndex()->retrieve();
259 $normkey = self::normaliseKey( $namespace, $key );
261 if ( isset( $mi[$normkey] ) ) {
262 return (array)$mi[$normkey];
269 public static function normaliseKey(
int $namespace,
string $key ): string {
270 $key = lcfirst( $key );
272 return strtr(
"$namespace:$key",
' ',
'_' );
282 public static function fieldset(
string $legend,
string $contents, array $attributes = [] ): string {
283 return Xml::openElement(
'fieldset', $attributes ) .
284 Xml::tags(
'legend', null, $legend ) . $contents .
285 Xml::closeElement(
'fieldset' );
300 $msg = htmlspecialchars( $message );
301 $msg = preg_replace(
'/^ /m',
' ', $msg );
302 $msg = preg_replace(
'/ $/m',
' ', $msg );
303 $msg = preg_replace(
'/ /',
'  ', $msg );
304 $msg = str_replace(
"\n",
'<br />', $msg );
314 public static function cacheFile(
string $filename ): string {
315 global $wgTranslateCacheDirectory, $wgCacheDirectory;
317 if ( $wgTranslateCacheDirectory !==
false ) {
318 $dir = $wgTranslateCacheDirectory;
319 } elseif ( $wgCacheDirectory !==
false ) {
320 $dir = $wgCacheDirectory;
322 throw new ConfigException(
"\$wgCacheDirectory must be configured" );
325 return "$dir/$filename";
332 return "\x7fUNIQ" . dechex( mt_rand( 0, 0x7fffffff ) ) .
333 dechex( mt_rand( 0, 0x7fffffff ) ) .
'-' . $i++;
343 $icon = $g->getIcon();
344 if ( !$icon || substr( $icon, 0, 7 ) !==
'wiki://' ) {
350 $filename = substr( $icon, 7 );
351 $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $filename );
353 wfWarn(
"Unknown message group icon file $icon" );
358 if ( $file->isVectorized() ) {
359 $formats[
'vector'] = $file->getFullUrl();
362 $formats[
'raster'] = $file->createThumb( $size, $size );
374 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
375 $index = self::shouldReadFromPrimary() ? DB_PRIMARY : DB_REPLICA;
377 return $lb->getConnection( $index );
382 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
385 if ( PageTranslationHooks::$renderingContext ) {
389 return PHP_SAPI ===
'cli' ||
390 RequestContext::getMain()->getRequest()->wasPosted() ||
391 $lb->hasOrMadeRecentPrimaryChanges();
400 if ( !$handle->isValid() ) {
401 return $handle->
getTitle()->getLocalURL( [
'action' =>
'edit' ] );
404 $title = MediaWikiServices::getInstance()
405 ->getSpecialPageFactory()->getPage(
'Translate' )->getPageTitle();
406 return $title->getFullURL( [
407 'showMessage' => $handle->getInternalKey(),
408 'group' => $handle->getGroup()->getId(),
409 'language' => $handle->getCode(),
418 return serialize( $value );
425 public static function deserialize(
string $str, array $opts = [
'allowed_classes' =>
false ] ) {
426 return unserialize( $str, $opts );
429 public static function getVersion(): string {
431 static $version = null;
432 if ( $version ===
null ) {
433 $version = json_decode( file_get_contents( __DIR__ .
'../../../extension.json' ) )->version;
446 $mwInstance = MediaWikiServices::getInstance();
447 $namespaceInfo = $mwInstance->getNamespaceInfo();
448 return $namespaceInfo->hasSubpages( $title->getNamespace() );
458 $all = self::getLanguageNames( LanguageNameUtils::AUTONYMS );
459 return isset( $all[ $code ] );
462 public static function getTextFromTextContent( ?Content $content ): string {
464 throw new UnexpectedValueException(
'Expected $content to be TextContent, got null instead.' );
467 if ( $content instanceof TextContent ) {
468 return $content->getText();
471 throw new UnexpectedValueException(
'Expected $content to be TextContent, but got ' . get_class( $content ) );
481 $namespace = $handle->getTitle()->getNamespace();
482 $base = $handle->
getKey();
484 $dbr = MediaWikiServices::getInstance()
485 ->getDBLoadBalancer()
486 ->getConnection( DB_REPLICA );
488 $titles = $dbr->newSelectQueryBuilder()
489 ->select(
'page_title' )
492 'page_namespace' => $namespace,
493 'page_title ' . $dbr->buildLike(
"$base/", $dbr->anyString() ),
495 ->caller( __METHOD__ )
496 ->orderBy(
'page_title' )
497 ->fetchFieldValues();
499 if ( $titles === [] ) {
503 return self::getContents( $titles, $namespace );
506 public static function isTranslationPage(
MessageHandle $handle ): bool {
511 $key = $handle->getKey();
512 $languageCode = $handle->
getCode();
513 if ( $key ===
'' || $languageCode ===
'' ) {
517 $baseTitle = Title::makeTitle( $handle->
getTitle()->getNamespace(), $key );
518 if ( !TranslatablePage::isSourcePage( $baseTitle ) ) {
522 static $codes =
null;
523 if ( $codes ===
null ) {
524 $codes = self::getLanguageNames( LanguageNameUtils::AUTONYMS );
527 return !$handle->
isDoc() && isset( $codes[ $languageCode ] );
531class_alias( Utilities::class,
'TranslateUtils' );
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: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:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $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:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, '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());}, '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(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(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(), $services->getDBLoadBalancer());}, '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
Hooks for page translation.
Mixed bag of methods related to translatable pages.
Class for pointing to messages, like Title class is for titles.
isDoc()
Determine whether the current handle is for message documentation.
getTitle()
Get the original title.
getCode()
Returns the language code.
getKey()
Returns the identified or guessed message key.
Interface for message groups.