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 JobQueueGroup;
8use MapCacheLRU;
9use MediaWiki\Config\ServiceOptions;
13use MediaWiki\Title\Title;
14use MessageGroup;
15use Psr\Log\LoggerInterface;
16use Wikimedia\ObjectCache\BagOStuff;
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
262 $recursion--;
263
264 return $new;
265 }
266
267 public function getStatusCacheKey(): string {
268 return $this->statusCache->makeKey( 'Translate', 'MessageIndex', 'status' );
269 }
270
271 public function storeInterim( MessageGroup $group, array $newKeys ): void {
272 $namespace = $group->getNamespace();
273 $id = $group->getId();
274
275 $normalizedNewKeys = [];
276 foreach ( $newKeys as $key ) {
277 $normalizedNewKeys[$this->normaliseKey( $namespace, $key )] = $id;
278 }
279
280 $cache = $this->interimCache;
281 // Merge with existing keys (if present)
282 $interimCacheValue = $cache->get( self::CACHE_KEY, $cache::READ_LATEST );
283 if ( $interimCacheValue ) {
284 $normalizedNewKeys = array_merge( $interimCacheValue['newKeys'], $normalizedNewKeys );
285 $this->logger->debug(
286 '[MessageIndex] interim cache: merging with existing cache of size {count}',
287 [ 'count' => count( $interimCacheValue['newKeys'] ) ]
288 );
289 }
290
291 $value = [
292 'timestamp' => microtime( true ),
293 'newKeys' => $normalizedNewKeys,
294 ];
295
296 $cache->set( self::CACHE_KEY, $value, $cache::TTL_DAY );
297 $this->logger->debug(
298 '[MessageIndex] interim cache: added group {groupId} with new size {count} keys and ' .
299 'timestamp {cacheTimestamp}',
300 [ 'groupId' => $id, 'count' => count( $normalizedNewKeys ), 'cacheTimestamp' => $value['timestamp'] ]
301 );
302 }
303
332 public function getArrayDiff( array $old, array $new ): array {
333 $values = [];
334 $record = static function ( $groups ) use ( &$values ) {
335 foreach ( $groups as $group ) {
336 $values[$group] = true;
337 }
338 };
339
340 $keys = [
341 'add' => [],
342 'del' => [],
343 'mod' => [],
344 ];
345
346 foreach ( $new as $key => $groups ) {
347 if ( !isset( $old[$key] ) ) {
348 $keys['add'][$key] = [ [], (array)$groups ];
349 $record( (array)$groups );
350 // Using != here on purpose to ignore the order of items
351 } elseif ( $groups != $old[$key] ) {
352 $keys['mod'][$key] = [ (array)$old[$key], (array)$groups ];
353 $record( array_diff( (array)$old[$key], (array)$groups ) );
354 $record( array_diff( (array)$groups, (array)$old[$key] ) );
355 }
356 }
357
358 foreach ( $old as $key => $groups ) {
359 if ( !isset( $new[$key] ) ) {
360 $keys['del'][$key] = [ (array)$groups, [] ];
361 $record( (array)$groups );
362 }
363 // We already checked for diffs above
364 }
365
366 return [
367 'keys' => $keys,
368 'values' => array_keys( $values ),
369 ];
370 }
371
373 protected function clearMessageGroupStats( array $diff ): void {
374 $job = RebuildMessageGroupStatsJob::newRefreshGroupsJob( $diff['values'] );
375 $this->jobQueueGroup->push( $job );
376
377 foreach ( $diff['keys'] as $keys ) {
378 foreach ( $keys as $key => $data ) {
379 [ $ns, $pageName ] = explode( ':', $key, 2 );
380 $title = Title::makeTitle( (int)$ns, $pageName );
381 $handle = new MessageHandle( $title );
382 [ $oldGroups, $newGroups ] = $data;
383 $this->hookRunner->onTranslateEventMessageMembershipChange(
384 $handle, $oldGroups, $newGroups );
385 }
386 }
387 }
388
389 protected function checkAndAdd( array &$hugeArray, MessageGroup $g, bool $ignore = false ): void {
390 $keys = $g->getKeys();
391 $id = $g->getId();
392 $namespace = $g->getNamespace();
393
394 foreach ( $keys as $key ) {
395 # Force all keys to lower case, because the case doesn't matter and it is
396 # easier to do comparing when the case of first letter is unknown, because
397 # mediawiki forces it to upper case
398 $key = $this->normaliseKey( $namespace, $key );
399 if ( isset( $hugeArray[$key] ) ) {
400 if ( !$ignore ) {
401 $to = implode( ', ', (array)$hugeArray[$key] );
402 wfWarn( "Key $key already belongs to $to, conflict with $id" );
403 }
404
405 if ( is_array( $hugeArray[$key] ) ) {
406 // Hard work is already done, just add a new reference
407 $hugeArray[$key][] = & $id;
408 } else {
409 // Store the actual reference, then remove it from array, to not
410 // replace the references value, but to store an array of new
411 // references instead. References are hard!
412 $value = & $hugeArray[$key];
413 unset( $hugeArray[$key] );
414 $hugeArray[$key] = [ &$value, &$id ];
415 }
416 } else {
417 $hugeArray[$key] = & $id;
418 }
419 }
420 unset( $id ); // Disconnect the previous references to this $id
421 }
422}
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.