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 ] + $this->options->get( MainConfigNames::MessagesDirs );
988 private function loadCoreData(
string $code ) {
990 throw new InvalidArgumentException(
"Invalid language code requested" );
992 if ( $this->coreDataLoaded[$code] ??
false ) {
996 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
999 # Load the primary localisation from the source file
1000 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
1001 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
1003 # Merge primary localisation
1004 foreach ( $data as $key => $value ) {
1005 $this->mergeItem( $key, $coreData[ $key ], $value );
1008 # Fill in the fallback if it's not there already
1010 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1011 $coreData[
'fallback'] =
false;
1012 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1014 if ( $coreData[
'fallback'] !==
null ) {
1015 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1017 $coreData[
'fallbackSequence'] = [];
1019 $len = count( $coreData[
'fallbackSequence'] );
1021 # Before we add the 'en' fallback for messages, keep a copy of
1022 # the original fallback sequence
1023 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1025 # Ensure that the sequence ends at 'en' for messages
1026 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1027 $coreData[
'fallbackSequence'][] =
'en';
1031 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1033 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1034 foreach ( self::CORE_ONLY_KEYS as $key ) {
1036 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1037 $coreData[$key] = $fbData[$key];
1042 $coreData[
'deps'] = $deps;
1043 foreach ( $coreData as $key => $item ) {
1044 $this->data[$code][$key] ??=
null;
1046 $this->mergeItem( $key, $this->data[$code][$key], $item );
1048 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1050 $key ===
'fallbackSequence' ||
1051 $key ===
'originalFallbackSequence'
1057 $this->loadedItems[$code][$key] =
true;
1061 $this->coreDataLoaded[$code] =
true;
1072 throw new InvalidArgumentException(
"Invalid language code requested" );
1074 $this->recachedLangs[ $code ] =
true;
1077 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1078 $this->data[$code] = [];
1079 $this->loadedItems[$code] = [];
1080 $this->loadedSubitems[$code] = [];
1081 $this->coreDataLoaded[$code] =
false;
1082 $this->loadCoreData( $code );
1083 $coreData = $this->data[$code];
1085 $deps = $coreData[
'deps'];
1086 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1088 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1089 $messageDirs = $this->getMessagesDirs();
1090 $translationAliasesDirs = $this->options->get( MainConfigNames::TranslationAliasesDirs );
1092 # Load non-JSON localisation data for extensions
1093 $extensionData = array_fill_keys( $codeSequence, $initialData );
1094 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1095 if ( isset( $messageDirs[$extension] ) || isset( $translationAliasesDirs[$extension] ) ) {
1096 # This extension has JSON message data; skip the PHP shim
1100 $data = $this->readPHPFile( $fileName,
'extension' );
1103 foreach ( $data as $key => $item ) {
1104 foreach ( $codeSequence as $csCode ) {
1105 if ( isset( $item[$csCode] ) ) {
1108 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1109 foreach ( $item[$csCode] as $subkey => $_ ) {
1110 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1113 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1124 # Load the localisation data for each fallback, then merge it into the full array
1125 $allData = $initialData;
1126 foreach ( $codeSequence as $csCode ) {
1127 $csData = $initialData;
1129 # Load core messages and the extension localisations.
1130 foreach ( $messageDirs as $dirs ) {
1131 foreach ( (array)$dirs as $dir ) {
1132 $fileName =
"$dir/$csCode.json";
1133 $messages = $this->readJSONFile( $fileName );
1135 foreach ( $messages as $subkey => $_ ) {
1136 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1138 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1144 foreach ( $translationAliasesDirs as $dirs ) {
1145 foreach ( (array)$dirs as $dir ) {
1146 $fileName =
"$dir/$csCode.json";
1147 $data = $this->readJSONFile( $fileName );
1149 foreach ( $data as $key => $item ) {
1152 $normalizedKey = lcfirst( $key );
1154 if ( $normalizedKey ===
'@metadata' ) {
1159 if ( !in_array( $normalizedKey, self::ALL_ALIAS_KEYS ) ) {
1160 throw new UnexpectedValueException(
1161 "Invalid key: \"$key\" for " . MainConfigNames::TranslationAliasesDirs .
". " .
1162 'Valid keys: ' . implode(
', ', self::ALL_ALIAS_KEYS )
1166 $this->mergeItem( $normalizedKey, $extensionData[$csCode][$normalizedKey], $item );
1173 # Merge non-JSON extension data
1174 if ( isset( $extensionData[$csCode] ) ) {
1175 foreach ( $extensionData[$csCode] as $key => $item ) {
1176 $this->mergeItem( $key, $csData[$key], $item );
1180 if ( $csCode === $code ) {
1181 # Merge core data into extension data
1182 foreach ( $coreData as $key => $item ) {
1183 $this->mergeItem( $key, $csData[$key], $item );
1186 # Load the secondary localisation from the source file to
1187 # avoid infinite cycles on cyclic fallbacks
1188 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1189 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1190 # Only merge the keys that make sense to merge
1191 foreach ( self::ALL_KEYS as $key ) {
1192 if ( !isset( $fbData[ $key ] ) ) {
1196 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1197 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1202 # Allow extensions an opportunity to adjust the data for this fallback
1203 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1205 # Merge the data for this fallback into the final array
1206 if ( $csCode === $code ) {
1209 foreach ( self::ALL_KEYS as $key ) {
1210 if ( !isset( $csData[$key] ) ) {
1215 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1216 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1222 if ( !isset( $allData[
'rtl'] ) ) {
1223 throw new RuntimeException( __METHOD__ .
': Localisation data failed validation check! ' .
1224 'Check that your languages/messages/MessagesEn.php file is intact.' );
1229 $deps[
'wgExtensionMessagesFiles'] =
1231 $deps[
'wgMessagesDirs'] =
1235 # Add dependencies to the cache entry
1236 $allData[
'deps'] = $deps;
1238 # Replace spaces with underscores in namespace names
1239 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1241 # And do the same for special page aliases. $page is an array.
1242 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1243 $page = str_replace(
' ',
'_', $page );
1245 # Decouple the reference to prevent accidental damage
1248 # If there were no plural rules, return an empty array
1249 $allData[
'pluralRules'] ??= [];
1250 $allData[
'compiledPluralRules'] ??= [];
1251 # If there were no plural rule types, return an empty array
1252 $allData[
'pluralRuleTypes'] ??= [];
1255 $allData[
'list'] = [];
1256 foreach ( self::SPLIT_KEYS as $key ) {
1257 $allData[
'list'][$key] = array_keys( $allData[$key] );
1261 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1263 # Save to the process cache and register the items loaded
1264 $this->data[$code] = $allData;
1265 $this->loadedItems[$code] = [];
1266 $this->loadedSubitems[$code] = [];
1267 foreach ( $allData as $key => $item ) {
1268 $this->loadedItems[$code][$key] =
true;
1271 # Prefix each item with its source language code before save
1272 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1274 foreach ( $allData[$key] as $subKey => $value ) {
1276 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1277 self::SOURCEPREFIX_SEPARATOR . $value;
1281 # Set the preload key
1282 $allData[
'preload'] = $this->buildPreload( $allData );
1284 # Save to the persistent cache
1285 $this->store->startWrite( $code );
1286 foreach ( $allData as $key => $value ) {
1287 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1288 foreach ( $value as $subkey => $subvalue ) {
1289 $this->store->set(
"$key:$subkey", $subvalue );
1292 $this->store->set( $key, $value );
1295 $this->store->finishWrite();
1297 # Clear out the MessageBlobStore
1298 # HACK: If using a null (i.e., disabled) storage backend, we
1299 # can't write to the MessageBlobStore either
1301 foreach ( $this->clearStoreCallbacks as $callback ) {
1316 private function buildPreload( $data ) {
1317 $preload = [
'messages' => [] ];
1318 foreach ( self::PRELOADED_KEYS as $key ) {
1319 $preload[$key] = $data[$key];
1322 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1323 $subitem = $data[
'messages'][$subkey] ??
null;
1324 $preload[
'messages'][$subkey] = $subitem;
1338 unset( $this->data[$code] );
1339 unset( $this->loadedItems[$code] );
1340 unset( $this->loadedSubitems[$code] );
1341 unset( $this->initialisedLangs[$code] );
1342 unset( $this->shallowFallbacks[$code] );
1343 unset( $this->sourceLanguage[$code] );
1344 unset( $this->coreDataLoaded[$code] );
1346 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1347 if ( $fbCode === $code ) {
1348 $this->unload( $shallowCode );
1357 foreach ( $this->initialisedLangs as $lang => $unused ) {
1358 $this->unload( $lang );
1367 $this->manualRecache =
false;