59 private $manualRecache;
76 protected $sourceLanguage = [];
85 private $clearStoreCallbacks;
87 private $langNameUtils;
98 private $loadedItems = [];
106 private $loadedSubitems = [];
115 private $initialisedLangs = [];
124 private $shallowFallbacks = [];
131 private $recachedLangs = [];
143 private $coreDataLoaded = [];
149 'fallback',
'namespaceNames',
'bookstoreList',
150 'magicWords',
'messages',
'rtl',
151 'digitTransformTable',
'separatorTransformTable',
152 'minimumGroupingDigits',
'fallback8bitEncoding',
153 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
154 'namespaceAliases',
'dateFormats',
'datePreferences',
155 'datePreferenceMigrationMap',
'defaultDateFormat',
156 'specialPageAliases',
'imageFiles',
'preloadedMessages',
157 'namespaceGenderAliases',
'digitGroupingPattern',
'pluralRules',
158 'pluralRuleTypes',
'compiledPluralRules',
168 private const CORE_ONLY_KEYS = [
169 'fallback',
'rtl',
'digitTransformTable',
'separatorTransformTable',
170 'minimumGroupingDigits',
'fallback8bitEncoding',
'linkPrefixExtension',
171 'linkTrail',
'linkPrefixCharset',
'datePreferences',
172 'datePreferenceMigrationMap',
'defaultDateFormat',
'digitGroupingPattern',
183 private const ALL_EXCEPT_CORE_ONLY_KEYS = [
184 'namespaceNames',
'bookstoreList',
'magicWords',
'messages',
185 'namespaceAliases',
'dateFormats',
'specialPageAliases',
186 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
187 'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
197 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
198 'namespaceAliases',
'dateFormats',
'imageFiles',
'preloadedMessages'
205 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
212 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
217 private const MAGIC_WORD_KEYS = [
'magicWords' ];
222 private const SPLIT_KEYS = [
'messages' ];
228 private const SOURCE_PREFIX_KEYS = [
'messages' ];
233 private const SOURCEPREFIX_SEPARATOR =
':';
238 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
240 private const PLURAL_FILES = [
242 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
244 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
253 private static $pluralRules =
null;
269 private static $pluralRuleTypes =
null;
281 $storeArg[
'directory'] =
282 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
284 if ( !empty( $conf[
'storeClass'] ) ) {
285 $storeClass = $conf[
'storeClass'];
286 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
287 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
289 $storeClass = LCStoreCDB::class;
290 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
291 $storeClass = LCStoreDB::class;
292 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
293 } elseif ( $conf[
'store'] ===
'array' ) {
294 $storeClass = LCStoreStaticArray::class;
296 throw new ConfigException(
297 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
301 return new $storeClass( $storeArg );
307 public const CONSTRUCTOR_OPTIONS = [
311 MainConfigNames::ExtensionMessagesFiles,
312 MainConfigNames::MessagesDirs,
313 MainConfigNames::TranslationAliasesDirs,
332 LoggerInterface $logger,
333 array $clearStoreCallbacks,
339 $this->options = $options;
340 $this->store = $store;
341 $this->logger = $logger;
342 $this->clearStoreCallbacks = $clearStoreCallbacks;
343 $this->langNameUtils = $langNameUtils;
344 $this->hookRunner =
new HookRunner( $hookContainer );
347 $this->manualRecache = $options->
get(
'manualRecache' );
356 private static function isMergeableKey(
string $key ): bool {
357 static $mergeableKeys;
358 $mergeableKeys ??= array_fill_keys( [
359 ...self::MERGEABLE_MAP_KEYS,
360 ...self::MERGEABLE_ALIAS_LIST_KEYS,
361 ...self::OPTIONAL_MERGE_KEYS,
362 ...self::MAGIC_WORD_KEYS,
364 return isset( $mergeableKeys[$key] );
377 if ( !isset( $this->loadedItems[$code][$key] ) ) {
378 $this->loadItem( $code, $key );
381 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
382 return $this->shallowFallbacks[$code];
386 return $this->data[$code][$key];
397 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
398 !isset( $this->loadedItems[$code][$key] )
400 $this->loadSubitem( $code, $key, $subkey );
403 return $this->data[$code][$key][$subkey] ??
null;
416 $subitem = $this->getSubitem( $code, $key, $subkey );
418 if ( $subitem ===
null ) {
423 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
440 if ( in_array( $key, self::SPLIT_KEYS ) ) {
441 return $this->getSubitem( $code,
'list', $key );
443 $item = $this->getItem( $code, $key );
444 if ( is_array( $item ) ) {
445 return array_keys( $item );
458 private function loadItem( $code, $key ) {
459 if ( isset( $this->loadedItems[$code][$key] ) ) {
464 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
466 $key ===
'fallbackSequence' ||
467 $key ===
'originalFallbackSequence'
469 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
470 $this->loadCoreData( $code );
475 if ( !isset( $this->initialisedLangs[$code] ) ) {
476 $this->initLanguage( $code );
479 if ( isset( $this->loadedItems[$code][$key] ) ) {
484 if ( isset( $this->shallowFallbacks[$code] ) ) {
485 $this->loadItem( $this->shallowFallbacks[$code], $key );
490 if ( in_array( $key, self::SPLIT_KEYS ) ) {
491 $subkeyList = $this->getSubitem( $code,
'list', $key );
492 foreach ( $subkeyList as $subkey ) {
493 if ( isset( $this->data[$code][$key][$subkey] ) ) {
496 $this->loadSubitem( $code, $key, $subkey );
499 $this->data[$code][$key] = $this->store->get( $code, $key );
502 $this->loadedItems[$code][$key] =
true;
512 private function loadSubitem( $code, $key, $subkey ) {
513 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
514 $this->loadItem( $code, $key );
519 if ( !isset( $this->initialisedLangs[$code] ) ) {
520 $this->initLanguage( $code );
524 if ( isset( $this->loadedItems[$code][$key] ) ||
525 isset( $this->loadedSubitems[$code][$key][$subkey] )
530 if ( isset( $this->shallowFallbacks[$code] ) ) {
531 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
536 $value = $this->store->get( $code,
"$key:$subkey" );
537 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
539 $this->sourceLanguage[$code][$key][$subkey],
540 $this->data[$code][$key][$subkey]
541 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
543 $this->data[$code][$key][$subkey] = $value;
546 $this->loadedSubitems[$code][$key][$subkey] =
true;
557 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
558 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
563 $deps = $this->store->get( $code,
'deps' );
564 $keys = $this->store->get( $code,
'list' );
565 $preload = $this->store->get( $code,
'preload' );
567 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
568 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
573 foreach ( $deps as $dep ) {
579 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
594 private function initLanguage( $code ) {
595 if ( isset( $this->initialisedLangs[$code] ) ) {
599 $this->initialisedLangs[$code] =
true;
601 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
602 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
603 $this->initShallowFallback( $code,
'en' );
608 # Re-cache the data if necessary
609 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
610 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
611 $this->recache( $code );
612 } elseif ( $code ===
'en' ) {
613 throw new RuntimeException(
'MessagesEn.php is missing.' );
615 $this->initShallowFallback( $code,
'en' );
622 $preload = $this->getItem( $code,
'preload' );
623 if ( $preload ===
null ) {
624 if ( $this->manualRecache ) {
626 if ( $code ===
'en' ) {
627 throw new RuntimeException(
'No localisation cache found for English. ' .
628 'Please run maintenance/rebuildLocalisationCache.php.' );
630 $this->initShallowFallback( $code,
'en' );
634 throw new RuntimeException(
'Invalid or missing localisation cache.' );
638 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
639 if ( !isset( $preload[$key] ) ) {
642 foreach ( $preload[$key] as $subkey => $value ) {
643 if ( $value !==
null ) {
645 $this->sourceLanguage[$code][$key][$subkey],
646 $preload[$key][$subkey]
647 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
649 $preload[$key][$subkey] =
null;
654 if ( isset( $this->data[$code] ) ) {
655 foreach ( $preload as $key => $value ) {
657 $this->mergeItem( $key, $this->data[$code][$key], $value );
660 $this->data[$code] = $preload;
662 foreach ( $preload as $key => $item ) {
663 if ( in_array( $key, self::SPLIT_KEYS ) ) {
664 foreach ( $item as $subkey => $subitem ) {
665 $this->loadedSubitems[$code][$key][$subkey] =
true;
668 $this->loadedItems[$code][$key] =
true;
680 private function initShallowFallback( $primaryCode, $fallbackCode ) {
681 $this->data[$primaryCode] =& $this->data[$fallbackCode];
682 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
683 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
684 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
685 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
699 if ( $_fileType ==
'core' ) {
700 foreach ( self::ALL_KEYS as $key ) {
703 if ( isset( $$key ) ) {
707 } elseif ( $_fileType ==
'extension' ) {
708 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
709 if ( isset( $$key ) ) {
713 } elseif ( $_fileType ==
'aliases' ) {
715 if ( isset( $aliases ) ) {
716 $data[
'aliases'] = $aliases;
719 throw new InvalidArgumentException( __METHOD__ .
": Invalid file type: $_fileType" );
731 private function readJSONFile( $fileName ) {
732 if ( !is_readable( $fileName ) ) {
736 $json = file_get_contents( $fileName );
737 if ( $json ===
false ) {
742 if ( $data ===
null ) {
743 throw new RuntimeException( __METHOD__ .
": Invalid JSON file: $fileName" );
747 foreach ( $data as $key => $unused ) {
748 if ( $key ===
'' || $key[0] ===
'@' ) {
749 unset( $data[$key] );
763 private function getCompiledPluralRules( $code ) {
764 $rules = $this->getPluralRules( $code );
765 if ( $rules ===
null ) {
769 $compiledRules = Evaluator::compile( $rules );
770 }
catch ( CLDRPluralRuleError $e ) {
771 $this->logger->debug( $e->getMessage() );
776 return $compiledRules;
788 private function getPluralRules( $code ) {
789 if ( self::$pluralRules ===
null ) {
790 self::loadPluralFiles();
792 return self::$pluralRules[$code] ??
null;
804 private function getPluralRuleTypes( $code ) {
805 if ( self::$pluralRuleTypes ===
null ) {
806 self::loadPluralFiles();
808 return self::$pluralRuleTypes[$code] ??
null;
814 private static function loadPluralFiles() {
815 foreach ( self::PLURAL_FILES as $fileName ) {
816 self::loadPluralFile( $fileName );
826 private static function loadPluralFile( $fileName ) {
828 $xml = file_get_contents( $fileName );
830 throw new RuntimeException(
"Unable to read plurals file $fileName" );
832 $doc =
new DOMDocument;
833 $doc->loadXML( $xml );
834 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
835 foreach ( $rulesets as $ruleset ) {
836 $codes = $ruleset->getAttribute(
'locales' );
839 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
840 foreach ( $ruleElements as $elt ) {
841 $ruleType = $elt->getAttribute(
'count' );
842 if ( $ruleType ===
'other' ) {
846 $rules[] = $elt->nodeValue;
847 $ruleTypes[] = $ruleType;
849 foreach ( explode(
' ', $codes ) as $code ) {
850 self::$pluralRules[$code] = $rules;
851 self::$pluralRuleTypes[$code] = $ruleTypes;
864 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
866 $fileName = $this->langNameUtils->getMessagesFileName( $code );
867 if ( !is_file( $fileName ) ) {
871 $data = $this->readPHPFile( $fileName,
'core' );
885 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
888 'pluralRules' => $this->getPluralRules( $code ),
890 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
892 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
895 foreach ( self::PLURAL_FILES as $fileName ) {
910 private function mergeItem( $key, &$value, $fallbackValue ) {
911 if ( $value !==
null ) {
912 if ( $fallbackValue !==
null ) {
913 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
914 $value += $fallbackValue;
915 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
916 $value = array_merge_recursive( $value, $fallbackValue );
917 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
918 if ( !empty( $value[
'inherit'] ) ) {
919 $value = array_merge( $fallbackValue, $value );
922 unset( $value[
'inherit'] );
923 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
924 $this->mergeMagicWords( $value, $fallbackValue );
928 $value = $fallbackValue;
936 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
937 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
938 if ( !isset( $value[$magicName] ) ) {
939 $value[$magicName] = $fallbackInfo;
941 $value[$magicName] = [
945 ...array_slice( $value[$magicName], 1 ),
946 ...array_slice( $fallbackInfo, 1 ),
964 'core' =>
"$IP/languages/i18n",
965 'exif' =>
"$IP/languages/i18n/exif",
966 'api' =>
"$IP/includes/api/i18n",
967 'rest' =>
"$IP/includes/Rest/i18n",
968 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
969 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
970 ] + $this->options->get( MainConfigNames::MessagesDirs );
983 private function loadCoreData(
string $code ) {
985 throw new InvalidArgumentException(
"Invalid language code requested" );
987 if ( $this->coreDataLoaded[$code] ??
false ) {
991 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
994 # Load the primary localisation from the source file
995 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
996 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
998 # Merge primary localisation
999 foreach ( $data as $key => $value ) {
1000 $this->mergeItem( $key, $coreData[ $key ], $value );
1003 # Fill in the fallback if it's not there already
1005 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1006 $coreData[
'fallback'] =
false;
1007 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1009 if ( $coreData[
'fallback'] !==
null ) {
1010 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1012 $coreData[
'fallbackSequence'] = [];
1014 $len = count( $coreData[
'fallbackSequence'] );
1016 # Before we add the 'en' fallback for messages, keep a copy of
1017 # the original fallback sequence
1018 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1020 # Ensure that the sequence ends at 'en' for messages
1021 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1022 $coreData[
'fallbackSequence'][] =
'en';
1026 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1028 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1029 foreach ( self::CORE_ONLY_KEYS as $key ) {
1031 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1032 $coreData[$key] = $fbData[$key];
1037 $coreData[
'deps'] = $deps;
1038 foreach ( $coreData as $key => $item ) {
1039 $this->data[$code][$key] ??=
null;
1041 $this->mergeItem( $key, $this->data[$code][$key], $item );
1043 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1045 $key ===
'fallbackSequence' ||
1046 $key ===
'originalFallbackSequence'
1052 $this->loadedItems[$code][$key] =
true;
1056 $this->coreDataLoaded[$code] =
true;
1067 throw new InvalidArgumentException(
"Invalid language code requested" );
1069 $this->recachedLangs[ $code ] =
true;
1072 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1073 $this->data[$code] = [];
1074 $this->loadedItems[$code] = [];
1075 $this->loadedSubitems[$code] = [];
1076 $this->coreDataLoaded[$code] =
false;
1077 $this->loadCoreData( $code );
1078 $coreData = $this->data[$code];
1080 $deps = $coreData[
'deps'];
1081 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1083 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1084 $messageDirs = $this->getMessagesDirs();
1085 $translationAliasesDirs = $this->options->get( MainConfigNames::TranslationAliasesDirs );
1087 # Load non-JSON localisation data for extensions
1088 $extensionData = array_fill_keys( $codeSequence, $initialData );
1089 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1090 if ( isset( $messageDirs[$extension] ) || isset( $translationAliasesDirs[$extension] ) ) {
1091 # This extension has JSON message data; skip the PHP shim
1095 $data = $this->readPHPFile( $fileName,
'extension' );
1098 foreach ( $data as $key => $item ) {
1099 foreach ( $codeSequence as $csCode ) {
1100 if ( isset( $item[$csCode] ) ) {
1103 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1104 foreach ( $item[$csCode] as $subkey => $_ ) {
1105 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1108 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1119 # Load the localisation data for each fallback, then merge it into the full array
1120 $allData = $initialData;
1121 foreach ( $codeSequence as $csCode ) {
1122 $csData = $initialData;
1124 # Load core messages and the extension localisations.
1125 foreach ( $messageDirs as $dirs ) {
1126 foreach ( (array)$dirs as $dir ) {
1127 $fileName =
"$dir/$csCode.json";
1128 $messages = $this->readJSONFile( $fileName );
1130 foreach ( $messages as $subkey => $_ ) {
1131 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1133 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1139 foreach ( $translationAliasesDirs as $dirs ) {
1140 foreach ( (array)$dirs as $dir ) {
1141 $fileName =
"$dir/$csCode.json";
1142 $data = $this->readJSONFile( $fileName );
1144 foreach ( $data as $key => $item ) {
1147 $normalizedKey = lcfirst( $key );
1149 if ( $normalizedKey ===
'@metadata' ) {
1154 if ( !in_array( $normalizedKey, self::ALL_ALIAS_KEYS ) ) {
1155 throw new UnexpectedValueException(
1156 "Invalid key: \"$key\" for " . MainConfigNames::TranslationAliasesDirs .
". " .
1157 'Valid keys: ' . implode(
', ', self::ALL_ALIAS_KEYS )
1161 $this->mergeItem( $normalizedKey, $extensionData[$csCode][$normalizedKey], $item );
1168 # Merge non-JSON extension data
1169 if ( isset( $extensionData[$csCode] ) ) {
1170 foreach ( $extensionData[$csCode] as $key => $item ) {
1171 $this->mergeItem( $key, $csData[$key], $item );
1175 if ( $csCode === $code ) {
1176 # Merge core data into extension data
1177 foreach ( $coreData as $key => $item ) {
1178 $this->mergeItem( $key, $csData[$key], $item );
1181 # Load the secondary localisation from the source file to
1182 # avoid infinite cycles on cyclic fallbacks
1183 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1184 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1185 # Only merge the keys that make sense to merge
1186 foreach ( self::ALL_KEYS as $key ) {
1187 if ( !isset( $fbData[ $key ] ) ) {
1191 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1192 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1197 # Allow extensions an opportunity to adjust the data for this fallback
1198 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1200 # Merge the data for this fallback into the final array
1201 if ( $csCode === $code ) {
1204 foreach ( self::ALL_KEYS as $key ) {
1205 if ( !isset( $csData[$key] ) ) {
1210 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1211 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1217 if ( !isset( $allData[
'rtl'] ) ) {
1218 throw new RuntimeException( __METHOD__ .
': Localisation data failed validation check! ' .
1219 'Check that your languages/messages/MessagesEn.php file is intact.' );
1224 $deps[
'wgExtensionMessagesFiles'] =
1226 $deps[
'wgMessagesDirs'] =
1230 # Add dependencies to the cache entry
1231 $allData[
'deps'] = $deps;
1233 # Replace spaces with underscores in namespace names
1234 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1236 # And do the same for special page aliases. $page is an array.
1237 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1238 $page = str_replace(
' ',
'_', $page );
1240 # Decouple the reference to prevent accidental damage
1243 # If there were no plural rules, return an empty array
1244 $allData[
'pluralRules'] ??= [];
1245 $allData[
'compiledPluralRules'] ??= [];
1246 # If there were no plural rule types, return an empty array
1247 $allData[
'pluralRuleTypes'] ??= [];
1250 $allData[
'list'] = [];
1251 foreach ( self::SPLIT_KEYS as $key ) {
1252 $allData[
'list'][$key] = array_keys( $allData[$key] );
1256 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1258 # Save to the process cache and register the items loaded
1259 $this->data[$code] = $allData;
1260 $this->loadedItems[$code] = [];
1261 $this->loadedSubitems[$code] = [];
1262 foreach ( $allData as $key => $item ) {
1263 $this->loadedItems[$code][$key] =
true;
1266 # Prefix each item with its source language code before save
1267 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1269 foreach ( $allData[$key] as $subKey => $value ) {
1271 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1272 self::SOURCEPREFIX_SEPARATOR . $value;
1276 # Set the preload key
1277 $allData[
'preload'] = $this->buildPreload( $allData );
1279 # Save to the persistent cache
1280 $this->store->startWrite( $code );
1281 foreach ( $allData as $key => $value ) {
1282 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1283 foreach ( $value as $subkey => $subvalue ) {
1284 $this->store->set(
"$key:$subkey", $subvalue );
1287 $this->store->set( $key, $value );
1290 $this->store->finishWrite();
1292 # Clear out the MessageBlobStore
1293 # HACK: If using a null (i.e., disabled) storage backend, we
1294 # can't write to the MessageBlobStore either
1296 foreach ( $this->clearStoreCallbacks as $callback ) {
1311 private function buildPreload( $data ) {
1312 $preload = [
'messages' => [] ];
1313 foreach ( self::PRELOADED_KEYS as $key ) {
1314 $preload[$key] = $data[$key];
1317 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1318 $subitem = $data[
'messages'][$subkey] ??
null;
1319 $preload[
'messages'][$subkey] = $subitem;
1333 unset( $this->data[$code] );
1334 unset( $this->loadedItems[$code] );
1335 unset( $this->loadedSubitems[$code] );
1336 unset( $this->initialisedLangs[$code] );
1337 unset( $this->shallowFallbacks[$code] );
1338 unset( $this->sourceLanguage[$code] );
1339 unset( $this->coreDataLoaded[$code] );
1341 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1342 if ( $fbCode === $code ) {
1343 $this->unload( $shallowCode );
1352 foreach ( $this->initialisedLangs as $lang => $unused ) {
1353 $this->unload( $lang );
1362 $this->manualRecache =
false;