61 private $manualRecache;
78 protected $sourceLanguage = [];
87 private $clearStoreCallbacks;
89 private $langNameUtils;
100 private $loadedItems = [];
108 private $loadedSubitems = [];
117 private $initialisedLangs = [];
126 private $shallowFallbacks = [];
133 private $recachedLangs = [];
145 private $coreDataLoaded = [];
151 'fallback',
'namespaceNames',
'bookstoreList',
152 'magicWords',
'messages',
'rtl',
153 'digitTransformTable',
'separatorTransformTable',
154 'minimumGroupingDigits',
'numberingSystem',
'fallback8bitEncoding',
155 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
156 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'datePreferences',
157 'datePreferenceMigrationMap',
'defaultDateFormat',
158 'specialPageAliases',
'imageFiles',
'preloadedMessages',
159 'namespaceGenderAliases',
'digitGroupingPattern',
'pluralRules',
160 'pluralRuleTypes',
'compiledPluralRules',
'formalityIndex'
170 private const CORE_ONLY_KEYS = [
171 'fallback',
'rtl',
'digitTransformTable',
'separatorTransformTable',
172 'minimumGroupingDigits',
'numberingSystem',
173 'fallback8bitEncoding',
'linkPrefixExtension',
174 'linkTrail',
'linkPrefixCharset',
'datePreferences',
175 'datePreferenceMigrationMap',
'defaultDateFormat',
'digitGroupingPattern',
187 private const ALL_EXCEPT_CORE_ONLY_KEYS = [
188 'namespaceNames',
'bookstoreList',
'magicWords',
'messages',
189 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'specialPageAliases',
190 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
191 'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
201 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
202 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'imageFiles',
'preloadedMessages'
209 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
216 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
221 private const MAGIC_WORD_KEYS = [
'magicWords' ];
226 private const SPLIT_KEYS = [
'messages' ];
232 private const SOURCE_PREFIX_KEYS = [
'messages' ];
237 private const SOURCEPREFIX_SEPARATOR =
':';
242 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
244 private const PLURAL_FILES = [
246 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
248 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
257 private static $pluralRules =
null;
273 private static $pluralRuleTypes =
null;
285 $storeArg[
'directory'] =
286 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
288 if ( !empty( $conf[
'storeClass'] ) ) {
289 $storeClass = $conf[
'storeClass'];
290 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
291 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
293 $storeClass = LCStoreCDB::class;
294 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
295 $storeClass = LCStoreDB::class;
296 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
297 } elseif ( $conf[
'store'] ===
'array' ) {
298 $storeClass = LCStoreStaticArray::class;
301 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
305 return new $storeClass( $storeArg );
311 public const CONSTRUCTOR_OPTIONS = [
315 MainConfigNames::ExtensionMessagesFiles,
316 MainConfigNames::MessagesDirs,
317 MainConfigNames::TranslationAliasesDirs,
336 LoggerInterface $logger,
337 array $clearStoreCallbacks,
343 $this->options = $options;
344 $this->store = $store;
345 $this->logger = $logger;
346 $this->clearStoreCallbacks = $clearStoreCallbacks;
347 $this->langNameUtils = $langNameUtils;
348 $this->hookRunner =
new HookRunner( $hookContainer );
351 $this->manualRecache = $options->
get(
'manualRecache' );
360 private static function isMergeableKey(
string $key ): bool {
361 static $mergeableKeys;
362 $mergeableKeys ??= array_fill_keys( [
363 ...self::MERGEABLE_MAP_KEYS,
364 ...self::MERGEABLE_ALIAS_LIST_KEYS,
365 ...self::OPTIONAL_MERGE_KEYS,
366 ...self::MAGIC_WORD_KEYS,
368 return isset( $mergeableKeys[$key] );
381 if ( !isset( $this->loadedItems[$code][$key] ) ) {
382 $this->loadItem( $code, $key );
385 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
386 return $this->shallowFallbacks[$code];
390 return $this->data[$code][$key];
401 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
402 !isset( $this->loadedItems[$code][$key] )
404 $this->loadSubitem( $code, $key, $subkey );
407 return $this->data[$code][$key][$subkey] ??
null;
420 $subitem = $this->getSubitem( $code, $key, $subkey );
422 if ( $subitem ===
null ) {
427 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
444 if ( in_array( $key, self::SPLIT_KEYS ) ) {
445 return $this->getSubitem( $code,
'list', $key );
447 $item = $this->getItem( $code, $key );
448 if ( is_array( $item ) ) {
449 return array_keys( $item );
462 private function loadItem( $code, $key ) {
463 if ( isset( $this->loadedItems[$code][$key] ) ) {
468 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
470 $key ===
'fallbackSequence' ||
471 $key ===
'originalFallbackSequence'
473 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
474 $this->loadCoreData( $code );
479 if ( !isset( $this->initialisedLangs[$code] ) ) {
480 $this->initLanguage( $code );
483 if ( isset( $this->loadedItems[$code][$key] ) ) {
488 if ( isset( $this->shallowFallbacks[$code] ) ) {
489 $this->loadItem( $this->shallowFallbacks[$code], $key );
494 if ( in_array( $key, self::SPLIT_KEYS ) ) {
495 $subkeyList = $this->getSubitem( $code,
'list', $key );
496 foreach ( $subkeyList as $subkey ) {
497 if ( isset( $this->data[$code][$key][$subkey] ) ) {
500 $this->loadSubitem( $code, $key, $subkey );
503 $this->data[$code][$key] = $this->store->get( $code, $key );
506 $this->loadedItems[$code][$key] =
true;
516 private function loadSubitem( $code, $key, $subkey ) {
517 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
518 $this->loadItem( $code, $key );
523 if ( !isset( $this->initialisedLangs[$code] ) ) {
524 $this->initLanguage( $code );
528 if ( isset( $this->loadedItems[$code][$key] ) ||
529 isset( $this->loadedSubitems[$code][$key][$subkey] )
534 if ( isset( $this->shallowFallbacks[$code] ) ) {
535 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
540 $value = $this->store->get( $code,
"$key:$subkey" );
541 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
543 $this->sourceLanguage[$code][$key][$subkey],
544 $this->data[$code][$key][$subkey]
545 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
547 $this->data[$code][$key][$subkey] = $value;
550 $this->loadedSubitems[$code][$key][$subkey] =
true;
561 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
562 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
567 $deps = $this->store->get( $code,
'deps' );
568 $keys = $this->store->get( $code,
'list' );
569 $preload = $this->store->get( $code,
'preload' );
571 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
572 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
577 foreach ( $deps as $dep ) {
583 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
598 private function initLanguage( $code ) {
599 if ( isset( $this->initialisedLangs[$code] ) ) {
603 $this->initialisedLangs[$code] =
true;
605 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
606 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
607 $this->initShallowFallback( $code,
'en' );
612 # Re-cache the data if necessary
613 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
614 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
615 $this->recache( $code );
616 } elseif ( $code ===
'en' ) {
617 throw new RuntimeException(
'MessagesEn.php is missing.' );
619 $this->initShallowFallback( $code,
'en' );
626 $preload = $this->getItem( $code,
'preload' );
627 if ( $preload ===
null ) {
628 if ( $this->manualRecache ) {
630 if ( $code ===
'en' ) {
631 throw new RuntimeException(
'No localisation cache found for English. ' .
632 'Please run maintenance/rebuildLocalisationCache.php.' );
634 $this->initShallowFallback( $code,
'en' );
638 throw new RuntimeException(
'Invalid or missing localisation cache.' );
642 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
643 if ( !isset( $preload[$key] ) ) {
646 foreach ( $preload[$key] as $subkey => $value ) {
647 if ( $value !==
null ) {
649 $this->sourceLanguage[$code][$key][$subkey],
650 $preload[$key][$subkey]
651 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
653 $preload[$key][$subkey] =
null;
658 if ( isset( $this->data[$code] ) ) {
659 foreach ( $preload as $key => $value ) {
661 $this->mergeItem( $key, $this->data[$code][$key], $value );
664 $this->data[$code] = $preload;
666 foreach ( $preload as $key => $item ) {
667 if ( in_array( $key, self::SPLIT_KEYS ) ) {
668 foreach ( $item as $subkey => $subitem ) {
669 $this->loadedSubitems[$code][$key][$subkey] =
true;
672 $this->loadedItems[$code][$key] =
true;
684 private function initShallowFallback( $primaryCode, $fallbackCode ) {
685 $this->data[$primaryCode] =& $this->data[$fallbackCode];
686 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
687 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
688 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
689 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
703 if ( $_fileType ==
'core' ) {
704 foreach ( self::ALL_KEYS as $key ) {
708 if ( isset( $$key ) ) {
712 } elseif ( $_fileType ==
'extension' ) {
713 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
715 if ( isset( $$key ) ) {
719 } elseif ( $_fileType ==
'aliases' ) {
721 if ( isset( $aliases ) ) {
722 $data[
'aliases'] = $aliases;
725 throw new InvalidArgumentException( __METHOD__ .
": Invalid file type: $_fileType" );
737 private function readJSONFile( $fileName ) {
738 if ( !is_readable( $fileName ) ) {
742 $json = file_get_contents( $fileName );
743 if ( $json ===
false ) {
747 $data = FormatJson::decode( $json,
true );
748 if ( $data ===
null ) {
749 throw new RuntimeException( __METHOD__ .
": Invalid JSON file: $fileName" );
753 foreach ( $data as $key => $unused ) {
754 if ( $key ===
'' || $key[0] ===
'@' ) {
755 unset( $data[$key] );
769 private function getCompiledPluralRules( $code ) {
770 $rules = $this->getPluralRules( $code );
771 if ( $rules ===
null ) {
775 $compiledRules = Evaluator::compile( $rules );
776 }
catch ( CLDRPluralRuleError $e ) {
777 $this->logger->debug( $e->getMessage() );
782 return $compiledRules;
794 private function getPluralRules( $code ) {
795 if ( self::$pluralRules ===
null ) {
796 self::loadPluralFiles();
798 return self::$pluralRules[$code] ??
null;
810 private function getPluralRuleTypes( $code ) {
811 if ( self::$pluralRuleTypes ===
null ) {
812 self::loadPluralFiles();
814 return self::$pluralRuleTypes[$code] ??
null;
820 private static function loadPluralFiles() {
821 foreach ( self::PLURAL_FILES as $fileName ) {
822 self::loadPluralFile( $fileName );
832 private static function loadPluralFile( $fileName ) {
834 $xml = file_get_contents( $fileName );
836 throw new RuntimeException(
"Unable to read plurals file $fileName" );
838 $doc =
new DOMDocument;
839 $doc->loadXML( $xml );
840 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
841 foreach ( $rulesets as $ruleset ) {
842 $codes = $ruleset->getAttribute(
'locales' );
845 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
846 foreach ( $ruleElements as $elt ) {
847 $ruleType = $elt->getAttribute(
'count' );
848 if ( $ruleType ===
'other' ) {
852 $rules[] = $elt->nodeValue;
853 $ruleTypes[] = $ruleType;
855 foreach ( explode(
' ', $codes ) as $code ) {
856 self::$pluralRules[$code] = $rules;
857 self::$pluralRuleTypes[$code] = $ruleTypes;
870 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
872 $fileName = $this->langNameUtils->getMessagesFileName( $code );
873 if ( !is_file( $fileName ) ) {
877 $data = $this->readPHPFile( $fileName,
'core' );
891 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
894 'pluralRules' => $this->getPluralRules( $code ),
896 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
898 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
901 foreach ( self::PLURAL_FILES as $fileName ) {
916 private function mergeItem( $key, &$value, $fallbackValue ) {
917 if ( $value !==
null ) {
918 if ( $fallbackValue !==
null ) {
919 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
920 $value += $fallbackValue;
921 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
922 $value = array_merge_recursive( $value, $fallbackValue );
923 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
924 if ( !empty( $value[
'inherit'] ) ) {
925 $value = array_merge( $fallbackValue, $value );
928 unset( $value[
'inherit'] );
929 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
930 $this->mergeMagicWords( $value, $fallbackValue );
934 $value = $fallbackValue;
938 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
939 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
940 if ( !isset( $value[$magicName] ) ) {
941 $value[$magicName] = $fallbackInfo;
943 $value[$magicName] = [
947 ...array_slice( $value[$magicName], 1 ),
948 ...array_slice( $fallbackInfo, 1 ),
966 'core' =>
"$IP/languages/i18n",
967 'codex' =>
"$IP/languages/i18n/codex",
968 'datetime' =>
"$IP/languages/i18n/datetime",
969 'exif' =>
"$IP/languages/i18n/exif",
970 'preferences' =>
"$IP/languages/i18n/preferences",
971 'api' =>
"$IP/includes/api/i18n",
972 'rest' =>
"$IP/includes/Rest/i18n",
973 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
974 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
975 'installer' =>
"$IP/includes/installer/i18n",
976 ] + $this->options->get( MainConfigNames::MessagesDirs );
989 private function loadCoreData(
string $code ) {
991 throw new InvalidArgumentException(
"Invalid language code requested" );
993 if ( $this->coreDataLoaded[$code] ??
false ) {
997 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
1000 # Load the primary localisation from the source file
1001 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
1002 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
1004 # Merge primary localisation
1005 foreach ( $data as $key => $value ) {
1006 $this->mergeItem( $key, $coreData[ $key ], $value );
1009 # Fill in the fallback if it's not there already
1011 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1012 $coreData[
'fallback'] =
false;
1013 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1015 if ( $coreData[
'fallback'] !==
null ) {
1016 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1018 $coreData[
'fallbackSequence'] = [];
1020 $len = count( $coreData[
'fallbackSequence'] );
1022 # Before we add the 'en' fallback for messages, keep a copy of
1023 # the original fallback sequence
1024 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1026 # Ensure that the sequence ends at 'en' for messages
1027 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1028 $coreData[
'fallbackSequence'][] =
'en';
1032 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1034 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1035 foreach ( self::CORE_ONLY_KEYS as $key ) {
1037 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1038 $coreData[$key] = $fbData[$key];
1043 $coreData[
'deps'] = $deps;
1044 foreach ( $coreData as $key => $item ) {
1045 $this->data[$code][$key] ??=
null;
1047 $this->mergeItem( $key, $this->data[$code][$key], $item );
1049 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1051 $key ===
'fallbackSequence' ||
1052 $key ===
'originalFallbackSequence'
1058 $this->loadedItems[$code][$key] =
true;
1062 $this->coreDataLoaded[$code] =
true;
1073 throw new InvalidArgumentException(
"Invalid language code requested" );
1075 $this->recachedLangs[ $code ] =
true;
1078 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1079 $this->data[$code] = [];
1080 $this->loadedItems[$code] = [];
1081 $this->loadedSubitems[$code] = [];
1082 $this->coreDataLoaded[$code] =
false;
1083 $this->loadCoreData( $code );
1084 $coreData = $this->data[$code];
1086 $deps = $coreData[
'deps'];
1087 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1089 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1090 $messageDirs = $this->getMessagesDirs();
1091 $translationAliasesDirs = $this->options->get( MainConfigNames::TranslationAliasesDirs );
1093 # Load non-JSON localisation data for extensions
1094 $extensionData = array_fill_keys( $codeSequence, $initialData );
1095 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1096 if ( isset( $messageDirs[$extension] ) || isset( $translationAliasesDirs[$extension] ) ) {
1097 # This extension has JSON message data; skip the PHP shim
1101 $data = $this->readPHPFile( $fileName,
'extension' );
1104 foreach ( $data as $key => $item ) {
1105 foreach ( $codeSequence as $csCode ) {
1106 if ( isset( $item[$csCode] ) ) {
1109 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1110 foreach ( $item[$csCode] as $subkey => $_ ) {
1111 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1114 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1125 # Load the localisation data for each fallback, then merge it into the full array
1126 $allData = $initialData;
1127 foreach ( $codeSequence as $csCode ) {
1128 $csData = $initialData;
1130 # Load core messages and the extension localisations.
1131 foreach ( $messageDirs as $dirs ) {
1132 foreach ( (array)$dirs as $dir ) {
1133 $fileName =
"$dir/$csCode.json";
1134 $messages = $this->readJSONFile( $fileName );
1136 foreach ( $messages as $subkey => $_ ) {
1137 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1139 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1145 foreach ( $translationAliasesDirs as $dirs ) {
1146 foreach ( (array)$dirs as $dir ) {
1147 $fileName =
"$dir/$csCode.json";
1148 $data = $this->readJSONFile( $fileName );
1150 foreach ( $data as $key => $item ) {
1153 $normalizedKey = lcfirst( $key );
1155 if ( $normalizedKey ===
'@metadata' ) {
1160 if ( !in_array( $normalizedKey, self::ALL_ALIAS_KEYS ) ) {
1161 throw new UnexpectedValueException(
1162 "Invalid key: \"$key\" for " . MainConfigNames::TranslationAliasesDirs .
". " .
1163 'Valid keys: ' . implode(
', ', self::ALL_ALIAS_KEYS )
1167 $this->mergeItem( $normalizedKey, $extensionData[$csCode][$normalizedKey], $item );
1174 # Merge non-JSON extension data
1175 if ( isset( $extensionData[$csCode] ) ) {
1176 foreach ( $extensionData[$csCode] as $key => $item ) {
1177 $this->mergeItem( $key, $csData[$key], $item );
1181 if ( $csCode === $code ) {
1182 # Merge core data into extension data
1183 foreach ( $coreData as $key => $item ) {
1184 $this->mergeItem( $key, $csData[$key], $item );
1187 # Load the secondary localisation from the source file to
1188 # avoid infinite cycles on cyclic fallbacks
1189 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1190 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1191 # Only merge the keys that make sense to merge
1192 foreach ( self::ALL_KEYS as $key ) {
1193 if ( !isset( $fbData[ $key ] ) ) {
1197 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1198 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1203 # Allow extensions an opportunity to adjust the data for this fallback
1204 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1206 # Merge the data for this fallback into the final array
1207 if ( $csCode === $code ) {
1210 foreach ( self::ALL_KEYS as $key ) {
1211 if ( !isset( $csData[$key] ) ) {
1216 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1217 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1223 if ( !isset( $allData[
'rtl'] ) ) {
1224 throw new RuntimeException( __METHOD__ .
': Localisation data failed validation check! ' .
1225 'Check that your languages/messages/MessagesEn.php file is intact.' );
1230 $deps[
'wgExtensionMessagesFiles'] =
1232 $deps[
'wgMessagesDirs'] =
1236 # Add dependencies to the cache entry
1237 $allData[
'deps'] = $deps;
1239 # Replace spaces with underscores in namespace names
1240 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1242 # And do the same for special page aliases. $page is an array.
1243 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1244 $page = str_replace(
' ',
'_', $page );
1246 # Decouple the reference to prevent accidental damage
1249 # If there were no plural rules, return an empty array
1250 $allData[
'pluralRules'] ??= [];
1251 $allData[
'compiledPluralRules'] ??= [];
1252 # If there were no plural rule types, return an empty array
1253 $allData[
'pluralRuleTypes'] ??= [];
1256 $allData[
'list'] = [];
1257 foreach ( self::SPLIT_KEYS as $key ) {
1258 $allData[
'list'][$key] = array_keys( $allData[$key] );
1262 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1264 # Save to the process cache and register the items loaded
1265 $this->data[$code] = $allData;
1266 $this->loadedItems[$code] = [];
1267 $this->loadedSubitems[$code] = [];
1268 foreach ( $allData as $key => $item ) {
1269 $this->loadedItems[$code][$key] =
true;
1272 # Prefix each item with its source language code before save
1273 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1275 foreach ( $allData[$key] as $subKey => $value ) {
1277 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1278 self::SOURCEPREFIX_SEPARATOR . $value;
1282 # Set the preload key
1283 $allData[
'preload'] = $this->buildPreload( $allData );
1285 # Save to the persistent cache
1286 $this->store->startWrite( $code );
1287 foreach ( $allData as $key => $value ) {
1288 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1289 foreach ( $value as $subkey => $subvalue ) {
1290 $this->store->set(
"$key:$subkey", $subvalue );
1293 $this->store->set( $key, $value );
1296 $this->store->finishWrite();
1298 # Clear out the MessageBlobStore
1299 # HACK: If using a null (i.e., disabled) storage backend, we
1300 # can't write to the MessageBlobStore either
1302 foreach ( $this->clearStoreCallbacks as $callback ) {
1317 private function buildPreload( $data ) {
1318 $preload = [
'messages' => [] ];
1319 foreach ( self::PRELOADED_KEYS as $key ) {
1320 $preload[$key] = $data[$key];
1323 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1324 $subitem = $data[
'messages'][$subkey] ??
null;
1325 $preload[
'messages'][$subkey] = $subitem;
1339 unset( $this->data[$code] );
1340 unset( $this->loadedItems[$code] );
1341 unset( $this->loadedSubitems[$code] );
1342 unset( $this->initialisedLangs[$code] );
1343 unset( $this->shallowFallbacks[$code] );
1344 unset( $this->sourceLanguage[$code] );
1345 unset( $this->coreDataLoaded[$code] );
1347 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1348 if ( $fbCode === $code ) {
1349 $this->unload( $shallowCode );
1358 foreach ( $this->initialisedLangs as $lang => $unused ) {
1359 $this->unload( $lang );
1368 $this->manualRecache =
false;