31 private bool $allowPotMode =
false;
32 private bool $offlineMode =
false;
42 public function setOfflineMode(
bool $value ): void {
43 $this->offlineMode = $value;
47 public function read( $languageCode ) {
50 $this->allowPotMode = $this->getGroup()->getSourceLanguage() === $languageCode;
53 return parent::read( $languageCode );
55 $this->allowPotMode =
false;
61 if ( !mb_check_encoding( $data,
'UTF-8' ) ) {
65 $data = Validator::cleanUp( $data );
69 preg_match_all(
'/^#\s*Author:\s*(.*)$/m', $data, $matches );
70 $authors = $matches[1];
72 # Then messages and everything else
73 $parsedData = $this->parseGettext( $data );
74 $parsedData[
'AUTHORS'] = $authors;
76 foreach ( $parsedData[
'MESSAGES'] as $key => $value ) {
77 if ( $value ===
'' ) {
78 unset( $parsedData[
'MESSAGES'][$key] );
86 private function parseGettext(
string $data ): array {
87 $mangler = $this->group->getMangler();
88 $useCtxtAsKey = $this->extra[
'CtxtAsKey'] ??
false;
89 $keyAlgorithm =
'simple';
90 if ( isset( $this->extra[
'keyAlgorithm'] ) ) {
91 $keyAlgorithm = $this->extra[
'keyAlgorithm'];
97 $data = str_replace(
"\r\n",
"\n", $data );
102 $sections = preg_split(
'/\n{2,}/', $data );
105 $headerSection = array_shift( $sections );
109 $match = $this->expectKeyword(
'msgstr', $headerSection );
110 if ( $match !==
null ) {
111 $headerBlock = $this->formatForWiki( $match,
'trim' );
112 $headers = $this->parseHeaderTags( $headerBlock );
115 $flags = $this->parseFlags( $headerSection );
116 if ( in_array(
'fuzzy', $flags,
true ) ) {
117 $potmode = $this->allowPotMode;
120 $message =
"Gettext file header was not found:\n\n$headerSection";
121 throw new GettextParseException( $message );
129 if ( isset( $headers[
'X-Language-Code'] ) ) {
130 $metadata[
'code'] = $headers[
'X-Language-Code'];
133 if ( isset( $headers[
'X-Message-Group'] ) ) {
134 $metadata[
'group'] = $headers[
'X-Message-Group'];
142 } elseif ( isset( $headers[
'Plural-Forms'] ) ) {
143 $pluralCount = $metadata[
'plural'] = GettextPlural::getPluralCount( $headers[
'Plural-Forms'] );
146 $metadata[
'plural'] = $pluralCount;
149 foreach ( $sections as $section ) {
150 $item = $this->parseGettextSection( $section, $pluralCount );
151 if ( $item ===
null ) {
155 if ( $useCtxtAsKey ) {
156 if ( $item[
'ctxt'] ===
false ) {
157 error_log(
"ctxt missing for: $section" );
160 $key = $item[
'ctxt'];
162 $key = $this->generateKeyFromItem( $item, $keyAlgorithm );
165 $key = $mangler->mangle( $key );
166 $messages[$key] = $potmode ? $item[
'id'] : $item[
'str'];
167 $template[$key] = $item;
171 'MESSAGES' => $messages,
173 'TEMPLATE' => $template,
174 'METADATA' => $metadata,
175 'HEADERS' => $headers,
181 private function parseGettextSection(
string $section, ?
int $pluralCount ): ?array {
182 if ( trim( $section ) ===
'' ) {
190 if ( preg_match(
'/^#~/m', $section ) ) {
202 $match = $this->expectKeyword(
'msgid', $section );
203 if ( $match !==
null ) {
204 $item[
'id'] = $this->formatForWiki( $match );
206 throw new GettextParseException(
"Unable to parse msgid:\n\n$section" );
209 $match = $this->expectKeyword(
'msgctxt', $section );
210 if ( $match !==
null ) {
211 $item[
'ctxt'] = $this->formatForWiki( $match );
214 $pluralMessage =
false;
215 $match = $this->expectKeyword(
'msgid_plural', $section );
216 if ( $match !==
null ) {
217 $pluralMessage =
true;
218 $plural = $this->formatForWiki( $match );
219 $item[
'id'] = GettextPlural::flatten( [ $item[
'id'], $plural ] );
222 if ( $pluralMessage ) {
223 $pluralMessageText = $this->processGettextPluralMessage( $pluralCount, $section );
226 if ( $pluralMessageText !==
'' ) {
227 $item[
'str'] = $pluralMessageText;
230 $match = $this->expectKeyword(
'msgstr', $section );
231 if ( $match !==
null ) {
232 $item[
'str'] = $this->formatForWiki( $match );
234 throw new GettextParseException(
"Unable to parse msgstr:\n\n$section" );
239 $flags = $this->parseFlags( $section );
240 foreach ( $flags as $key => $flag ) {
241 if ( $flag ===
'fuzzy' ) {
242 $item[
'str'] = TRANSLATE_FUZZY . $item[
'str'];
243 unset( $flags[$key] );
246 $item[
'flags'] = $flags;
250 if ( preg_match_all(
'/^#(.?) (.*)$/m', $section, $matches, PREG_SET_ORDER ) ) {
251 foreach ( $matches as $match ) {
252 if ( $match[1] !==
',' && !str_starts_with( $match[1],
'[Wiki]' ) ) {
253 $item[
'comments'][$match[1]][] = $match[2];
261 private function processGettextPluralMessage( ?
int $pluralCount,
string $section ): string {
264 for ( $i = 0; $i < $pluralCount; $i++ ) {
265 $match = $this->expectKeyword(
"msgstr\\[$i\\]", $section );
267 if ( $match !==
null ) {
268 $actualForms[] = $this->formatForWiki( $match );
271 error_log(
"Plural $i not found, expecting total of $pluralCount for $section" );
275 if ( array_sum( array_map(
'strlen', $actualForms ) ) > 0 ) {
276 return GettextPlural::flatten( $actualForms );
282 private function parseFlags(
string $section ): array {
284 if ( preg_match(
'/^#,(.*)$/mu', $section, $matches ) ) {
285 return array_map(
'trim', explode(
',', $matches[1] ) );
291 private function expectKeyword(
string $name,
string $section ): ?string {
295 $poformat =
'".*"\n?(^".*"$\n?)*';
298 if ( preg_match(
"/^$name\s($poformat)/mx", $section, $matches ) ) {
312 $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage(
'en' );
314 if ( $item[
'ctxt'] ===
'' ) {
318 $hash = sha1( $item[
'id'] .
'MSGEMPTYCTXT' );
320 $hash = sha1( $item[
'ctxt'] . $item[
'id'] );
323 if ( $algorithm ===
'simple' ) {
324 $hash = substr( $hash, 0, 6 );
325 $snippet = $lang->truncateForDatabase( $item[
'id'], 30,
'' );
326 $snippet = str_replace(
' ',
'_', trim( $snippet ) );
328 $legalChars = Title::legalChars();
329 $snippet = $item[
'id'];
330 $snippet = preg_replace(
"/[^$legalChars]/",
' ', $snippet );
331 $snippet = preg_replace(
'/[:&%\/_]/',
' ', $snippet );
332 $snippet = preg_replace(
'/ {2,}/',
' ', $snippet );
333 $snippet = $lang->truncateForDatabase( $snippet, 30,
'' );
334 $snippet = str_replace(
' ',
'_', trim( $snippet ) );
337 return "$hash-$snippet";
343 private function processData(
string $data ): string {
344 $quotePattern =
'/(^"|"$\n?)/m';
345 $data = preg_replace( $quotePattern,
'', $data );
346 return stripcslashes( $data );
353 private function handleWhitespace(
string $data,
string $whitespace ): string {
354 if ( preg_match(
'/\s$/', $data ) ) {
355 if ( $whitespace ===
'mark' ) {
357 } elseif ( $whitespace ===
'trim' ) {
358 $data = rtrim( $data );
361 throw new InvalidArgumentException(
"Unknown action for whitespace: $whitespace" );
374 private function formatForWiki(
string $data,
string $whitespace =
'mark' ): string {
375 $data = $this->processData( $data );
376 return $this->handleWhitespace( $data, $whitespace );
379 private function parseHeaderTags(
string $headers ): array {
381 foreach ( explode(
"\n", $headers ) as $line ) {
382 if ( !str_contains( $line,
':' ) ) {
383 error_log( __METHOD__ .
": $line" );
385 [ $key, $value ] = explode(
':', $line, 2 );
386 $tags[trim( $key )] = trim( $value );
392 protected function writeReal( MessageCollection $collection ): string {
394 $pot = $this->read(
'en' ) ?? [];
395 $code = $collection->code;
396 $template = $this->read( $code ) ?? [];
397 $output = $this->doGettextHeader( $collection, $template[
'EXTRA'] ?? [] );
399 $pluralRule = GettextPlural::getPluralRule( $code );
400 if ( !$pluralRule ) {
401 $pluralRule = GettextPlural::getPluralRule(
'en' );
402 LoggerFactory::getInstance( LogNames::MAIN )->warning(
403 "T235180: Missing Gettext plural rule for '{languagecode}'",
404 [
'languagecode' => $code ]
407 $pluralCount = GettextPlural::getPluralCount( $pluralRule );
409 $documentationLanguageCode = MediaWikiServices::getInstance()
411 ->get(
'TranslateDocumentationLanguageCode' );
412 $documentationCollection =
null;
413 if ( is_string( $documentationLanguageCode ) ) {
414 $documentationCollection = clone $collection;
415 $documentationCollection->resetForNewLanguage( $documentationLanguageCode );
416 $documentationCollection->loadTranslations();
420 foreach ( $collection as $key => $m ) {
421 $transTemplate = $template[
'EXTRA'][
'TEMPLATE'][$key] ?? [];
422 $potTemplate = $pot[
'EXTRA'][
'TEMPLATE'][$key] ?? [];
423 $documentation = isset( $documentationCollection[$key] ) ?
424 $documentationCollection[$key]->translation() :
null;
426 $output .= $this->formatMessageBlock(
439 private function doGettextHeader( MessageCollection $collection, array $template ): string {
442 $code = $collection->code;
443 $name = Utilities::getLanguageName( $code );
444 $native = Utilities::getLanguageName( $code, $code );
445 $authors = $this->doAuthors( $collection );
446 if ( isset( $this->extra[
'header'] ) ) {
447 $extra =
"# --\n" . $this->extra[
'header'];
452 $group = $this->getGroup();
455 # Translation of {$group->getLabel()} to $name ($native)
456 # Exported from $wgSitename
462 $output = trim( $output ) .
"\n";
464 $specs = $template[
'HEADERS'] ?? [];
466 $timestamp = wfTimestampNow();
467 $specs[
'PO-Revision-Date'] = $this->formatTime( $timestamp );
468 if ( $this->offlineMode ) {
469 $specs[
'POT-Creation-Date'] = $this->formatTime( $timestamp );
471 $specs[
'X-POT-Import-Date'] = $this->formatTime( wfTimestamp( TS_MW, $this->getPotTime() ) );
473 $specs[
'Content-Type'] =
'text/plain; charset=UTF-8';
474 $specs[
'Content-Transfer-Encoding'] =
'8bit';
476 $specs[
'Language'] = LanguageCode::bcp47( $this->group->mapCode( $code ) );
478 Services::getInstance()->getHookRunner()->onTranslate_GettextFormat_headerFields(
484 $specs[
'X-Generator'] =
'MediaWiki '
485 . SpecialVersion::getVersion()
487 . Utilities::getVersion();
489 if ( $this->offlineMode ) {
490 $specs[
'X-Language-Code'] = $code;
491 $specs[
'X-Message-Group'] = $group->getId();
494 $specs[
'Plural-Forms'] = GettextPlural::getPluralRule( $code )
495 ?: GettextPlural::getPluralRule(
'en' );
497 $output .=
'msgid ""' .
"\n";
498 $output .=
'msgstr ""' .
"\n";
499 $output .=
'""' .
"\n";
501 foreach ( $specs as $k => $v ) {
502 $output .= $this->escape(
"$k: $v\n" ) .
"\n";
510 private function doAuthors( MessageCollection $collection ): string {
512 $authors = $collection->getAuthors();
513 $authors = $this->filterAuthors( $authors, $collection->code );
515 foreach ( $authors as $author ) {
516 $output .=
"# Author: $author\n";
522 private function formatMessageBlock(
528 ?
string $documentation
530 $header = $this->formatDocumentation( $documentation );
533 $comments = $pot[
'comments'] ?? $trans[
'comments'] ?? [];
534 foreach ( $comments as $type => $typecomments ) {
535 foreach ( $typecomments as $comment ) {
536 $header .=
"#$type $comment\n";
540 $flags = $pot[
'flags'] ?? $trans[
'flags'] ?? [];
541 $flags = array_merge( $message->getTags(), $flags );
543 if ( $this->offlineMode ) {
544 $content .=
'msgctxt ' . $this->escape( $key ) .
"\n";
546 $ctxt = $pot[
'ctxt'] ?? $trans[
'ctxt'] ??
false;
547 if ( $ctxt !==
false ) {
548 $content .=
'msgctxt ' . $this->escape( $ctxt ) .
"\n";
552 $msgid = $message->definition();
553 $msgstr = $message->translation() ??
'';
554 if ( str_contains( $msgstr, TRANSLATE_FUZZY ) ) {
555 $msgstr = str_replace( TRANSLATE_FUZZY,
'', $msgstr );
560 if ( GettextPlural::hasPlural( $msgid ) ) {
561 $forms = GettextPlural::unflatten( $msgid, 2 );
562 $content .=
'msgid ' . $this->escape( $forms[0] ) .
"\n";
563 $content .=
'msgid_plural ' . $this->escape( $forms[1] ) .
"\n";
566 $forms = GettextPlural::unflatten( $msgstr, $pluralCount );
567 foreach ( $forms as $index => $form ) {
568 $content .=
"msgstr[$index] " . $this->escape( $form ) .
"\n";
570 }
catch ( GettextPluralException ) {
571 $flags[] =
'invalid-plural';
572 for ( $i = 0; $i < $pluralCount; $i++ ) {
573 $content .=
"msgstr[$i] \"\"\n";
577 $content .=
'msgid ' . $this->escape( $msgid ) .
"\n";
578 $content .=
'msgstr ' . $this->escape( $msgstr ) .
"\n";
583 $header .=
'#, ' . implode(
', ', array_unique( $flags ) ) .
"\n";
586 $output = $header ?:
"#\n";
587 $output .= $content .
"\n";
592 private function formatTime(
string $time ): string {
593 $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage(
'en' );
595 return $lang->sprintfDate(
'xnY-xnm-xnd xnH:xni:xns+0000', $time );
598 private function getPotTime(): string {
599 $cache = $this->group->getMessageGroupCache( $this->group->getSourceLanguage() );
601 return $cache->exists() ? $cache->getTimestamp() : wfTimestampNow();
604 private function formatDocumentation( ?
string $documentation ): string {
605 if ( !is_string( $documentation ) ) {
609 if ( !$this->offlineMode ) {
613 $lines = explode(
"\n", $documentation );
615 foreach ( $lines as $line ) {
616 $out .=
"#. [Wiki] $line\n";
622 private function escape(
string $line ): string {
624 $line = preg_replace(
'/(\s)\\\\$/',
'\1', $line );
625 $line = addcslashes( $line,
'\\"' );
626 $line = str_replace(
"\n",
'\n', $line );
627 return '"' . $line .
'"';
630 public function shouldOverwrite(
string $a,
string $b ): bool {
631 $regex =
'/^"(.+)-Date: \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\+\d\d\d\d\\\\n"$/m';
633 $a = preg_replace( $regex,
'', $a );
634 $b = preg_replace( $regex,
'', $b );
639 public static function getExtraSchema(): array {
652 '_values' => [
'simple',
'legacy' ],
655 '_type' =>
'boolean',
664 public function isContentEqual( ?
string $a, ?
string $b ): bool {
669 if ( $a ===
null || $b ===
null ) {
674 $parsedA = GettextPlural::parsePluralForms( $a );
675 $parsedB = GettextPlural::parsePluralForms( $b );
678 if ( count( $parsedA[1] ) !== count( $parsedB[1] ) ) {
682 }
catch ( GettextPluralException ) {
687 $expectedPluralCount = count( $parsedA[1] );
691 if ( $expectedPluralCount === 0 ) {
695 return GettextPlural::unflatten( $a, $expectedPluralCount )
696 === GettextPlural::unflatten( $b, $expectedPluralCount );
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