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 ) {
707 if ( isset( $$key ) ) {
711 } elseif ( $_fileType ==
'extension' ) {
712 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
714 if ( isset( $$key ) ) {
718 } elseif ( $_fileType ==
'aliases' ) {
720 if ( isset( $aliases ) ) {
721 $data[
'aliases'] = $aliases;
724 throw new InvalidArgumentException( __METHOD__ .
": Invalid file type: $_fileType" );
736 private function readJSONFile( $fileName ) {
737 if ( !is_readable( $fileName ) ) {
741 $json = file_get_contents( $fileName );
742 if ( $json ===
false ) {
746 $data = FormatJson::decode( $json,
true );
747 if ( $data ===
null ) {
748 throw new RuntimeException( __METHOD__ .
": Invalid JSON file: $fileName" );
752 foreach ( $data as $key => $unused ) {
753 if ( $key ===
'' || $key[0] ===
'@' ) {
754 unset( $data[$key] );
768 private function getCompiledPluralRules( $code ) {
769 $rules = $this->getPluralRules( $code );
770 if ( $rules ===
null ) {
774 $compiledRules = Evaluator::compile( $rules );
775 }
catch ( CLDRPluralRuleError $e ) {
776 $this->logger->debug( $e->getMessage() );
781 return $compiledRules;
793 private function getPluralRules( $code ) {
794 if ( self::$pluralRules ===
null ) {
795 self::loadPluralFiles();
797 return self::$pluralRules[$code] ??
null;
809 private function getPluralRuleTypes( $code ) {
810 if ( self::$pluralRuleTypes ===
null ) {
811 self::loadPluralFiles();
813 return self::$pluralRuleTypes[$code] ??
null;
819 private static function loadPluralFiles() {
820 foreach ( self::PLURAL_FILES as $fileName ) {
821 self::loadPluralFile( $fileName );
831 private static function loadPluralFile( $fileName ) {
833 $xml = file_get_contents( $fileName );
835 throw new RuntimeException(
"Unable to read plurals file $fileName" );
837 $doc =
new DOMDocument;
838 $doc->loadXML( $xml );
839 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
840 foreach ( $rulesets as $ruleset ) {
841 $codes = $ruleset->getAttribute(
'locales' );
844 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
845 foreach ( $ruleElements as $elt ) {
846 $ruleType = $elt->getAttribute(
'count' );
847 if ( $ruleType ===
'other' ) {
851 $rules[] = $elt->nodeValue;
852 $ruleTypes[] = $ruleType;
854 foreach ( explode(
' ', $codes ) as $code ) {
855 self::$pluralRules[$code] = $rules;
856 self::$pluralRuleTypes[$code] = $ruleTypes;
869 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
871 $fileName = $this->langNameUtils->getMessagesFileName( $code );
872 if ( !is_file( $fileName ) ) {
876 $data = $this->readPHPFile( $fileName,
'core' );
890 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
893 'pluralRules' => $this->getPluralRules( $code ),
895 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
897 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
900 foreach ( self::PLURAL_FILES as $fileName ) {
915 private function mergeItem( $key, &$value, $fallbackValue ) {
916 if ( $value !==
null ) {
917 if ( $fallbackValue !==
null ) {
918 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
919 $value += $fallbackValue;
920 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
921 $value = array_merge_recursive( $value, $fallbackValue );
922 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
923 if ( !empty( $value[
'inherit'] ) ) {
924 $value = array_merge( $fallbackValue, $value );
927 unset( $value[
'inherit'] );
928 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
929 $this->mergeMagicWords( $value, $fallbackValue );
933 $value = $fallbackValue;
937 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
938 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
939 if ( !isset( $value[$magicName] ) ) {
940 $value[$magicName] = $fallbackInfo;
942 $value[$magicName] = [
946 ...array_slice( $value[$magicName], 1 ),
947 ...array_slice( $fallbackInfo, 1 ),
965 'core' =>
"$IP/languages/i18n",
966 'codex' =>
"$IP/languages/i18n/codex",
967 'exif' =>
"$IP/languages/i18n/exif",
968 'preferences' =>
"$IP/languages/i18n/preferences",
969 'api' =>
"$IP/includes/api/i18n",
970 'rest' =>
"$IP/includes/Rest/i18n",
971 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
972 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
973 'installer' =>
"$IP/includes/installer/i18n",
974 ] + $this->options->get( MainConfigNames::MessagesDirs );
987 private function loadCoreData(
string $code ) {
989 throw new InvalidArgumentException(
"Invalid language code requested" );
991 if ( $this->coreDataLoaded[$code] ??
false ) {
995 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
998 # Load the primary localisation from the source file
999 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
1000 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
1002 # Merge primary localisation
1003 foreach ( $data as $key => $value ) {
1004 $this->mergeItem( $key, $coreData[ $key ], $value );
1007 # Fill in the fallback if it's not there already
1009 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1010 $coreData[
'fallback'] =
false;
1011 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1013 if ( $coreData[
'fallback'] !==
null ) {
1014 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1016 $coreData[
'fallbackSequence'] = [];
1018 $len = count( $coreData[
'fallbackSequence'] );
1020 # Before we add the 'en' fallback for messages, keep a copy of
1021 # the original fallback sequence
1022 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1024 # Ensure that the sequence ends at 'en' for messages
1025 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1026 $coreData[
'fallbackSequence'][] =
'en';
1030 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1032 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1033 foreach ( self::CORE_ONLY_KEYS as $key ) {
1035 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1036 $coreData[$key] = $fbData[$key];
1041 $coreData[
'deps'] = $deps;
1042 foreach ( $coreData as $key => $item ) {
1043 $this->data[$code][$key] ??=
null;
1045 $this->mergeItem( $key, $this->data[$code][$key], $item );
1047 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1049 $key ===
'fallbackSequence' ||
1050 $key ===
'originalFallbackSequence'
1056 $this->loadedItems[$code][$key] =
true;
1060 $this->coreDataLoaded[$code] =
true;
1071 throw new InvalidArgumentException(
"Invalid language code requested" );
1073 $this->recachedLangs[ $code ] =
true;
1076 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1077 $this->data[$code] = [];
1078 $this->loadedItems[$code] = [];
1079 $this->loadedSubitems[$code] = [];
1080 $this->coreDataLoaded[$code] =
false;
1081 $this->loadCoreData( $code );
1082 $coreData = $this->data[$code];
1084 $deps = $coreData[
'deps'];
1085 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1087 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1088 $messageDirs = $this->getMessagesDirs();
1089 $translationAliasesDirs = $this->options->get( MainConfigNames::TranslationAliasesDirs );
1091 # Load non-JSON localisation data for extensions
1092 $extensionData = array_fill_keys( $codeSequence, $initialData );
1093 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1094 if ( isset( $messageDirs[$extension] ) || isset( $translationAliasesDirs[$extension] ) ) {
1095 # This extension has JSON message data; skip the PHP shim
1099 $data = $this->readPHPFile( $fileName,
'extension' );
1102 foreach ( $data as $key => $item ) {
1103 foreach ( $codeSequence as $csCode ) {
1104 if ( isset( $item[$csCode] ) ) {
1107 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1108 foreach ( $item[$csCode] as $subkey => $_ ) {
1109 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1112 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1123 # Load the localisation data for each fallback, then merge it into the full array
1124 $allData = $initialData;
1125 foreach ( $codeSequence as $csCode ) {
1126 $csData = $initialData;
1128 # Load core messages and the extension localisations.
1129 foreach ( $messageDirs as $dirs ) {
1130 foreach ( (array)$dirs as $dir ) {
1131 $fileName =
"$dir/$csCode.json";
1132 $messages = $this->readJSONFile( $fileName );
1134 foreach ( $messages as $subkey => $_ ) {
1135 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1137 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1143 foreach ( $translationAliasesDirs as $dirs ) {
1144 foreach ( (array)$dirs as $dir ) {
1145 $fileName =
"$dir/$csCode.json";
1146 $data = $this->readJSONFile( $fileName );
1148 foreach ( $data as $key => $item ) {
1151 $normalizedKey = lcfirst( $key );
1153 if ( $normalizedKey ===
'@metadata' ) {
1158 if ( !in_array( $normalizedKey, self::ALL_ALIAS_KEYS ) ) {
1159 throw new UnexpectedValueException(
1160 "Invalid key: \"$key\" for " . MainConfigNames::TranslationAliasesDirs .
". " .
1161 'Valid keys: ' . implode(
', ', self::ALL_ALIAS_KEYS )
1165 $this->mergeItem( $normalizedKey, $extensionData[$csCode][$normalizedKey], $item );
1172 # Merge non-JSON extension data
1173 if ( isset( $extensionData[$csCode] ) ) {
1174 foreach ( $extensionData[$csCode] as $key => $item ) {
1175 $this->mergeItem( $key, $csData[$key], $item );
1179 if ( $csCode === $code ) {
1180 # Merge core data into extension data
1181 foreach ( $coreData as $key => $item ) {
1182 $this->mergeItem( $key, $csData[$key], $item );
1185 # Load the secondary localisation from the source file to
1186 # avoid infinite cycles on cyclic fallbacks
1187 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1188 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1189 # Only merge the keys that make sense to merge
1190 foreach ( self::ALL_KEYS as $key ) {
1191 if ( !isset( $fbData[ $key ] ) ) {
1195 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1196 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1201 # Allow extensions an opportunity to adjust the data for this fallback
1202 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1204 # Merge the data for this fallback into the final array
1205 if ( $csCode === $code ) {
1208 foreach ( self::ALL_KEYS as $key ) {
1209 if ( !isset( $csData[$key] ) ) {
1214 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1215 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1221 if ( !isset( $allData[
'rtl'] ) ) {
1222 throw new RuntimeException( __METHOD__ .
': Localisation data failed validation check! ' .
1223 'Check that your languages/messages/MessagesEn.php file is intact.' );
1228 $deps[
'wgExtensionMessagesFiles'] =
1230 $deps[
'wgMessagesDirs'] =
1234 # Add dependencies to the cache entry
1235 $allData[
'deps'] = $deps;
1237 # Replace spaces with underscores in namespace names
1238 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1240 # And do the same for special page aliases. $page is an array.
1241 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1242 $page = str_replace(
' ',
'_', $page );
1244 # Decouple the reference to prevent accidental damage
1247 # If there were no plural rules, return an empty array
1248 $allData[
'pluralRules'] ??= [];
1249 $allData[
'compiledPluralRules'] ??= [];
1250 # If there were no plural rule types, return an empty array
1251 $allData[
'pluralRuleTypes'] ??= [];
1254 $allData[
'list'] = [];
1255 foreach ( self::SPLIT_KEYS as $key ) {
1256 $allData[
'list'][$key] = array_keys( $allData[$key] );
1260 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1262 # Save to the process cache and register the items loaded
1263 $this->data[$code] = $allData;
1264 $this->loadedItems[$code] = [];
1265 $this->loadedSubitems[$code] = [];
1266 foreach ( $allData as $key => $item ) {
1267 $this->loadedItems[$code][$key] =
true;
1270 # Prefix each item with its source language code before save
1271 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1273 foreach ( $allData[$key] as $subKey => $value ) {
1275 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1276 self::SOURCEPREFIX_SEPARATOR . $value;
1280 # Set the preload key
1281 $allData[
'preload'] = $this->buildPreload( $allData );
1283 # Save to the persistent cache
1284 $this->store->startWrite( $code );
1285 foreach ( $allData as $key => $value ) {
1286 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1287 foreach ( $value as $subkey => $subvalue ) {
1288 $this->store->set(
"$key:$subkey", $subvalue );
1291 $this->store->set( $key, $value );
1294 $this->store->finishWrite();
1296 # Clear out the MessageBlobStore
1297 # HACK: If using a null (i.e., disabled) storage backend, we
1298 # can't write to the MessageBlobStore either
1300 foreach ( $this->clearStoreCallbacks as $callback ) {
1315 private function buildPreload( $data ) {
1316 $preload = [
'messages' => [] ];
1317 foreach ( self::PRELOADED_KEYS as $key ) {
1318 $preload[$key] = $data[$key];
1321 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1322 $subitem = $data[
'messages'][$subkey] ??
null;
1323 $preload[
'messages'][$subkey] = $subitem;
1337 unset( $this->data[$code] );
1338 unset( $this->loadedItems[$code] );
1339 unset( $this->loadedSubitems[$code] );
1340 unset( $this->initialisedLangs[$code] );
1341 unset( $this->shallowFallbacks[$code] );
1342 unset( $this->sourceLanguage[$code] );
1343 unset( $this->coreDataLoaded[$code] );
1345 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1346 if ( $fbCode === $code ) {
1347 $this->unload( $shallowCode );
1356 foreach ( $this->initialisedLangs as $lang => $unused ) {
1357 $this->unload( $lang );
1366 $this->manualRecache =
false;