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',
194 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
195 'namespaceAliases',
'dateFormats',
'imageFiles',
'preloadedMessages'
202 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
209 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
214 private const MAGIC_WORD_KEYS = [
'magicWords' ];
219 private const SPLIT_KEYS = [
'messages' ];
225 private const SOURCE_PREFIX_KEYS = [
'messages' ];
230 private const SOURCEPREFIX_SEPARATOR =
':';
235 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
237 private const PLURAL_FILES = [
239 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
241 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
250 private static $pluralRules =
null;
266 private static $pluralRuleTypes =
null;
278 $storeArg[
'directory'] =
279 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
281 if ( !empty( $conf[
'storeClass'] ) ) {
282 $storeClass = $conf[
'storeClass'];
283 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
284 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
286 $storeClass = LCStoreCDB::class;
287 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
288 $storeClass = LCStoreDB::class;
289 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
290 } elseif ( $conf[
'store'] ===
'array' ) {
291 $storeClass = LCStoreStaticArray::class;
294 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
298 return new $storeClass( $storeArg );
304 public const CONSTRUCTOR_OPTIONS = [
308 MainConfigNames::ExtensionMessagesFiles,
309 MainConfigNames::MessagesDirs,
328 LoggerInterface $logger,
329 array $clearStoreCallbacks,
335 $this->options = $options;
336 $this->store = $store;
337 $this->logger = $logger;
338 $this->clearStoreCallbacks = $clearStoreCallbacks;
339 $this->langNameUtils = $langNameUtils;
340 $this->hookRunner =
new HookRunner( $hookContainer );
343 $this->manualRecache = $options->
get(
'manualRecache' );
352 private static function isMergeableKey(
string $key ): bool {
353 static $mergeableKeys;
354 $mergeableKeys ??= array_fill_keys( [
355 ...self::MERGEABLE_MAP_KEYS,
356 ...self::MERGEABLE_ALIAS_LIST_KEYS,
357 ...self::OPTIONAL_MERGE_KEYS,
358 ...self::MAGIC_WORD_KEYS,
360 return isset( $mergeableKeys[$key] );
373 if ( !isset( $this->loadedItems[$code][$key] ) ) {
374 $this->loadItem( $code, $key );
377 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
378 return $this->shallowFallbacks[$code];
382 return $this->data[$code][$key];
393 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
394 !isset( $this->loadedItems[$code][$key] )
396 $this->loadSubitem( $code, $key, $subkey );
399 return $this->data[$code][$key][$subkey] ??
null;
412 $subitem = $this->getSubitem( $code, $key, $subkey );
414 if ( $subitem ===
null ) {
419 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
436 if ( in_array( $key, self::SPLIT_KEYS ) ) {
437 return $this->getSubitem( $code,
'list', $key );
439 $item = $this->getItem( $code, $key );
440 if ( is_array( $item ) ) {
441 return array_keys( $item );
454 private function loadItem( $code, $key ) {
455 if ( isset( $this->loadedItems[$code][$key] ) ) {
460 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
462 $key ===
'fallbackSequence' ||
463 $key ===
'originalFallbackSequence'
465 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
466 $this->loadCoreData( $code );
471 if ( !isset( $this->initialisedLangs[$code] ) ) {
472 $this->initLanguage( $code );
475 if ( isset( $this->loadedItems[$code][$key] ) ) {
480 if ( isset( $this->shallowFallbacks[$code] ) ) {
481 $this->loadItem( $this->shallowFallbacks[$code], $key );
486 if ( in_array( $key, self::SPLIT_KEYS ) ) {
487 $subkeyList = $this->getSubitem( $code,
'list', $key );
488 foreach ( $subkeyList as $subkey ) {
489 if ( isset( $this->data[$code][$key][$subkey] ) ) {
492 $this->loadSubitem( $code, $key, $subkey );
495 $this->data[$code][$key] = $this->store->get( $code, $key );
498 $this->loadedItems[$code][$key] =
true;
508 private function loadSubitem( $code, $key, $subkey ) {
509 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
510 $this->loadItem( $code, $key );
515 if ( !isset( $this->initialisedLangs[$code] ) ) {
516 $this->initLanguage( $code );
520 if ( isset( $this->loadedItems[$code][$key] ) ||
521 isset( $this->loadedSubitems[$code][$key][$subkey] )
526 if ( isset( $this->shallowFallbacks[$code] ) ) {
527 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
532 $value = $this->store->get( $code,
"$key:$subkey" );
533 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
535 $this->sourceLanguage[$code][$key][$subkey],
536 $this->data[$code][$key][$subkey]
537 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
539 $this->data[$code][$key][$subkey] = $value;
542 $this->loadedSubitems[$code][$key][$subkey] =
true;
553 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
554 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
559 $deps = $this->store->get( $code,
'deps' );
560 $keys = $this->store->get( $code,
'list' );
561 $preload = $this->store->get( $code,
'preload' );
563 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
564 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
569 foreach ( $deps as $dep ) {
575 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
591 private function initLanguage( $code ) {
592 if ( isset( $this->initialisedLangs[$code] ) ) {
596 $this->initialisedLangs[$code] =
true;
598 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
599 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
600 $this->initShallowFallback( $code,
'en' );
605 # Re-cache the data if necessary
606 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
607 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
608 $this->recache( $code );
609 } elseif ( $code ===
'en' ) {
610 throw new MWException(
'MessagesEn.php is missing.' );
612 $this->initShallowFallback( $code,
'en' );
619 $preload = $this->getItem( $code,
'preload' );
620 if ( $preload ===
null ) {
621 if ( $this->manualRecache ) {
623 if ( $code ===
'en' ) {
624 throw new MWException(
'No localisation cache found for English. ' .
625 'Please run maintenance/rebuildLocalisationCache.php.' );
627 $this->initShallowFallback( $code,
'en' );
631 throw new MWException(
'Invalid or missing localisation cache.' );
635 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
636 if ( !isset( $preload[$key] ) ) {
639 foreach ( $preload[$key] as $subkey => $value ) {
640 if ( $value !==
null ) {
642 $this->sourceLanguage[$code][$key][$subkey],
643 $preload[$key][$subkey]
644 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
646 $preload[$key][$subkey] =
null;
651 if ( isset( $this->data[$code] ) ) {
652 foreach ( $preload as $key => $value ) {
654 $this->mergeItem( $key, $this->data[$code][$key], $value );
657 $this->data[$code] = $preload;
659 foreach ( $preload as $key => $item ) {
660 if ( in_array( $key, self::SPLIT_KEYS ) ) {
661 foreach ( $item as $subkey => $subitem ) {
662 $this->loadedSubitems[$code][$key][$subkey] =
true;
665 $this->loadedItems[$code][$key] =
true;
677 private function initShallowFallback( $primaryCode, $fallbackCode ) {
678 $this->data[$primaryCode] =& $this->data[$fallbackCode];
679 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
680 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
681 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
682 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
697 if ( $_fileType ==
'core' ) {
698 foreach ( self::ALL_KEYS as $key ) {
701 if ( isset( $$key ) ) {
705 } elseif ( $_fileType ==
'extension' ) {
706 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
707 if ( isset( $$key ) ) {
711 } elseif ( $_fileType ==
'aliases' ) {
713 if ( isset( $aliases ) ) {
714 $data[
'aliases'] = $aliases;
717 throw new MWException( __METHOD__ .
": Invalid file type: $_fileType" );
730 private function readJSONFile( $fileName ) {
731 if ( !is_readable( $fileName ) ) {
735 $json = file_get_contents( $fileName );
736 if ( $json ===
false ) {
741 if ( $data ===
null ) {
742 throw new MWException( __METHOD__ .
": Invalid JSON file: $fileName" );
746 foreach ( $data as $key => $unused ) {
747 if ( $key ===
'' || $key[0] ===
'@' ) {
748 unset( $data[$key] );
762 private function getCompiledPluralRules( $code ) {
763 $rules = $this->getPluralRules( $code );
764 if ( $rules ===
null ) {
768 $compiledRules = Evaluator::compile( $rules );
769 }
catch ( CLDRPluralRuleError $e ) {
770 $this->logger->debug( $e->getMessage() );
775 return $compiledRules;
787 private function getPluralRules( $code ) {
788 if ( self::$pluralRules ===
null ) {
789 self::loadPluralFiles();
791 return self::$pluralRules[$code] ??
null;
803 private function getPluralRuleTypes( $code ) {
804 if ( self::$pluralRuleTypes ===
null ) {
805 self::loadPluralFiles();
807 return self::$pluralRuleTypes[$code] ??
null;
813 private static function loadPluralFiles() {
814 foreach ( self::PLURAL_FILES as $fileName ) {
815 self::loadPluralFile( $fileName );
826 private static function loadPluralFile( $fileName ) {
828 $xml = file_get_contents( $fileName );
830 throw new MWException(
"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 MWException(
"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;
1068 throw new MWException(
"Invalid language code requested" );
1070 $this->recachedLangs[ $code ] =
true;
1073 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1074 $this->data[$code] = [];
1075 $this->loadedItems[$code] = [];
1076 $this->loadedSubitems[$code] = [];
1077 $this->coreDataLoaded[$code] =
false;
1078 $this->loadCoreData( $code );
1079 $coreData = $this->data[$code];
1081 $deps = $coreData[
'deps'];
1082 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1084 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1085 $messageDirs = $this->getMessagesDirs();
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] ) ) {
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 # Merge non-JSON extension data
1140 if ( isset( $extensionData[$csCode] ) ) {
1141 foreach ( $extensionData[$csCode] as $key => $item ) {
1142 $this->mergeItem( $key, $csData[$key], $item );
1146 if ( $csCode === $code ) {
1147 # Merge core data into extension data
1148 foreach ( $coreData as $key => $item ) {
1149 $this->mergeItem( $key, $csData[$key], $item );
1152 # Load the secondary localisation from the source file to
1153 # avoid infinite cycles on cyclic fallbacks
1154 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1155 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1156 # Only merge the keys that make sense to merge
1157 foreach ( self::ALL_KEYS as $key ) {
1158 if ( !isset( $fbData[ $key ] ) ) {
1162 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1163 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1168 # Allow extensions an opportunity to adjust the data for this fallback
1169 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1171 # Merge the data for this fallback into the final array
1172 if ( $csCode === $code ) {
1175 foreach ( self::ALL_KEYS as $key ) {
1176 if ( !isset( $csData[$key] ) ) {
1181 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1182 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1188 if ( !isset( $allData[
'rtl'] ) ) {
1189 throw new MWException( __METHOD__ .
': Localisation data failed validation check! ' .
1190 'Check that your languages/messages/MessagesEn.php file is intact.' );
1193 # Add cache dependencies for any referenced globals
1194 $deps[
'wgExtensionMessagesFiles'] =
new GlobalDependency(
'wgExtensionMessagesFiles' );
1200 # Add dependencies to the cache entry
1201 $allData[
'deps'] = $deps;
1203 # Replace spaces with underscores in namespace names
1204 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1206 # And do the same for special page aliases. $page is an array.
1207 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1208 $page = str_replace(
' ',
'_', $page );
1210 # Decouple the reference to prevent accidental damage
1213 # If there were no plural rules, return an empty array
1214 $allData[
'pluralRules'] ??= [];
1215 $allData[
'compiledPluralRules'] ??= [];
1216 # If there were no plural rule types, return an empty array
1217 $allData[
'pluralRuleTypes'] ??= [];
1220 $allData[
'list'] = [];
1221 foreach ( self::SPLIT_KEYS as $key ) {
1222 $allData[
'list'][$key] = array_keys( $allData[$key] );
1226 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1228 # Save to the process cache and register the items loaded
1229 $this->data[$code] = $allData;
1230 $this->loadedItems[$code] = [];
1231 $this->loadedSubitems[$code] = [];
1232 foreach ( $allData as $key => $item ) {
1233 $this->loadedItems[$code][$key] =
true;
1236 # Prefix each item with its source language code before save
1237 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1239 foreach ( $allData[$key] as $subKey => $value ) {
1241 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1242 self::SOURCEPREFIX_SEPARATOR . $value;
1246 # Set the preload key
1247 $allData[
'preload'] = $this->buildPreload( $allData );
1249 # Save to the persistent cache
1250 $this->store->startWrite( $code );
1251 foreach ( $allData as $key => $value ) {
1252 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1253 foreach ( $value as $subkey => $subvalue ) {
1254 $this->store->set(
"$key:$subkey", $subvalue );
1257 $this->store->set( $key, $value );
1260 $this->store->finishWrite();
1262 # Clear out the MessageBlobStore
1263 # HACK: If using a null (i.e., disabled) storage backend, we
1264 # can't write to the MessageBlobStore either
1266 foreach ( $this->clearStoreCallbacks as $callback ) {
1281 private function buildPreload( $data ) {
1282 $preload = [
'messages' => [] ];
1283 foreach ( self::PRELOADED_KEYS as $key ) {
1284 $preload[$key] = $data[$key];
1287 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1288 $subitem = $data[
'messages'][$subkey] ??
null;
1289 $preload[
'messages'][$subkey] = $subitem;
1303 unset( $this->data[$code] );
1304 unset( $this->loadedItems[$code] );
1305 unset( $this->loadedSubitems[$code] );
1306 unset( $this->initialisedLangs[$code] );
1307 unset( $this->shallowFallbacks[$code] );
1308 unset( $this->sourceLanguage[$code] );
1309 unset( $this->coreDataLoaded[$code] );
1311 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1312 if ( $fbCode === $code ) {
1313 $this->unload( $shallowCode );
1322 foreach ( $this->initialisedLangs as $lang => $unused ) {
1323 $this->unload( $lang );
1332 $this->manualRecache =
false;