Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
EntitySearch.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorInterface;
5
6use MediaWiki\Collation\Collation;
12use MediaWiki\Title\MalformedTitleException;
13use MediaWiki\Title\NamespaceInfo;
14use MediaWiki\Title\TitleFormatter;
15use MediaWiki\Title\TitleParser;
16use SplMinHeap;
17use Wikimedia\LightweightObjectStore\ExpirationAwareness;
18use Wikimedia\ObjectCache\WANObjectCache;
19
27 private const FIELD_DELIMITER = "\x7F";
28 private const ROW_DELIMITER = "\n";
29
30 private const TYPE_AGGREGATE = AggregateMessageGroup::class;
31 private const TYPE_MESSAGE_BUNDLE = MessageBundleMessageGroup::class;
32 private const TYPE_WIKIPAGE = WikiPageMessageGroup::class;
33 private const TYPE_OTHERS = 'o';
34 private const TYPE_MAPPING = [
35 self::TYPE_AGGREGATE => [ 'aggregate-groups', 'a' ],
36 self::TYPE_MESSAGE_BUNDLE => [ 'message-bundles', 'm' ],
37 self::TYPE_WIKIPAGE => [ 'translatable-pages', 'w' ]
38 ];
40 private array $mappedTypes;
41
42 public function __construct(
43 private readonly WANObjectCache $cache,
44 private readonly Collation $collation,
45 private readonly MessageGroups $messageGroupFactory,
46 private readonly NamespaceInfo $namespaceInfo,
47 private readonly MessageIndex $messageIndex,
48 private readonly TitleParser $titleParser,
49 private readonly TitleFormatter $titleFormatter,
50 ) {
51 $this->mappedTypes = $this->getGroupTypes();
52 }
53
54 public function searchStaticMessageGroups( string $query, int $maxResults, array $types = [] ): array {
55 $cache = $this->cache;
56 // None of the static groups currently use language-dependent labels. This
57 // may need revisiting later and splitting the cache by language.
58 $key = $cache->makeKey( 'Translate', 'EntitySearch', 'static-groups', '-v2' );
59 $haystack = $cache->getWithSetCallback(
60 $key,
61 ExpirationAwareness::TTL_WEEK,
62 function (): string {
63 return $this->getStaticMessageGroupsHaystack();
64 },
65 [
66 // Calling touchCheckKey() on this key purges the cache
67 'checkKeys' => [ $this->messageGroupFactory->getCacheKey() ],
68 // Avoid querying cache servers multiple times in a web request
69 'pcTTL' => ExpirationAwareness::TTL_PROC_LONG
70 ]
71 );
72
73 // Algorithm: Construct one big string with one entity per line. Then run
74 // preg_match_all twice over it, first to collect prefix match (to show them
75 // first), then to match words if more results are needed.
76 $results = [];
77
78 $delimiter = self::FIELD_DELIMITER;
79 $anything = "[^$delimiter\n]";
80 $query = preg_quote( $query, '/' );
81
82 $groupTypeFilter = [];
83 foreach ( $types as $filter ) {
84 $groupTypeFilter[] = $this->mappedTypes[$filter] ?? '';
85 }
86 $groupTypeFilter = count( $groupTypeFilter )
87 ? ( '[' . implode( $groupTypeFilter ) . ']' )
88 : $anything;
89
90 // Prefix match
91 $pattern = "/^$groupTypeFilter$delimiter($query$anything*)$delimiter($anything+)$/miu";
92 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
93 foreach ( $matches as [ , $label, $groupId ] ) {
94 // Index by $groupId to avoid duplicates from the prefix match and the word match
95 $results[$groupId] = [
96 'label' => $label,
97 'group' => $groupId,
98 ];
99
100 if ( count( $results ) >= $maxResults ) {
101 return array_values( $results );
102 }
103 }
104
105 // Word match
106 $pattern = "/^$groupTypeFilter$delimiter($anything*\b$query$anything*)$delimiter($anything+)$/miu";
107 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
108 foreach ( $matches as [ , $label, $groupId ] ) {
109 $results[$groupId] = [
110 'label' => $label,
111 'group' => $groupId,
112 ];
113
114 if ( count( $results ) >= $maxResults ) {
115 return array_values( $results );
116 }
117 }
118
119 return array_values( $results );
120 }
121
123 public function searchMessages( string $query, int $maxResults ): array {
124 // Optimized based on requirements:
125 // * "Natural" sorting of results
126 // * No need to show which message group things belong to
127 // * Match at any point in the message
128 // * Return full keys of prefixes that match multiple messages
129
130 // Algorithm: Construct one big string (haystack) with one entity per line.
131 // Then run preg_match_all over it. Because we will have many more matches
132 // than search results, this may be more efficient than calling preg_match
133 // repeatedly in a loop. On the other hand, it can use a lot of memory to
134 // construct the array for all the matches.
135 $haystack = $this->getMessagesHaystack();
136 $rowDelimiter = self::ROW_DELIMITER;
137 $anything = "[^$rowDelimiter]";
138 // If the query ends with a wildcard, remove it. The search handles
139 // wildcards implicitly by being a prefix search.
140 $query = rtrim( $query, '*' );
141 $query = preg_quote( $query, '/' );
142
143 // Word match
144 $pattern = "/^($anything*\b$query)$anything*$/miu";
145 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
146
147 $results = [];
148 $previousPrefixMatch = null;
149 foreach ( $matches as [ $full, $prefixMatch ] ) {
150 // This is a bit tricky. If we are at the maximum results, continue processing
151 // until the prefix changes, to get an accurate count
152 if ( count( $results ) >= $maxResults && $previousPrefixMatch !== $prefixMatch ) {
153 break;
154 }
155
156 if ( $full === $prefixMatch ) {
157 $results[$full] = [ $full, 1, true, $full ];
158 } else {
159 if ( !isset( $results["$prefixMatch*"] ) ) {
160 $results["$prefixMatch*"] = [ "$prefixMatch*", 0, false, $full ];
161 }
162 $results["$prefixMatch*"][1]++;
163 }
164 $previousPrefixMatch = $prefixMatch;
165 }
166
167 // Convert partial matches with single results to full match
168 foreach ( $results as $index => [ $label, $count, $isExact, $full ] ) {
169 if ( $count === 1 && !$isExact ) {
170 $results[$index][0] = $full;
171 }
172 }
173
174 // Drop unnecessary fields, pretty format title
175 foreach ( $results as &$value ) {
176 try {
177 $title = $this->titleParser->parseTitle( $value[0] );
178 $label = $this->titleFormatter->getPrefixedText( $title );
179 } catch ( MalformedTitleException ) {
180 $label = $value[0];
181 }
182 $value = [
183 'pattern' => $label,
184 'count' => $value[1]
185 ];
186 }
187
188 return array_values( $results );
189 }
190
192 public function matchMessages( string $query ): array {
193 $haystack = $this->getMessagesHaystack();
194 $rowDelimiter = self::ROW_DELIMITER;
195 $anything = "[^$rowDelimiter]*";
196
197 // Need something that's not affected by preg_quote and cannot be guessed
198 $placeholder = bin2hex( random_bytes( 16 ) );
199 $query = str_replace( '*', $placeholder, $query );
200 $query = preg_quote( $query, '/' );
201 $query = str_replace( $placeholder, $anything, $query );
202
203 $pattern = "/^$query/miu";
204 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
205 return array_column( $matches, 0 );
206 }
207
208 private function getStaticMessageGroupsHaystack(): string {
209 $groups = $this->messageGroupFactory->getGroups();
210 $data = new SplMinHeap();
211 foreach ( $groups as $group ) {
212 $label = $group->getLabel();
213 // Ensure there are no special chars that will break matching
214 $label = strtr( $label, [ self::FIELD_DELIMITER => '', self::ROW_DELIMITER => '' ] );
215 $sortKey = $this->collation->getSortKey( $label );
216 // It is unlikely that different groups have the same label (or sort key),
217 // but it's possible, so cannot use a hashmap here.
218 $groupType = get_class( $group );
219 $type = self::TYPE_MAPPING[$groupType][1] ?? self::TYPE_OTHERS;
220 $data->insert( [ $sortKey, $label, $group->getId(), $type ] );
221 }
222
223 $haystack = '';
224 foreach ( $data as [ , $label, $groupId, $type ] ) {
225 $haystack
226 .= $type
227 . self::FIELD_DELIMITER
228 . $label
229 . self::FIELD_DELIMITER
230 . $groupId
231 . self::ROW_DELIMITER;
232 }
233 return $haystack;
234 }
235
236 private function getMessageHaystackUncached(): string {
237 $namespaceMap = [];
238 $data = new SplMinHeap();
239 $keys = $this->messageIndex->getKeys();
240 foreach ( $keys as $key ) {
241 // Normalize "_" to " " so that \b in regexp matches words separated by underscores
242 $key = strtr( $key, [ '_' => ' ' ] );
243
244 [ $namespaceId, $label ] = explode( ':', $key, 2 );
245 if ( !isset( $namespaceMap[$namespaceId] ) ) {
246 $namespaceMap[$namespaceId] = $this->namespaceInfo->getCanonicalName( (int)$namespaceId );
247 }
248 $label = $namespaceMap[$namespaceId] . ":$label";
249
250 // Ensure there are no special chars that will break matching
251 $label = strtr( $label, [ self::ROW_DELIMITER => '' ] );
252 $sortKey = $this->collation->getSortKey( $label );
253 $data->insert( [ $sortKey, $label ] );
254 }
255
256 $haystack = '';
257 foreach ( $data as [ , $label ] ) {
258 $haystack .= $label . self::ROW_DELIMITER;
259 }
260
261 return $haystack;
262 }
263
264 private function getMessagesHaystack(): string {
265 $cache = $this->cache;
266 $key = $cache->makeKey( 'Translate', 'EntitySearch', 'messages' );
267 return $cache->getWithSetCallback(
268 $key,
269 ExpirationAwareness::TTL_WEEK,
270 function (): string {
271 // This can get rather large. On translatewiki.net it is multiple megabytes
272 // uncompressed. With compression (assumed to happen implicitly in the
273 // caching layer) it's under a megabyte.
274 return $this->getMessageHaystackUncached();
275 },
276 [
277 // Calling touchCheckKey() on this key purges the cache
278 'checkKeys' => [ $this->messageIndex->getStatusCacheKey() ],
279 // Avoid querying cache servers multiple times in a web request
280 'pcTTL' => ExpirationAwareness::TTL_PROC_LONG
281 ]
282 );
283 }
284
286 public function getGroupTypes(): array {
287 return array_column( self::TYPE_MAPPING, 1, 0 );
288 }
289}
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'), $services->get( 'Translate:MessageGroupFactory'),);}, '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->get( 'Translate:MessageGroupFactory'), $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:MessageGroupFactory'=> static function(MediaWikiServices $services):MessageGroupFactory { return new MessageGroupFactory($services->get( 'Translate:MessageGroupTypeRegistry'));}, '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(), $services->getNotificationService(), 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();}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageGroupTypeRegistry'=> static function():MessageGroupTypeRegistry { return new MessageGroupTypeRegistry();}, '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
Factory class for accessing message groups individually by id or all of them as a list.
Groups multiple message groups together as one group.
Wraps the translatable page sections into a message group.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
Service for searching message groups and message keys.
searchMessages(string $query, int $maxResults)
Search message prefixes.
matchMessages(string $query)
Match messages matching a pattern.