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',
'fallback8bitEncoding',
155 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
156 'namespaceAliases',
'dateFormats',
'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',
'fallback8bitEncoding',
'linkPrefixExtension',
173 'linkTrail',
'linkPrefixCharset',
'datePreferences',
174 'datePreferenceMigrationMap',
'defaultDateFormat',
'digitGroupingPattern',
186 private const ALL_EXCEPT_CORE_ONLY_KEYS = [
187 'namespaceNames',
'bookstoreList',
'magicWords',
'messages',
188 'namespaceAliases',
'dateFormats',
'specialPageAliases',
189 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
190 'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
200 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
201 'namespaceAliases',
'dateFormats',
'imageFiles',
'preloadedMessages'
208 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
215 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
220 private const MAGIC_WORD_KEYS = [
'magicWords' ];
225 private const SPLIT_KEYS = [
'messages' ];
231 private const SOURCE_PREFIX_KEYS = [
'messages' ];
236 private const SOURCEPREFIX_SEPARATOR =
':';
241 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
243 private const PLURAL_FILES = [
245 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
247 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
256 private static $pluralRules =
null;
272 private static $pluralRuleTypes =
null;
284 $storeArg[
'directory'] =
285 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
287 if ( !empty( $conf[
'storeClass'] ) ) {
288 $storeClass = $conf[
'storeClass'];
289 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
290 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
292 $storeClass = LCStoreCDB::class;
293 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
294 $storeClass = LCStoreDB::class;
295 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
296 } elseif ( $conf[
'store'] ===
'array' ) {
297 $storeClass = LCStoreStaticArray::class;
300 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
304 return new $storeClass( $storeArg );
310 public const CONSTRUCTOR_OPTIONS = [
314 MainConfigNames::ExtensionMessagesFiles,
315 MainConfigNames::MessagesDirs,
316 MainConfigNames::TranslationAliasesDirs,
335 LoggerInterface $logger,
336 array $clearStoreCallbacks,
342 $this->options = $options;
343 $this->store = $store;
344 $this->logger = $logger;
345 $this->clearStoreCallbacks = $clearStoreCallbacks;
346 $this->langNameUtils = $langNameUtils;
347 $this->hookRunner =
new HookRunner( $hookContainer );
350 $this->manualRecache = $options->
get(
'manualRecache' );
359 private static function isMergeableKey(
string $key ): bool {
360 static $mergeableKeys;
361 $mergeableKeys ??= array_fill_keys( [
362 ...self::MERGEABLE_MAP_KEYS,
363 ...self::MERGEABLE_ALIAS_LIST_KEYS,
364 ...self::OPTIONAL_MERGE_KEYS,
365 ...self::MAGIC_WORD_KEYS,
367 return isset( $mergeableKeys[$key] );
380 if ( !isset( $this->loadedItems[$code][$key] ) ) {
381 $this->loadItem( $code, $key );
384 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
385 return $this->shallowFallbacks[$code];
389 return $this->data[$code][$key];
400 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
401 !isset( $this->loadedItems[$code][$key] )
403 $this->loadSubitem( $code, $key, $subkey );
406 return $this->data[$code][$key][$subkey] ??
null;
419 $subitem = $this->getSubitem( $code, $key, $subkey );
421 if ( $subitem ===
null ) {
426 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
443 if ( in_array( $key, self::SPLIT_KEYS ) ) {
444 return $this->getSubitem( $code,
'list', $key );
446 $item = $this->getItem( $code, $key );
447 if ( is_array( $item ) ) {
448 return array_keys( $item );
461 private function loadItem( $code, $key ) {
462 if ( isset( $this->loadedItems[$code][$key] ) ) {
467 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
469 $key ===
'fallbackSequence' ||
470 $key ===
'originalFallbackSequence'
472 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
473 $this->loadCoreData( $code );
478 if ( !isset( $this->initialisedLangs[$code] ) ) {
479 $this->initLanguage( $code );
482 if ( isset( $this->loadedItems[$code][$key] ) ) {
487 if ( isset( $this->shallowFallbacks[$code] ) ) {
488 $this->loadItem( $this->shallowFallbacks[$code], $key );
493 if ( in_array( $key, self::SPLIT_KEYS ) ) {
494 $subkeyList = $this->getSubitem( $code,
'list', $key );
495 foreach ( $subkeyList as $subkey ) {
496 if ( isset( $this->data[$code][$key][$subkey] ) ) {
499 $this->loadSubitem( $code, $key, $subkey );
502 $this->data[$code][$key] = $this->store->get( $code, $key );
505 $this->loadedItems[$code][$key] =
true;
515 private function loadSubitem( $code, $key, $subkey ) {
516 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
517 $this->loadItem( $code, $key );
522 if ( !isset( $this->initialisedLangs[$code] ) ) {
523 $this->initLanguage( $code );
527 if ( isset( $this->loadedItems[$code][$key] ) ||
528 isset( $this->loadedSubitems[$code][$key][$subkey] )
533 if ( isset( $this->shallowFallbacks[$code] ) ) {
534 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
539 $value = $this->store->get( $code,
"$key:$subkey" );
540 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
542 $this->sourceLanguage[$code][$key][$subkey],
543 $this->data[$code][$key][$subkey]
544 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
546 $this->data[$code][$key][$subkey] = $value;
549 $this->loadedSubitems[$code][$key][$subkey] =
true;
560 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
561 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
566 $deps = $this->store->get( $code,
'deps' );
567 $keys = $this->store->get( $code,
'list' );
568 $preload = $this->store->get( $code,
'preload' );
570 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
571 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
576 foreach ( $deps as $dep ) {
582 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
597 private function initLanguage( $code ) {
598 if ( isset( $this->initialisedLangs[$code] ) ) {
602 $this->initialisedLangs[$code] =
true;
604 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
605 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
606 $this->initShallowFallback( $code,
'en' );
611 # Re-cache the data if necessary
612 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
613 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
614 $this->recache( $code );
615 } elseif ( $code ===
'en' ) {
616 throw new RuntimeException(
'MessagesEn.php is missing.' );
618 $this->initShallowFallback( $code,
'en' );
625 $preload = $this->getItem( $code,
'preload' );
626 if ( $preload ===
null ) {
627 if ( $this->manualRecache ) {
629 if ( $code ===
'en' ) {
630 throw new RuntimeException(
'No localisation cache found for English. ' .
631 'Please run maintenance/rebuildLocalisationCache.php.' );
633 $this->initShallowFallback( $code,
'en' );
637 throw new RuntimeException(
'Invalid or missing localisation cache.' );
641 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
642 if ( !isset( $preload[$key] ) ) {
645 foreach ( $preload[$key] as $subkey => $value ) {
646 if ( $value !==
null ) {
648 $this->sourceLanguage[$code][$key][$subkey],
649 $preload[$key][$subkey]
650 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
652 $preload[$key][$subkey] =
null;
657 if ( isset( $this->data[$code] ) ) {
658 foreach ( $preload as $key => $value ) {
660 $this->mergeItem( $key, $this->data[$code][$key], $value );
663 $this->data[$code] = $preload;
665 foreach ( $preload as $key => $item ) {
666 if ( in_array( $key, self::SPLIT_KEYS ) ) {
667 foreach ( $item as $subkey => $subitem ) {
668 $this->loadedSubitems[$code][$key][$subkey] =
true;
671 $this->loadedItems[$code][$key] =
true;
683 private function initShallowFallback( $primaryCode, $fallbackCode ) {
684 $this->data[$primaryCode] =& $this->data[$fallbackCode];
685 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
686 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
687 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
688 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
702 if ( $_fileType ==
'core' ) {
703 foreach ( self::ALL_KEYS as $key ) {
706 if ( isset( $$key ) ) {
710 } elseif ( $_fileType ==
'extension' ) {
711 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
712 if ( isset( $$key ) ) {
716 } elseif ( $_fileType ==
'aliases' ) {
718 if ( isset( $aliases ) ) {
719 $data[
'aliases'] = $aliases;
722 throw new InvalidArgumentException( __METHOD__ .
": Invalid file type: $_fileType" );
734 private function readJSONFile( $fileName ) {
735 if ( !is_readable( $fileName ) ) {
739 $json = file_get_contents( $fileName );
740 if ( $json ===
false ) {
744 $data = FormatJson::decode( $json,
true );
745 if ( $data ===
null ) {
746 throw new RuntimeException( __METHOD__ .
": Invalid JSON file: $fileName" );
750 foreach ( $data as $key => $unused ) {
751 if ( $key ===
'' || $key[0] ===
'@' ) {
752 unset( $data[$key] );
766 private function getCompiledPluralRules( $code ) {
767 $rules = $this->getPluralRules( $code );
768 if ( $rules ===
null ) {
772 $compiledRules = Evaluator::compile( $rules );
773 }
catch ( CLDRPluralRuleError $e ) {
774 $this->logger->debug( $e->getMessage() );
779 return $compiledRules;
791 private function getPluralRules( $code ) {
792 if ( self::$pluralRules ===
null ) {
793 self::loadPluralFiles();
795 return self::$pluralRules[$code] ??
null;
807 private function getPluralRuleTypes( $code ) {
808 if ( self::$pluralRuleTypes ===
null ) {
809 self::loadPluralFiles();
811 return self::$pluralRuleTypes[$code] ??
null;
817 private static function loadPluralFiles() {
818 foreach ( self::PLURAL_FILES as $fileName ) {
819 self::loadPluralFile( $fileName );
829 private static function loadPluralFile( $fileName ) {
831 $xml = file_get_contents( $fileName );
833 throw new RuntimeException(
"Unable to read plurals file $fileName" );
835 $doc =
new DOMDocument;
836 $doc->loadXML( $xml );
837 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
838 foreach ( $rulesets as $ruleset ) {
839 $codes = $ruleset->getAttribute(
'locales' );
842 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
843 foreach ( $ruleElements as $elt ) {
844 $ruleType = $elt->getAttribute(
'count' );
845 if ( $ruleType ===
'other' ) {
849 $rules[] = $elt->nodeValue;
850 $ruleTypes[] = $ruleType;
852 foreach ( explode(
' ', $codes ) as $code ) {
853 self::$pluralRules[$code] = $rules;
854 self::$pluralRuleTypes[$code] = $ruleTypes;
867 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
869 $fileName = $this->langNameUtils->getMessagesFileName( $code );
870 if ( !is_file( $fileName ) ) {
874 $data = $this->readPHPFile( $fileName,
'core' );
888 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
891 'pluralRules' => $this->getPluralRules( $code ),
893 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
895 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
898 foreach ( self::PLURAL_FILES as $fileName ) {
913 private function mergeItem( $key, &$value, $fallbackValue ) {
914 if ( $value !==
null ) {
915 if ( $fallbackValue !==
null ) {
916 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
917 $value += $fallbackValue;
918 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
919 $value = array_merge_recursive( $value, $fallbackValue );
920 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
921 if ( !empty( $value[
'inherit'] ) ) {
922 $value = array_merge( $fallbackValue, $value );
925 unset( $value[
'inherit'] );
926 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
927 $this->mergeMagicWords( $value, $fallbackValue );
931 $value = $fallbackValue;
939 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
940 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
941 if ( !isset( $value[$magicName] ) ) {
942 $value[$magicName] = $fallbackInfo;
944 $value[$magicName] = [
948 ...array_slice( $value[$magicName], 1 ),
949 ...array_slice( $fallbackInfo, 1 ),
967 'core' =>
"$IP/languages/i18n",
968 'codex' =>
"$IP/languages/i18n/codex",
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;