2declare( strict_types = 1 );
4namespace MediaWiki\Extension\Translate\Utilities;
10use MediaWiki\Languages\LanguageNameUtils;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\Revision\SlotRecord;
20use UnexpectedValueException;
21use Wikimedia\Rdbms\IDatabase;
39 public static function title(
string $message,
string $code,
int $ns = NS_MEDIAWIKI ): string {
42 $key = $ns .
':' . $message;
44 if ( !isset( $cache[$key] ) ) {
45 $cache[$key] = Title::capitalize( $message, $ns );
49 return $cache[$key] .
'/' . $code;
62 $pos = strrpos( $text,
'/' );
63 $code = substr( $text, $pos + 1 );
64 $key = substr( $text, 0, $pos );
66 return [ $key, $code ];
76 public static function getMessageContent(
string $key,
string $language,
int $namespace = NS_MEDIAWIKI ): ?string {
77 $title = self::title( $key, $language, $namespace );
78 $data = self::getContents( [ $title ], $namespace );
80 return $data[$title][0] ??
null;
91 public static function getContents( $titles,
int $namespace ): array {
92 $mwServices = MediaWikiServices::getInstance();
93 $dbr = $mwServices->getDBLoadBalancer()->getConnection( DB_REPLICA );
94 $revStore = $mwServices->getRevisionStore();
97 $query = $revStore->getQueryInfo( [
'page',
'user' ] );
102 'page_namespace' => $namespace,
103 'page_title' => $titles,
104 'page_latest=rev_id',
111 $revisions = $revStore->newRevisionsFromBatch( $rows, [
116 foreach ( $rows as $row ) {
118 $rev = $revisions[$row->rev_id];
121 $content = $rev->getContent( SlotRecord::MAIN );
123 $titleContents[$row->page_title] = [
133 return $titleContents;
143 $store = MediaWikiServices::getInstance()->getRevisionStore();
144 $revision = $store->getRevisionByTitle( $title );
146 if ( $revision ===
null ) {
150 $content = $revision->getContent( SlotRecord::MAIN );
151 $wiki = ( $content instanceof TextContent ) ? $content->getText() :
null;
154 if ( $wiki ===
null ) {
160 if ( $handle->isFuzzy() ) {
161 $wiki = TRANSLATE_FUZZY . str_replace( TRANSLATE_FUZZY,
'', $wiki );
176 public static function getLanguageName(
string $code, ?
string $language =
'en' ): string {
177 $languages = self::getLanguageNames( $language );
178 return $languages[$code] ?? $code;
190 $selector = self::getLanguageSelector( $language );
191 $selector->setDefault( $selectedId );
192 $selector->setAttribute(
'id',
'language' );
193 $selector->setAttribute(
'name',
'language' );
195 return $selector->getHTML();
205 $languages = self::getLanguageNames( $language );
208 $selector =
new XmlSelect();
209 if ( $labelOption !==
null ) {
210 $selector->addOption( $labelOption,
'-' );
213 foreach ( $languages as $code => $name ) {
214 $selector->addOption(
"$code - $name", $code );
228 $mwServices = MediaWikiServices::getInstance();
229 $languageNames = $mwServices->getLanguageNameUtils()->getLanguageNames( $code );
231 $deprecatedCodes = LanguageCode::getDeprecatedCodeMapping();
232 foreach ( array_keys( $deprecatedCodes ) as $deprecatedCode ) {
233 unset( $languageNames[ $deprecatedCode ] );
235 $mwServices->getHookContainer()->run(
'TranslateSupportedLanguages', [ &$languageNames, $code ] );
237 return $languageNames;
247 $groups = self::messageKeyToGroups( $namespace, $key );
249 return count( $groups ) ? $groups[0] :
null;
257 $mi =
Services::getInstance()->getMessageIndex()->retrieve();
258 $normkey = self::normaliseKey( $namespace, $key );
260 if ( isset( $mi[$normkey] ) ) {
261 return (array)$mi[$normkey];
268 public static function normaliseKey(
int $namespace,
string $key ): string {
269 $key = lcfirst( $key );
271 return strtr(
"$namespace:$key",
' ',
'_' );
281 public static function fieldset(
string $legend,
string $contents, array $attributes = [] ): string {
282 return Xml::openElement(
'fieldset', $attributes ) .
283 Xml::tags(
'legend', null, $legend ) . $contents .
284 Xml::closeElement(
'fieldset' );
299 $msg = htmlspecialchars( $message );
300 $msg = preg_replace(
'/^ /m',
' ', $msg );
301 $msg = preg_replace(
'/ $/m',
' ', $msg );
302 $msg = preg_replace(
'/ /',
'  ', $msg );
303 $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 MWException(
"\$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 );
507class_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: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
Hooks for page translation.
Class for pointing to messages, like Title class is for titles.
getTitle()
Get the original title.
getKey()
Returns the identified or guessed message key.
Interface for message groups.