57 private $manualRecache;
83 private $clearStoreCallbacks;
85 private $langNameUtils;
96 private $loadedItems = [];
104 private $loadedSubitems = [];
113 private $initialisedLangs = [];
122 private $shallowFallbacks = [];
128 private $fallbackCodes = [];
135 private $recachedLangs = [];
147 private $coreDataLoaded = [];
153 'fallback',
'namespaceNames',
'bookstoreList',
154 'magicWords',
'messages',
'rtl',
155 'digitTransformTable',
'separatorTransformTable',
156 'minimumGroupingDigits',
'numberingSystem',
'fallback8bitEncoding',
157 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
158 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'datePreferences',
159 'datePreferenceMigrationMap',
'defaultDateFormat',
160 'specialPageAliases',
'imageFiles',
'preloadedMessages',
161 'namespaceGenderAliases',
'digitGroupingPattern',
'pluralRules',
162 'pluralRuleTypes',
'compiledPluralRules',
'formalityIndex'
172 private const CORE_ONLY_KEYS = [
173 'fallback',
'rtl',
'digitTransformTable',
'separatorTransformTable',
174 'minimumGroupingDigits',
'numberingSystem',
175 'fallback8bitEncoding',
'linkPrefixExtension',
176 'linkTrail',
'linkPrefixCharset',
'datePreferences',
177 'datePreferenceMigrationMap',
'defaultDateFormat',
'digitGroupingPattern',
189 private const ALL_EXCEPT_CORE_ONLY_KEYS = [
190 'namespaceNames',
'bookstoreList',
'magicWords',
'messages',
191 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'specialPageAliases',
192 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
193 'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
203 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
204 'namespaceAliases',
'dateFormats',
'jsDateFormats',
'imageFiles',
'preloadedMessages'
211 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
218 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
223 private const MAGIC_WORD_KEYS = [
'magicWords' ];
228 private const SPLIT_KEYS = [
'messages' ];
234 private const SOURCE_PREFIX_KEYS = [
'messages' ];
239 private const SOURCEPREFIX_SEPARATOR =
':';
244 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
249 private const META_KEYS = [
'deps',
'list',
'preload' ];
251 private const PLURAL_FILES = [
253 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
255 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
264 private static $pluralRules =
null;
280 private static $pluralRuleTypes =
null;
292 'directory' => $conf[
'storeDirectory'] ?: $fallbackCacheDir,
296 $storeClass = match ( $conf[
'storeClass'] ) {
297 'LCStoreCDB' => LCStoreCDB::class,
298 'LCStoreDB' => LCStoreDB::class,
299 'LCStoreNull' => LCStoreNull::class,
300 'LCStoreStaticArray' => LCStoreStaticArray::class,
301 default => $conf[
'storeClass'],
303 if ( !$storeClass ) {
305 $conf[
'store'] ===
'files'
306 || $conf[
'store'] ===
'file'
307 || ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
309 $storeClass = LCStoreCDB::class;
310 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
311 $storeClass = LCStoreDB::class;
312 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
313 } elseif ( $conf[
'store'] ===
'array' ) {
314 $storeClass = LCStoreStaticArray::class;
316 throw new ConfigException(
317 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
322 return new $storeClass( $storeArg );
328 public const CONSTRUCTOR_OPTIONS = [
353 LoggerInterface $logger,
354 array $clearStoreCallbacks,
360 $this->options = $options;
361 $this->store = $store;
362 $this->logger = $logger;
363 $this->clearStoreCallbacks = $clearStoreCallbacks;
364 $this->langNameUtils = $langNameUtils;
365 $this->hookRunner =
new HookRunner( $hookContainer );
368 $this->manualRecache = $options->
get(
'manualRecache' );
377 private static function isMergeableKey(
string $key ): bool {
378 static $mergeableKeys;
379 $mergeableKeys ??= array_fill_keys( [
380 ...self::MERGEABLE_MAP_KEYS,
381 ...self::MERGEABLE_ALIAS_LIST_KEYS,
382 ...self::OPTIONAL_MERGE_KEYS,
383 ...self::MAGIC_WORD_KEYS,
385 return isset( $mergeableKeys[$key] );
398 if ( !isset( $this->loadedItems[$code][$key] ) ) {
399 $this->loadItem( $code, $key );
402 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
403 return $this->shallowFallbacks[$code];
407 return $this->data[$code][$key];
418 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
419 !isset( $this->loadedItems[$code][$key] )
421 $this->loadSubitem( $code, $key, $subkey );
424 return $this->data[$code][$key][$subkey] ??
null;
437 $subitem = $this->getSubitem( $code, $key, $subkey );
439 if ( $subitem ===
null ) {
444 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
461 if ( in_array( $key, self::SPLIT_KEYS ) ) {
462 return $this->getSubitem( $code,
'list', $key );
464 $item = $this->getItem( $code, $key );
465 if ( is_array( $item ) ) {
466 return array_keys( $item );
479 private function loadItem( $code, $key ) {
480 if ( isset( $this->loadedItems[$code][$key] ) ) {
485 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
487 $key ===
'fallbackSequence' ||
488 $key ===
'originalFallbackSequence'
490 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
491 $this->loadCoreData( $code );
496 if ( !isset( $this->initialisedLangs[$code] ) ) {
497 $this->initLanguage( $code );
500 if ( isset( $this->loadedItems[$code][$key] ) ) {
505 if ( isset( $this->shallowFallbacks[$code] ) ) {
506 $this->loadItem( $this->shallowFallbacks[$code], $key );
511 if ( in_array( $key, self::SPLIT_KEYS ) ) {
512 $subkeyList = $this->getSubitem( $code,
'list', $key );
513 foreach ( $subkeyList as $subkey ) {
514 if ( isset( $this->data[$code][$key][$subkey] ) ) {
517 $this->loadSubitem( $code, $key, $subkey );
520 $this->data[$code][$key] = $this->getFromStore( $code, $key );
523 $this->loadedItems[$code][$key] =
true;
532 private function getFromStore(
string $code,
string $key ) {
533 if ( $this->store->lateFallback() ) {
535 foreach ( $this->getFallbackCodes( $code ) as $langCode ) {
536 $value = $this->store->get( $langCode, $key );
537 $this->mergeItem( $key, $result, $value );
538 if ( in_array( $key, self::META_KEYS ) ) {
541 if ( is_string( $result ) ) {
548 return $this->store->get( $code, $key );
558 if ( !array_key_exists( $code, $this->fallbackCodes ) ) {
559 $this->fallbackCodes[$code] = [
561 ...MediaWikiServices::getInstance()->getLanguageFallback()->getAll( $code )
564 return $this->fallbackCodes[$code];
574 private function loadSubitem( $code, $key, $subkey ) {
575 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
576 $this->loadItem( $code, $key );
581 if ( !isset( $this->initialisedLangs[$code] ) ) {
582 $this->initLanguage( $code );
586 if ( isset( $this->loadedItems[$code][$key] ) ||
587 isset( $this->loadedSubitems[$code][$key][$subkey] )
592 if ( isset( $this->shallowFallbacks[$code] ) ) {
593 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
598 $value = $this->getFromStore( $code,
"$key:$subkey" );
599 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
601 $this->sourceLanguage[$code][$key][$subkey],
602 $this->data[$code][$key][$subkey]
603 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
605 $this->data[$code][$key][$subkey] = $value;
608 $this->loadedSubitems[$code][$key][$subkey] =
true;
619 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
620 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
625 $deps = $this->getFromStore( $code,
'deps' );
626 $keys = $this->getFromStore( $code,
'list' );
627 $preload = $this->getFromStore( $code,
'preload' );
629 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
630 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
635 foreach ( $deps as $dep ) {
641 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
656 private function initLanguage( $langCode ) {
657 foreach ( array_reverse( $this->getFallbackCodes( $langCode ) ) as $code ) {
658 if ( isset( $this->initialisedLangs[$code] ) ) {
662 $this->initialisedLangs[$code] =
true;
664 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
665 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
666 $this->initShallowFallback( $code,
'en' );
671 # Re-cache the data if necessary
672 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
673 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
674 $this->recache( $code );
675 } elseif ( $code ===
'en' ) {
676 throw new RuntimeException(
'MessagesEn.php is missing.' );
678 $this->initShallowFallback( $code,
'en' );
685 $preload = $this->getItem( $code,
'preload' );
686 if ( $preload ===
null ) {
687 if ( $this->manualRecache ) {
689 if ( $code ===
'en' ) {
690 throw new RuntimeException(
'No localisation cache found for English. ' .
691 'Please run maintenance/rebuildLocalisationCache.php.' );
693 $this->initShallowFallback( $code,
'en' );
697 throw new RuntimeException(
'Invalid or missing localisation cache.' );
701 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
702 if ( !isset( $preload[$key] ) ) {
705 foreach ( $preload[$key] as $subkey => $value ) {
706 if ( $value !==
null ) {
708 $this->sourceLanguage[$code][$key][$subkey],
709 $preload[$key][$subkey]
710 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
712 $preload[$key][$subkey] =
null;
717 if ( isset( $this->data[$code] ) ) {
718 foreach ( $preload as $key => $value ) {
720 $this->mergeItem( $key, $this->data[$code][$key], $value );
723 $this->data[$code] = $preload;
725 foreach ( $preload as $key => $item ) {
726 if ( in_array( $key, self::SPLIT_KEYS ) ) {
727 foreach ( $item as $subkey => $subitem ) {
728 $this->loadedSubitems[$code][$key][$subkey] =
true;
731 $this->loadedItems[$code][$key] =
true;
744 private function initShallowFallback( $primaryCode, $fallbackCode ) {
745 $this->data[$primaryCode] =& $this->data[$fallbackCode];
746 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
747 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
748 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
749 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
763 if ( $_fileType ==
'core' ) {
764 foreach ( self::ALL_KEYS as $key ) {
768 if ( isset( $$key ) ) {
772 } elseif ( $_fileType ==
'extension' ) {
773 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
775 if ( isset( $$key ) ) {
779 } elseif ( $_fileType ==
'aliases' ) {
781 if ( isset( $aliases ) ) {
782 $data[
'aliases'] = $aliases;
785 throw new InvalidArgumentException( __METHOD__ .
": Invalid file type: $_fileType" );
797 private function readJSONFile( $fileName ) {
798 if ( !is_readable( $fileName ) ) {
802 $json = file_get_contents( $fileName );
803 if ( $json ===
false ) {
807 $data = FormatJson::decode( $json,
true );
808 if ( $data ===
null ) {
809 throw new RuntimeException( __METHOD__ .
": Invalid JSON file: $fileName" );
813 foreach ( $data as $key => $unused ) {
814 if ( $key ===
'' || $key[0] ===
'@' ) {
815 unset( $data[$key] );
829 private function getCompiledPluralRules( $code ) {
830 $rules = $this->getPluralRules( $code );
831 if ( $rules ===
null ) {
835 $compiledRules = Evaluator::compile( $rules );
836 }
catch ( CLDRPluralRuleError $e ) {
837 $this->logger->debug( $e->getMessage() );
842 return $compiledRules;
854 private function getPluralRules( $code ) {
855 if ( self::$pluralRules ===
null ) {
856 self::loadPluralFiles();
858 return self::$pluralRules[$code] ??
null;
870 private function getPluralRuleTypes( $code ) {
871 if ( self::$pluralRuleTypes ===
null ) {
872 self::loadPluralFiles();
874 return self::$pluralRuleTypes[$code] ??
null;
880 private static function loadPluralFiles() {
881 foreach ( self::PLURAL_FILES as $fileName ) {
882 self::loadPluralFile( $fileName );
892 private static function loadPluralFile( $fileName ) {
894 $xml = file_get_contents( $fileName );
896 throw new RuntimeException(
"Unable to read plurals file $fileName" );
898 $doc =
new DOMDocument;
899 $doc->loadXML( $xml );
900 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
901 foreach ( $rulesets as $ruleset ) {
902 $codes = $ruleset->getAttribute(
'locales' );
905 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
906 foreach ( $ruleElements as $elt ) {
907 $ruleType = $elt->getAttribute(
'count' );
908 if ( $ruleType ===
'other' ) {
912 $rules[] = $elt->nodeValue;
913 $ruleTypes[] = $ruleType;
915 foreach ( explode(
' ', $codes ) as $code ) {
916 self::$pluralRules[$code] = $rules;
917 self::$pluralRuleTypes[$code] = $ruleTypes;
930 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
932 $fileName = $this->langNameUtils->getMessagesFileName( $code );
933 if ( !is_file( $fileName ) ) {
937 $data = $this->readPHPFile( $fileName,
'core' );
951 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
954 'pluralRules' => $this->getPluralRules( $code ),
956 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
958 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
961 foreach ( self::PLURAL_FILES as $fileName ) {
976 private function mergeItem( $key, &$value, $fallbackValue ) {
977 if ( $value !==
null ) {
978 if ( $fallbackValue !==
null ) {
979 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
980 $value += $fallbackValue;
981 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
982 $value = array_merge_recursive( $value, $fallbackValue );
983 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
984 if ( !empty( $value[
'inherit'] ) ) {
985 $value = array_merge( $fallbackValue, $value );
988 unset( $value[
'inherit'] );
989 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
990 $this->mergeMagicWords( $value, $fallbackValue );
994 $value = $fallbackValue;
998 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
999 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
1000 if ( !isset( $value[$magicName] ) ) {
1001 $value[$magicName] = $fallbackInfo;
1003 $value[$magicName] = [
1007 ...array_slice( $value[$magicName], 1 ),
1008 ...array_slice( $fallbackInfo, 1 ),
1026 'core' =>
"$IP/languages/i18n",
1027 'botpasswords' =>
"$IP/languages/i18n/botpasswords",
1028 'codex' =>
"$IP/languages/i18n/codex",
1029 'datetime' =>
"$IP/languages/i18n/datetime",
1030 'exif' =>
"$IP/languages/i18n/exif",
1031 'languageconverter' =>
"$IP/languages/i18n/languageconverter",
1032 'interwiki' =>
"$IP/languages/i18n/interwiki",
1033 'preferences' =>
"$IP/languages/i18n/preferences",
1034 'userrights' =>
"$IP/languages/i18n/userrights",
1036 'nontranslatable' =>
"$IP/languages/i18n/nontranslatable",
1038 'api' =>
"$IP/includes/Api/i18n",
1039 'rest' =>
"$IP/includes/Rest/i18n",
1040 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
1041 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
1042 'installer' =>
"$IP/includes/Installer/i18n",
1043 ] + $this->options->get( MainConfigNames::MessagesDirs );
1056 private function loadCoreData(
string $code ) {
1058 throw new InvalidArgumentException(
"Invalid language code requested" );
1060 if ( $this->coreDataLoaded[$code] ??
false ) {
1064 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
1067 # Load the primary localisation from the source file
1068 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
1069 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
1071 # Merge primary localisation
1072 foreach ( $data as $key => $value ) {
1073 $this->mergeItem( $key, $coreData[ $key ], $value );
1076 # Fill in the fallback if it's not there already
1078 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1079 $coreData[
'fallback'] =
false;
1080 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1082 if ( $coreData[
'fallback'] !==
null ) {
1083 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1085 $coreData[
'fallbackSequence'] = [];
1087 $len = count( $coreData[
'fallbackSequence'] );
1089 # Before we add the 'en' fallback for messages, keep a copy of
1090 # the original fallback sequence
1091 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1093 # Ensure that the sequence ends at 'en' for messages
1094 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1095 $coreData[
'fallbackSequence'][] =
'en';
1099 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1101 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1102 foreach ( self::CORE_ONLY_KEYS as $key ) {
1104 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1105 $coreData[$key] = $fbData[$key];
1110 $coreData[
'deps'] = $deps;
1111 foreach ( $coreData as $key => $item ) {
1112 $this->data[$code][$key] ??=
null;
1114 $this->mergeItem( $key, $this->data[$code][$key], $item );
1116 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1118 $key ===
'fallbackSequence' ||
1119 $key ===
'originalFallbackSequence'
1125 $this->loadedItems[$code][$key] =
true;
1129 $this->coreDataLoaded[$code] =
true;
1140 throw new InvalidArgumentException(
"Invalid language code requested" );
1142 $this->recachedLangs[ $code ] =
true;
1145 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1146 $this->data[$code] = [];
1147 $this->loadedItems[$code] = [];
1148 $this->loadedSubitems[$code] = [];
1149 $this->coreDataLoaded[$code] =
false;
1150 $this->loadCoreData( $code );
1151 $coreData = $this->data[$code];
1152 $deps = $coreData[
'deps'];
1153 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1155 if ( $this->store->lateFallback() ) {
1158 $codeSequence = [ $code ];
1162 $codeSequence = [ $code, ...$coreData[
'fallbackSequence'] ];
1164 $messageDirs = $this->getMessagesDirs();
1165 $translationAliasesDirs = $this->options->get( MainConfigNames::TranslationAliasesDirs );
1167 # Load non-JSON localisation data for extensions
1168 $extensionData = array_fill_keys( $codeSequence, $initialData );
1169 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1170 if ( isset( $messageDirs[$extension] ) || isset( $translationAliasesDirs[$extension] ) ) {
1171 # This extension has JSON message data; skip the PHP shim
1175 $data = $this->readPHPFile( $fileName,
'extension' );
1178 foreach ( $data as $key => $item ) {
1179 foreach ( $codeSequence as $csCode ) {
1180 if ( isset( $item[$csCode] ) ) {
1183 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1184 foreach ( $item[$csCode] as $subkey => $_ ) {
1185 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1188 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1199 # Load the localisation data for each fallback, then merge it into the full array
1200 $allData = $initialData;
1201 foreach ( $codeSequence as $csCode ) {
1202 $csData = $initialData;
1204 # Load core messages and the extension localisations.
1205 foreach ( $messageDirs as $dirs ) {
1206 foreach ( (array)$dirs as $dir ) {
1207 $fileName =
"$dir/$csCode.json";
1208 $messages = $this->readJSONFile( $fileName );
1210 foreach ( $messages as $subkey => $_ ) {
1211 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1213 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1219 foreach ( $translationAliasesDirs as $dirs ) {
1220 foreach ( (array)$dirs as $dir ) {
1221 $fileName =
"$dir/$csCode.json";
1222 $data = $this->readJSONFile( $fileName );
1224 foreach ( $data as $key => $item ) {
1227 $normalizedKey = lcfirst( $key );
1229 if ( $normalizedKey ===
'@metadata' ) {
1234 if ( !in_array( $normalizedKey, self::ALL_ALIAS_KEYS ) ) {
1235 throw new UnexpectedValueException(
1236 "Invalid key: \"$key\" for " . MainConfigNames::TranslationAliasesDirs .
". " .
1237 'Valid keys: ' . implode(
', ', self::ALL_ALIAS_KEYS )
1241 $this->mergeItem( $normalizedKey, $extensionData[$csCode][$normalizedKey], $item );
1248 # Merge non-JSON extension data
1249 if ( isset( $extensionData[$csCode] ) ) {
1250 foreach ( $extensionData[$csCode] as $key => $item ) {
1251 $this->mergeItem( $key, $csData[$key], $item );
1255 if ( $csCode === $code ) {
1256 # Merge core data into extension data
1257 foreach ( $coreData as $key => $item ) {
1258 $this->mergeItem( $key, $csData[$key], $item );
1261 # Load the secondary localisation from the source file to
1262 # avoid infinite cycles on cyclic fallbacks
1263 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1264 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1265 # Only merge the keys that make sense to merge
1266 foreach ( self::ALL_KEYS as $key ) {
1267 if ( !isset( $fbData[ $key ] ) ) {
1271 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1272 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1277 # Allow extensions an opportunity to adjust the data for this fallback
1278 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1280 # Merge the data for this fallback into the final array
1281 if ( $csCode === $code ) {
1284 foreach ( self::ALL_KEYS as $key ) {
1285 if ( !isset( $csData[$key] ) ) {
1290 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1291 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1297 if ( !isset( $allData[
'rtl'] ) ) {
1298 throw new RuntimeException( __METHOD__ .
': Localisation data failed validation check! ' .
1299 'Check that your languages/messages/MessagesEn.php file is intact.' );
1304 $deps[
'wgExtensionMessagesFiles'] =
1306 $deps[
'wgMessagesDirs'] =
1310 # Add dependencies to the cache entry
1311 $allData[
'deps'] = $deps;
1313 # Replace spaces with underscores in namespace names
1314 if ( isset( $allData[
'namespaceNames'] ) ) {
1315 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1318 # And do the same for special page aliases. $page is an array.
1319 if ( isset( $allData[
'specialPageAliases'] ) ) {
1320 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1321 $page = str_replace(
' ',
'_', $page );
1324 # Decouple the reference to prevent accidental damage
1327 # If there were no plural rules, return an empty array
1328 $allData[
'pluralRules'] ??= [];
1329 $allData[
'compiledPluralRules'] ??= [];
1330 # If there were no plural rule types, return an empty array
1331 $allData[
'pluralRuleTypes'] ??= [];
1334 $allData[
'list'] = [];
1335 foreach ( self::SPLIT_KEYS as $key ) {
1336 $allData[
'list'][$key] = array_keys( $allData[$key] );
1340 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1342 if ( $this->store->lateFallback() ) {
1347 $this->data[$code] = $allData;
1348 $this->loadedItems[$code] = [];
1349 $this->loadedSubitems[$code] = [];
1350 foreach ( $allData as $key => $item ) {
1351 $this->loadedItems[$code][$key] =
true;
1355 # Prefix each item with its source language code before save
1356 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1358 foreach ( $allData[$key] as $subKey => $value ) {
1360 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1361 self::SOURCEPREFIX_SEPARATOR . $value;
1365 # Set the preload key
1366 $allData[
'preload'] = $this->buildPreload( $allData );
1368 # Save to the persistent cache
1369 $this->store->startWrite( $code );
1370 foreach ( $allData as $key => $value ) {
1371 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1372 foreach ( $value as $subkey => $subvalue ) {
1373 $this->store->set(
"$key:$subkey", $subvalue );
1376 $this->store->set( $key, $value );
1379 $this->store->finishWrite();
1381 # Clear out the MessageBlobStore
1382 # HACK: If using a null (i.e., disabled) storage backend, we
1383 # can't write to the MessageBlobStore either
1385 foreach ( $this->clearStoreCallbacks as $callback ) {
1400 private function buildPreload( $data ) {
1401 $preload = [
'messages' => [] ];
1402 foreach ( self::PRELOADED_KEYS as $key ) {
1403 if ( isset( $data[$key] ) ) {
1404 $preload[$key] = $data[$key];
1408 foreach ( $data[
'preloadedMessages'] ?? [] as $subkey ) {
1409 if ( isset( $data[
'messages'][$subkey] ) ) {
1410 $preload[
'messages'][$subkey] = $data[
'messages'][$subkey];
1425 unset( $this->data[$code] );
1426 unset( $this->loadedItems[$code] );
1427 unset( $this->loadedSubitems[$code] );
1428 unset( $this->initialisedLangs[$code] );
1429 unset( $this->shallowFallbacks[$code] );
1430 unset( $this->sourceLanguage[$code] );
1431 unset( $this->coreDataLoaded[$code] );
1433 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1434 if ( $fbCode === $code ) {
1435 $this->unload( $shallowCode );
1444 foreach ( $this->initialisedLangs as $lang => $unused ) {
1445 $this->unload( $lang );
1454 $this->manualRecache =
false;