Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageIndex.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageLoading;
5
6use Exception;
7use MediaWiki\Config\ServiceOptions;
11use MediaWiki\JobQueue\JobQueueGroup;
12use MediaWiki\Title\Title;
13use MessageGroup;
14use Psr\Log\LoggerInterface;
15use Wikimedia\ObjectCache\BagOStuff;
16use Wikimedia\ObjectCache\MapCacheLRU;
17use Wikimedia\ObjectCache\WANObjectCache;
18use Wikimedia\Rdbms\IConnectionProvider;
19
33 // TODO: Use dependency injection
34 private const CACHE_KEY = 'Translate-MessageIndex-interim';
35 private const READ_LATEST = true;
36
37 private MapCacheLRU $keysCache;
38 private array $translateMessageNamespaces;
39 public const SERVICE_OPTIONS = [
40 'TranslateMessageNamespaces'
41 ];
42
43 public function __construct(
44 private readonly MessageIndexStore $messageIndexStore,
45 private readonly WANObjectCache $statusCache,
46 private readonly JobQueueGroup $jobQueueGroup,
47 private readonly HookRunner $hookRunner,
48 private readonly LoggerInterface $logger,
49 private readonly BagOStuff $interimCache,
50 private readonly IConnectionProvider $dbProvider,
51 ServiceOptions $options
52 ) {
53 $this->keysCache = new MapCacheLRU( 30 );
54 $options->assertRequiredOptions( self::SERVICE_OPTIONS );
55 $this->translateMessageNamespaces = $options->get( 'TranslateMessageNamespaces' );
56 }
57
59 private function normaliseKey( int $namespace, string $key ): string {
60 $key = lcfirst( $key );
61
62 return strtr( "$namespace:$key", ' ', '_' );
63 }
64
69 public function getGroupIds( MessageHandle $handle ): array {
70 $title = $handle->getTitle();
71
72 if ( !$title->inNamespaces( $this->translateMessageNamespaces ) ) {
73 return [];
74 }
75
76 $namespace = $title->getNamespace();
77 $key = $handle->getKey();
78 $normalisedKey = $this->normaliseKey( $namespace, $key );
79
80 $value = $this->keysCache->get( $normalisedKey );
81 if ( $value === null ) {
82 $value = (array)$this->getWithCache( $normalisedKey );
83 $this->keysCache->set( $normalisedKey, $value );
84 }
85
86 return $value;
87 }
88
96 public function getGroupIdsForBaseDatabaseTitle( int $namespace, string $title ): array {
97 $normalisedKey = $this->normaliseKey( $namespace, $title );
98
99 // Optimization 1: skip LRU cache assuming that hit rate is very low for this use case
100 // Optimization 2: skip interim cache as not essential
101
102 return (array)$this->get( $normalisedKey );
103 }
104
105 public function getPrimaryGroupId( MessageHandle $handle ): ?string {
106 $groups = $this->getGroupIds( $handle );
107
108 return count( $groups ) ? array_shift( $groups ) : null;
109 }
110
112 private function getWithCache( string $key ) {
113 $interimCacheValue = $this->interimCache->get( self::CACHE_KEY );
114 if ( $interimCacheValue && isset( $interimCacheValue['newKeys'][$key] ) ) {
115 $this->logger->debug(
116 '[MessageIndex] interim cache hit: {messageKey} with value {groupId}',
117 [ 'messageKey' => $key, 'groupId' => $interimCacheValue['newKeys'][$key] ]
118 );
119 return $interimCacheValue['newKeys'][$key];
120 }
121
122 return $this->messageIndexStore->get( $key );
123 }
124
126 public function get( string $key ) {
127 return $this->messageIndexStore->get( $key );
128 }
129
131 public function getKeys(): array {
132 return $this->messageIndexStore->getKeys();
133 }
134
135 private function lock(): bool {
136 $dbw = $this->dbProvider->getPrimaryDatabase();
137
138 // Any transaction should be flushed after getting the lock to avoid
139 // stale pre-lock REPEATABLE-READ snapshot data.
140 $ok = $dbw->lock( 'translate-messageindex', __METHOD__, 5 );
141 if ( $ok ) {
142 $dbw->commit( __METHOD__, 'flush' );
143 }
144
145 return $ok;
146 }
147
148 private function unlock(): void {
149 $fname = __METHOD__;
150 $dbw = $this->dbProvider->getPrimaryDatabase();
151 // Unlock once the rows are actually unlocked to avoid deadlocks
152 if ( !$dbw->trxLevel() ) {
153 $dbw->unlock( 'translate-messageindex', $fname );
154 } else {
155 $dbw->onTransactionResolution( static function () use ( $dbw, $fname ) {
156 $dbw->unlock( 'translate-messageindex', $fname );
157 }, $fname );
158 }
159 }
160
167 public function rebuild( ?float $timestamp = null ): array {
168 static $recursion = 0;
169
170 if ( $recursion > 0 ) {
171 $msg = __METHOD__ . ': trying to recurse - building the index first time?';
172 wfWarn( $msg );
173
174 $recursion--;
175 return [];
176 }
177 $recursion++;
178
179 $this->logger->info( '[MessageIndex] Started rebuild.' );
180
181 $tsStart = microtime( true );
182 if ( !$this->lock() ) {
183 throw new MessageIndexException( __CLASS__ . ': unable to acquire lock' );
184 }
185
186 $lockWaitDuration = microtime( true ) - $tsStart;
187 $this->logger->info(
188 '[MessageIndex] Got lock in {duration}',
189 [ 'duration' => $lockWaitDuration ]
190 );
191
192 $groups = MessageGroups::singleton()->getGroups();
193 $this->keysCache->clear();
194
195 $new = [];
196 $old = $this->messageIndexStore->retrieve( self::READ_LATEST );
197 $postponed = [];
198
199 foreach ( $groups as $messageGroup ) {
200 if ( !$messageGroup->exists() ) {
201 $id = $messageGroup->getId();
202 wfWarn( __METHOD__ . ": group '$id' is registered but does not exist" );
203 continue;
204 }
205
206 # Skip meta thingies
207 if ( $messageGroup->isMeta() ) {
208 $postponed[] = $messageGroup;
209 continue;
210 }
211
212 $this->checkAndAdd( $new, $messageGroup );
213 }
214
215 foreach ( $postponed as $messageGroup ) {
216 $this->checkAndAdd( $new, $messageGroup, true );
217 }
218
219 $diff = self::getArrayDiff( $old, $new );
220 $this->messageIndexStore->store( $new, $diff['keys'] );
221
222 $cache = $this->interimCache;
223 $interimCacheValue = $cache->get( self::CACHE_KEY );
224 if ( $interimCacheValue ) {
225 $timestamp ??= microtime( true );
226 if ( $interimCacheValue['timestamp'] <= $timestamp ) {
227 $cache->delete( self::CACHE_KEY );
228 $this->logger->debug(
229 '[MessageIndex] Deleted interim cache with timestamp {cacheTimestamp} <= {currentTimestamp}.',
230 [
231 'cacheTimestamp' => $interimCacheValue['timestamp'],
232 'currentTimestamp' => $timestamp,
233 ]
234 );
235 } else {
236 // Cache has a later timestamp. This may be caused due to
237 // job deduplication. Just in case, spin off a new job to clean up the cache.
238 $job = RebuildMessageIndexJob::newJob( __METHOD__ );
239 $this->jobQueueGroup->push( $job );
240 $this->logger->debug(
241 '[MessageIndex] Kept interim cache with timestamp {cacheTimestamp} > {currentTimestamp}.',
242 [
243 'cacheTimestamp' => $interimCacheValue['timestamp'],
244 'currentTimestamp' => $timestamp,
245 ]
246 );
247 }
248 }
249
250 $this->unlock();
251 $criticalSectionDuration = microtime( true ) - $tsStart - $lockWaitDuration;
252 $this->logger->info(
253 '[MessageIndex] Finished critical section in {duration}',
254 [ 'duration' => $criticalSectionDuration ]
255 );
256
257 // Other caches can check this key to know when they need to refresh
258 $this->statusCache->touchCheckKey( $this->getStatusCacheKey() );
259
260 $this->clearMessageGroupStats( $diff );
261 $this->logger->info(
262 '[MessageIndex] Finished everything in {duration}',
263 [ 'duration' => microtime( true ) - $tsStart ]
264 );
265
266 $recursion--;
267
268 return $new;
269 }
270
271 public function getStatusCacheKey(): string {
272 return $this->statusCache->makeKey( 'Translate', 'MessageIndex', 'status' );
273 }
274
275 public function storeInterim( MessageGroup $group, array $newKeys ): void {
276 $namespace = $group->getNamespace();
277 $id = $group->getId();
278
279 $normalizedNewKeys = [];
280 foreach ( $newKeys as $key ) {
281 $normalizedNewKeys[$this->normaliseKey( $namespace, $key )] = $id;
282 }
283
284 $cache = $this->interimCache;
285 // Merge with existing keys (if present)
286 $interimCacheValue = $cache->get( self::CACHE_KEY, $cache::READ_LATEST );
287 if ( $interimCacheValue ) {
288 $normalizedNewKeys = array_merge( $interimCacheValue['newKeys'], $normalizedNewKeys );
289 $this->logger->debug(
290 '[MessageIndex] interim cache: merging with existing cache of size {count}',
291 [ 'count' => count( $interimCacheValue['newKeys'] ) ]
292 );
293 }
294
295 $value = [
296 'timestamp' => microtime( true ),
297 'newKeys' => $normalizedNewKeys,
298 ];
299
300 $cache->set( self::CACHE_KEY, $value, $cache::TTL_DAY );
301 $this->logger->debug(
302 '[MessageIndex] interim cache: added group {groupId} with new size {count} keys and ' .
303 'timestamp {cacheTimestamp}',
304 [ 'groupId' => $id, 'count' => count( $normalizedNewKeys ), 'cacheTimestamp' => $value['timestamp'] ]
305 );
306 }
307
336 public function getArrayDiff( array $old, array $new ): array {
337 $values = [];
338 $record = static function ( $groups ) use ( &$values ) {
339 foreach ( $groups as $group ) {
340 $values[$group] = true;
341 }
342 };
343
344 $keys = [
345 'add' => [],
346 'del' => [],
347 'mod' => [],
348 ];
349
350 foreach ( $new as $key => $groups ) {
351 if ( !isset( $old[$key] ) ) {
352 $keys['add'][$key] = [ [], (array)$groups ];
353 $record( (array)$groups );
354 // Using != here on purpose to ignore the order of items
355 } elseif ( $groups != $old[$key] ) {
356 $keys['mod'][$key] = [ (array)$old[$key], (array)$groups ];
357 $record( array_diff( (array)$old[$key], (array)$groups ) );
358 $record( array_diff( (array)$groups, (array)$old[$key] ) );
359 }
360 }
361
362 foreach ( $old as $key => $groups ) {
363 if ( !isset( $new[$key] ) ) {
364 $keys['del'][$key] = [ (array)$groups, [] ];
365 $record( (array)$groups );
366 }
367 // We already checked for diffs above
368 }
369
370 return [
371 'keys' => $keys,
372 'values' => array_keys( $values ),
373 ];
374 }
375
377 protected function clearMessageGroupStats( array $diff ): void {
378 $job = RebuildMessageGroupStatsJob::newRefreshGroupsJob( $diff['values'] );
379 $this->jobQueueGroup->push( $job );
380
381 foreach ( $diff['keys'] as $keys ) {
382 foreach ( $keys as $key => $data ) {
383 [ $ns, $pageName ] = explode( ':', $key, 2 );
384 $title = Title::makeTitle( (int)$ns, $pageName );
385 $handle = new MessageHandle( $title );
386 [ $oldGroups, $newGroups ] = $data;
387 $this->hookRunner->onTranslateEventMessageMembershipChange(
388 $handle, $oldGroups, $newGroups );
389 }
390 }
391 }
392
393 protected function checkAndAdd( array &$hugeArray, MessageGroup $g, bool $ignore = false ): void {
394 $keys = $g->getKeys();
395 $id = $g->getId();
396 $namespace = $g->getNamespace();
397
398 foreach ( $keys as $key ) {
399 # Force all keys to lower case, because the case doesn't matter and it is
400 # easier to do comparing when the case of first letter is unknown, because
401 # mediawiki forces it to upper case
402 $key = $this->normaliseKey( $namespace, $key );
403 if ( isset( $hugeArray[$key] ) ) {
404 if ( !$ignore ) {
405 $to = implode( ', ', (array)$hugeArray[$key] );
406 wfWarn( "Key $key already belongs to $to, conflict with $id" );
407 }
408
409 if ( is_array( $hugeArray[$key] ) ) {
410 // Hard work is already done, just add a new reference
411 $hugeArray[$key][] = & $id;
412 } else {
413 // Store the actual reference, then remove it from array, to not
414 // replace the references value, but to store an array of new
415 // references instead. References are hard!
416 $value = & $hugeArray[$key];
417 unset( $hugeArray[$key] );
418 $hugeArray[$key] = [ &$value, &$id ];
419 }
420 } else {
421 $hugeArray[$key] = & $id;
422 }
423 }
424 unset( $id ); // Disconnect the previous references to this $id
425 }
426}
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
Hook runner for the Translate extension.
Factory class for accessing message groups individually by id or all of them as a list.
Class for pointing to messages, like Title class is for titles.
getKey()
Returns the identified or guessed message key.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
getGroupIds(MessageHandle $handle)
Retrieves a list of groups given MessageHandle belongs to.
getGroupIdsForBaseDatabaseTitle(int $namespace, string $title)
Fast-path to retrieve groups for database titles.
getArrayDiff(array $old, array $new)
Compares two associative arrays.
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
rebuild(?float $timestamp=null)
Creates the index from scratch.
Interface for message groups.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.