21 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
22 use CLDRPluralRuleParser\Evaluator;
28 use Psr\Log\LoggerInterface;
59 private $manualRecache;
85 private $clearStoreCallbacks;
87 private $langNameUtils;
98 private $loadedItems = [];
106 private $loadedSubitems = [];
115 private $initialisedLangs = [];
124 private $shallowFallbacks = [];
131 private $recachedLangs = [];
143 private $coreDataLoaded = [];
149 'fallback',
'namespaceNames',
'bookstoreList',
150 'magicWords',
'messages',
'rtl',
151 'digitTransformTable',
'separatorTransformTable',
152 'minimumGroupingDigits',
'fallback8bitEncoding',
153 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
154 'namespaceAliases',
'dateFormats',
'datePreferences',
155 'datePreferenceMigrationMap',
'defaultDateFormat',
156 'specialPageAliases',
'imageFiles',
'preloadedMessages',
157 'namespaceGenderAliases',
'digitGroupingPattern',
'pluralRules',
158 'pluralRuleTypes',
'compiledPluralRules',
168 private const CORE_ONLY_KEYS = [
169 'fallback',
'rtl',
'digitTransformTable',
'separatorTransformTable',
170 'minimumGroupingDigits',
'fallback8bitEncoding',
'linkPrefixExtension',
171 'linkTrail',
'linkPrefixCharset',
'datePreferences',
172 'datePreferenceMigrationMap',
'defaultDateFormat',
'digitGroupingPattern',
183 private const ALL_EXCEPT_CORE_ONLY_KEYS = [
184 'namespaceNames',
'bookstoreList',
'magicWords',
'messages',
185 'namespaceAliases',
'dateFormats',
'specialPageAliases',
186 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
187 'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
194 private const MERGEABLE_MAP_KEYS = [
'messages',
'namespaceNames',
195 'namespaceAliases',
'dateFormats',
'imageFiles',
'preloadedMessages'
202 private const MERGEABLE_ALIAS_LIST_KEYS = [
'specialPageAliases' ];
209 private const OPTIONAL_MERGE_KEYS = [
'bookstoreList' ];
214 private const MAGIC_WORD_KEYS = [
'magicWords' ];
219 private const SPLIT_KEYS = [
'messages' ];
225 private const SOURCE_PREFIX_KEYS = [
'messages' ];
230 private const SOURCEPREFIX_SEPARATOR =
':';
235 private const PRELOADED_KEYS = [
'dateFormats',
'namespaceNames' ];
237 private const PLURAL_FILES = [
239 MW_INSTALL_PATH .
'/languages/data/plurals.xml',
241 MW_INSTALL_PATH .
'/languages/data/plurals-mediawiki.xml',
250 private static $pluralRules =
null;
266 private static $pluralRuleTypes =
null;
278 $storeArg[
'directory'] =
279 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
281 if ( !empty( $conf[
'storeClass'] ) ) {
282 $storeClass = $conf[
'storeClass'];
283 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
284 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
286 $storeClass = LCStoreCDB::class;
287 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
288 $storeClass = LCStoreDB::class;
289 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
290 } elseif ( $conf[
'store'] ===
'array' ) {
291 $storeClass = LCStoreStaticArray::class;
294 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
298 return new $storeClass( $storeArg );
304 public const CONSTRUCTOR_OPTIONS = [
308 MainConfigNames::ExtensionMessagesFiles,
309 MainConfigNames::MessagesDirs,
328 LoggerInterface $logger,
329 array $clearStoreCallbacks,
335 $this->options = $options;
336 $this->store = $store;
337 $this->logger = $logger;
338 $this->clearStoreCallbacks = $clearStoreCallbacks;
339 $this->langNameUtils = $langNameUtils;
340 $this->hookRunner =
new HookRunner( $hookContainer );
343 $this->manualRecache = $options->
get(
'manualRecache' );
352 private static function isMergeableKey(
string $key ): bool {
353 static $mergeableKeys;
354 $mergeableKeys ??= array_fill_keys( [
355 ...self::MERGEABLE_MAP_KEYS,
356 ...self::MERGEABLE_ALIAS_LIST_KEYS,
357 ...self::OPTIONAL_MERGE_KEYS,
358 ...self::MAGIC_WORD_KEYS,
360 return isset( $mergeableKeys[$key] );
373 if ( !isset( $this->loadedItems[$code][$key] ) ) {
374 $this->loadItem( $code, $key );
377 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
378 return $this->shallowFallbacks[$code];
382 return $this->data[$code][$key];
393 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
394 !isset( $this->loadedItems[$code][$key] )
396 $this->loadSubitem( $code, $key, $subkey );
399 return $this->data[$code][$key][$subkey] ??
null;
412 $subitem = $this->getSubitem( $code, $key, $subkey );
414 if ( $subitem ===
null ) {
419 return [ $subitem, $this->sourceLanguage[$code][$key][$subkey] ?? $code ];
436 if ( in_array( $key, self::SPLIT_KEYS ) ) {
437 return $this->getSubitem( $code,
'list', $key );
439 $item = $this->getItem( $code, $key );
440 if ( is_array( $item ) ) {
441 return array_keys( $item );
454 private function loadItem( $code, $key ) {
455 if ( isset( $this->loadedItems[$code][$key] ) ) {
460 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
462 $key ===
'fallbackSequence' ||
463 $key ===
'originalFallbackSequence'
465 if ( $this->langNameUtils->isValidBuiltInCode( $code ) ) {
466 $this->loadCoreData( $code );
471 if ( !isset( $this->initialisedLangs[$code] ) ) {
472 $this->initLanguage( $code );
475 if ( isset( $this->loadedItems[$code][$key] ) ) {
480 if ( isset( $this->shallowFallbacks[$code] ) ) {
481 $this->loadItem( $this->shallowFallbacks[$code], $key );
486 if ( in_array( $key, self::SPLIT_KEYS ) ) {
487 $subkeyList = $this->getSubitem( $code,
'list', $key );
488 foreach ( $subkeyList as $subkey ) {
489 if ( isset( $this->data[$code][$key][$subkey] ) ) {
492 $this->loadSubitem( $code, $key, $subkey );
495 $this->data[$code][$key] = $this->store->get( $code, $key );
498 $this->loadedItems[$code][$key] =
true;
508 private function loadSubitem( $code, $key, $subkey ) {
509 if ( !in_array( $key, self::SPLIT_KEYS ) ) {
510 $this->loadItem( $code, $key );
515 if ( !isset( $this->initialisedLangs[$code] ) ) {
516 $this->initLanguage( $code );
520 if ( isset( $this->loadedItems[$code][$key] ) ||
521 isset( $this->loadedSubitems[$code][$key][$subkey] )
526 if ( isset( $this->shallowFallbacks[$code] ) ) {
527 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
532 $value = $this->store->get( $code,
"$key:$subkey" );
533 if ( $value !==
null && in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
535 $this->sourceLanguage[$code][$key][$subkey],
536 $this->data[$code][$key][$subkey]
537 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
539 $this->data[$code][$key][$subkey] = $value;
542 $this->loadedSubitems[$code][$key][$subkey] =
true;
553 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
554 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
559 $deps = $this->store->get( $code,
'deps' );
560 $keys = $this->store->get( $code,
'list' );
561 $preload = $this->store->get( $code,
'preload' );
563 if ( $deps ===
null || $keys ===
null || $preload ===
null ) {
564 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
569 foreach ( $deps as $dep ) {
575 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
591 private function initLanguage( $code ) {
592 if ( isset( $this->initialisedLangs[$code] ) ) {
596 $this->initialisedLangs[$code] =
true;
598 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
599 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
600 $this->initShallowFallback( $code,
'en' );
605 # Re-cache the data if necessary
606 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
607 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
608 $this->recache( $code );
609 } elseif ( $code ===
'en' ) {
610 throw new MWException(
'MessagesEn.php is missing.' );
612 $this->initShallowFallback( $code,
'en' );
619 $preload = $this->getItem( $code,
'preload' );
620 if ( $preload ===
null ) {
621 if ( $this->manualRecache ) {
623 if ( $code ===
'en' ) {
624 throw new MWException(
'No localisation cache found for English. ' .
625 'Please run maintenance/rebuildLocalisationCache.php.' );
627 $this->initShallowFallback( $code,
'en' );
631 throw new MWException(
'Invalid or missing localisation cache.' );
635 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
636 if ( !isset( $preload[$key] ) ) {
639 foreach ( $preload[$key] as $subkey => $value ) {
641 $this->sourceLanguage[$code][$key][$subkey],
642 $preload[$key][$subkey]
643 ] = explode( self::SOURCEPREFIX_SEPARATOR, $value, 2 );
647 if ( isset( $this->data[$code] ) ) {
648 foreach ( $preload as $key => $value ) {
650 $this->mergeItem( $key, $this->data[$code][$key], $value );
653 $this->data[$code] = $preload;
655 foreach ( $preload as $key => $item ) {
656 if ( in_array( $key, self::SPLIT_KEYS ) ) {
657 foreach ( $item as $subkey => $subitem ) {
658 $this->loadedSubitems[$code][$key][$subkey] =
true;
661 $this->loadedItems[$code][$key] =
true;
673 private function initShallowFallback( $primaryCode, $fallbackCode ) {
674 $this->data[$primaryCode] =& $this->data[$fallbackCode];
675 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
676 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
677 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
678 $this->coreDataLoaded[$primaryCode] =& $this->coreDataLoaded[$fallbackCode];
693 if ( $_fileType ==
'core' ) {
694 foreach ( self::ALL_KEYS as $key ) {
697 if ( isset( $$key ) ) {
701 } elseif ( $_fileType ==
'extension' ) {
702 foreach ( self::ALL_EXCEPT_CORE_ONLY_KEYS as $key ) {
703 if ( isset( $$key ) ) {
707 } elseif ( $_fileType ==
'aliases' ) {
709 if ( isset( $aliases ) ) {
710 $data[
'aliases'] = $aliases;
713 throw new MWException( __METHOD__ .
": Invalid file type: $_fileType" );
726 private function readJSONFile( $fileName ) {
727 if ( !is_readable( $fileName ) ) {
731 $json = file_get_contents( $fileName );
732 if ( $json ===
false ) {
737 if ( $data ===
null ) {
738 throw new MWException( __METHOD__ .
": Invalid JSON file: $fileName" );
742 foreach ( $data as $key => $unused ) {
743 if ( $key ===
'' || $key[0] ===
'@' ) {
744 unset( $data[$key] );
758 private function getCompiledPluralRules( $code ) {
759 $rules = $this->getPluralRules( $code );
760 if ( $rules ===
null ) {
764 $compiledRules = Evaluator::compile( $rules );
765 }
catch ( CLDRPluralRuleError $e ) {
766 $this->logger->debug( $e->getMessage() );
771 return $compiledRules;
783 private function getPluralRules( $code ) {
784 if ( self::$pluralRules ===
null ) {
785 self::loadPluralFiles();
787 return self::$pluralRules[$code] ??
null;
799 private function getPluralRuleTypes( $code ) {
800 if ( self::$pluralRuleTypes ===
null ) {
801 self::loadPluralFiles();
803 return self::$pluralRuleTypes[$code] ??
null;
809 private static function loadPluralFiles() {
810 foreach ( self::PLURAL_FILES as $fileName ) {
811 self::loadPluralFile( $fileName );
822 private static function loadPluralFile( $fileName ) {
824 $xml = file_get_contents( $fileName );
826 throw new MWException(
"Unable to read plurals file $fileName" );
828 $doc =
new DOMDocument;
829 $doc->loadXML( $xml );
830 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
831 foreach ( $rulesets as $ruleset ) {
832 $codes = $ruleset->getAttribute(
'locales' );
835 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
836 foreach ( $ruleElements as $elt ) {
837 $ruleType = $elt->getAttribute(
'count' );
838 if ( $ruleType ===
'other' ) {
842 $rules[] = $elt->nodeValue;
843 $ruleTypes[] = $ruleType;
845 foreach ( explode(
' ', $codes ) as $code ) {
846 self::$pluralRules[$code] = $rules;
847 self::$pluralRuleTypes[$code] = $ruleTypes;
860 private function readSourceFilesAndRegisterDeps( $code, &$deps ) {
862 $fileName = $this->langNameUtils->getMessagesFileName( $code );
863 if ( !is_file( $fileName ) ) {
867 $data = $this->readPHPFile( $fileName,
'core' );
881 private function readPluralFilesAndRegisterDeps( $code, &$deps ) {
884 'pluralRules' => $this->getPluralRules( $code ),
886 'compiledPluralRules' => $this->getCompiledPluralRules( $code ),
888 'pluralRuleTypes' => $this->getPluralRuleTypes( $code ),
891 foreach ( self::PLURAL_FILES as $fileName ) {
906 private function mergeItem( $key, &$value, $fallbackValue ) {
907 if ( $value !==
null ) {
908 if ( $fallbackValue !==
null ) {
909 if ( in_array( $key, self::MERGEABLE_MAP_KEYS ) ) {
910 $value += $fallbackValue;
911 } elseif ( in_array( $key, self::MERGEABLE_ALIAS_LIST_KEYS ) ) {
912 $value = array_merge_recursive( $value, $fallbackValue );
913 } elseif ( in_array( $key, self::OPTIONAL_MERGE_KEYS ) ) {
914 if ( !empty( $value[
'inherit'] ) ) {
915 $value = array_merge( $fallbackValue, $value );
918 unset( $value[
'inherit'] );
919 } elseif ( in_array( $key, self::MAGIC_WORD_KEYS ) ) {
920 $this->mergeMagicWords( $value, $fallbackValue );
924 $value = $fallbackValue;
932 private function mergeMagicWords( array &$value, array $fallbackValue ): void {
933 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
934 if ( !isset( $value[$magicName] ) ) {
935 $value[$magicName] = $fallbackInfo;
937 $value[$magicName] = [
941 ...array_slice( $value[$magicName], 1 ),
942 ...array_slice( $fallbackInfo, 1 ),
960 'core' =>
"$IP/languages/i18n",
961 'exif' =>
"$IP/languages/i18n/exif",
962 'api' =>
"$IP/includes/api/i18n",
963 'rest' =>
"$IP/includes/Rest/i18n",
964 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
965 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
966 ] + $this->options->get( MainConfigNames::MessagesDirs );
979 private function loadCoreData(
string $code ) {
981 throw new MWException(
"Invalid language code requested" );
983 if ( $this->coreDataLoaded[$code] ??
false ) {
987 $coreData = array_fill_keys( self::CORE_ONLY_KEYS,
null );
990 # Load the primary localisation from the source file
991 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
992 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
994 # Merge primary localisation
995 foreach ( $data as $key => $value ) {
996 $this->mergeItem( $key, $coreData[ $key ], $value );
999 # Fill in the fallback if it's not there already
1001 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
1002 $coreData[
'fallback'] =
false;
1003 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
1005 if ( $coreData[
'fallback'] !==
null ) {
1006 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
1008 $coreData[
'fallbackSequence'] = [];
1010 $len = count( $coreData[
'fallbackSequence'] );
1012 # Before we add the 'en' fallback for messages, keep a copy of
1013 # the original fallback sequence
1014 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
1016 # Ensure that the sequence ends at 'en' for messages
1017 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
1018 $coreData[
'fallbackSequence'][] =
'en';
1022 foreach ( $coreData[
'fallbackSequence'] as $fbCode ) {
1024 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
1025 foreach ( self::CORE_ONLY_KEYS as $key ) {
1027 if ( isset( $fbData[$key] ) && !isset( $coreData[$key] ) ) {
1028 $coreData[$key] = $fbData[$key];
1033 $coreData[
'deps'] = $deps;
1034 foreach ( $coreData as $key => $item ) {
1035 $this->data[$code][$key] ??=
null;
1037 $this->mergeItem( $key, $this->data[$code][$key], $item );
1039 in_array( $key, self::CORE_ONLY_KEYS,
true ) ||
1041 $key ===
'fallbackSequence' ||
1042 $key ===
'originalFallbackSequence'
1048 $this->loadedItems[$code][$key] =
true;
1052 $this->coreDataLoaded[$code] =
true;
1064 throw new MWException(
"Invalid language code requested" );
1066 $this->recachedLangs[ $code ] =
true;
1069 $initialData = array_fill_keys( self::ALL_KEYS,
null );
1070 $this->data[$code] = [];
1071 $this->loadedItems[$code] = [];
1072 $this->loadedSubitems[$code] = [];
1073 $this->coreDataLoaded[$code] =
false;
1074 $this->loadCoreData( $code );
1075 $coreData = $this->data[$code];
1077 $deps = $coreData[
'deps'];
1078 $coreData += $this->readPluralFilesAndRegisterDeps( $code, $deps );
1080 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
1081 $messageDirs = $this->getMessagesDirs();
1083 # Load non-JSON localisation data for extensions
1084 $extensionData = array_fill_keys( $codeSequence, $initialData );
1085 foreach ( $this->options->get( MainConfigNames::ExtensionMessagesFiles ) as $extension => $fileName ) {
1086 if ( isset( $messageDirs[$extension] ) ) {
1087 # This extension has JSON message data; skip the PHP shim
1091 $data = $this->readPHPFile( $fileName,
'extension' );
1094 foreach ( $data as $key => $item ) {
1095 foreach ( $codeSequence as $csCode ) {
1096 if ( isset( $item[$csCode] ) ) {
1099 if ( in_array( $key, self::SOURCE_PREFIX_KEYS ) ) {
1100 foreach ( $item[$csCode] as $subkey => $_ ) {
1101 $this->sourceLanguage[$code][$key][$subkey] ??= $csCode;
1104 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
1115 # Load the localisation data for each fallback, then merge it into the full array
1116 $allData = $initialData;
1117 foreach ( $codeSequence as $csCode ) {
1118 $csData = $initialData;
1120 # Load core messages and the extension localisations.
1121 foreach ( $messageDirs as $dirs ) {
1122 foreach ( (array)$dirs as $dir ) {
1123 $fileName =
"$dir/$csCode.json";
1124 $messages = $this->readJSONFile( $fileName );
1126 foreach ( $messages as $subkey => $_ ) {
1127 $this->sourceLanguage[$code][
'messages'][$subkey] ??= $csCode;
1129 $this->mergeItem(
'messages', $csData[
'messages'], $messages );
1135 # Merge non-JSON extension data
1136 if ( isset( $extensionData[$csCode] ) ) {
1137 foreach ( $extensionData[$csCode] as $key => $item ) {
1138 $this->mergeItem( $key, $csData[$key], $item );
1142 if ( $csCode === $code ) {
1143 # Merge core data into extension data
1144 foreach ( $coreData as $key => $item ) {
1145 $this->mergeItem( $key, $csData[$key], $item );
1148 # Load the secondary localisation from the source file to
1149 # avoid infinite cycles on cyclic fallbacks
1150 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
1151 $fbData += $this->readPluralFilesAndRegisterDeps( $csCode, $deps );
1152 # Only merge the keys that make sense to merge
1153 foreach ( self::ALL_KEYS as $key ) {
1154 if ( !isset( $fbData[ $key ] ) ) {
1158 if ( !isset( $coreData[ $key ] ) || self::isMergeableKey( $key ) ) {
1159 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
1164 # Allow extensions an opportunity to adjust the data for this fallback
1165 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
1167 # Merge the data for this fallback into the final array
1168 if ( $csCode === $code ) {
1171 foreach ( self::ALL_KEYS as $key ) {
1172 if ( !isset( $csData[$key] ) ) {
1177 if ( $allData[$key] ===
null || self::isMergeableKey( $key ) ) {
1178 $this->mergeItem( $key, $allData[$key], $csData[$key] );
1184 if ( !isset( $allData[
'rtl'] ) ) {
1185 throw new MWException( __METHOD__ .
': Localisation data failed validation check! ' .
1186 'Check that your languages/messages/MessagesEn.php file is intact.' );
1189 # Add cache dependencies for any referenced globals
1190 $deps[
'wgExtensionMessagesFiles'] =
new GlobalDependency(
'wgExtensionMessagesFiles' );
1196 # Add dependencies to the cache entry
1197 $allData[
'deps'] = $deps;
1199 # Replace spaces with underscores in namespace names
1200 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1202 # And do the same for special page aliases. $page is an array.
1203 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1204 $page = str_replace(
' ',
'_', $page );
1206 # Decouple the reference to prevent accidental damage
1209 # If there were no plural rules, return an empty array
1210 $allData[
'pluralRules'] ??= [];
1211 $allData[
'compiledPluralRules'] ??= [];
1212 # If there were no plural rule types, return an empty array
1213 $allData[
'pluralRuleTypes'] ??= [];
1216 $allData[
'list'] = [];
1217 foreach ( self::SPLIT_KEYS as $key ) {
1218 $allData[
'list'][$key] = array_keys( $allData[$key] );
1222 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1224 # Save to the process cache and register the items loaded
1225 $this->data[$code] = $allData;
1226 $this->loadedItems[$code] = [];
1227 $this->loadedSubitems[$code] = [];
1228 foreach ( $allData as $key => $item ) {
1229 $this->loadedItems[$code][$key] =
true;
1232 # Prefix each item with its source language code before save
1233 foreach ( self::SOURCE_PREFIX_KEYS as $key ) {
1235 foreach ( $allData[$key] as $subKey => $value ) {
1237 $allData[$key][$subKey] = ( $this->sourceLanguage[$code][$key][$subKey] ?? $code ) .
1238 self::SOURCEPREFIX_SEPARATOR . $value;
1242 # Set the preload key
1243 $allData[
'preload'] = $this->buildPreload( $allData );
1245 # Save to the persistent cache
1246 $this->store->startWrite( $code );
1247 foreach ( $allData as $key => $value ) {
1248 if ( in_array( $key, self::SPLIT_KEYS ) ) {
1249 foreach ( $value as $subkey => $subvalue ) {
1250 $this->store->set(
"$key:$subkey", $subvalue );
1253 $this->store->set( $key, $value );
1256 $this->store->finishWrite();
1258 # Clear out the MessageBlobStore
1259 # HACK: If using a null (i.e., disabled) storage backend, we
1260 # can't write to the MessageBlobStore either
1262 foreach ( $this->clearStoreCallbacks as $callback ) {
1277 private function buildPreload( $data ) {
1278 $preload = [
'messages' => [] ];
1279 foreach ( self::PRELOADED_KEYS as $key ) {
1280 $preload[$key] = $data[$key];
1283 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1284 $subitem = $data[
'messages'][$subkey] ??
null;
1285 $preload[
'messages'][$subkey] = $subitem;
1299 unset( $this->data[$code] );
1300 unset( $this->loadedItems[$code] );
1301 unset( $this->loadedSubitems[$code] );
1302 unset( $this->initialisedLangs[$code] );
1303 unset( $this->shallowFallbacks[$code] );
1304 unset( $this->sourceLanguage[$code] );
1305 unset( $this->coreDataLoaded[$code] );
1307 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1308 if ( $fbCode === $code ) {
1309 $this->unload( $shallowCode );
1318 foreach ( $this->initialisedLangs as $lang => $unused ) {
1319 $this->unload( $lang );
1328 $this->manualRecache =
false;
if(!defined( 'MEDIAWIKI')) if(ini_get( 'mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Base class to represent dependencies for LocalisationCache entries.
isExpired()
Returns true if the dependency is expired, false otherwise.
Depend on a PHP constant.
Depend on a PHP global variable.
Null store backend, used to avoid DB errors during MediaWiki installation.
Caching for the contents of localisation files.
getSubitemWithSource( $code, $key, $subkey)
Get a subitem with its source language.
array< string, array< string, array< string, string > > > $sourceLanguage
The source language of cached data items.
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
unload( $code)
Unload the data for a given language from the object cache.
unloadAll()
Unload all data.
disableBackend()
Disable the storage backend.
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
__construct(ServiceOptions $options, LCStore $store, LoggerInterface $logger, array $clearStoreCallbacks, LanguageNameUtils $langNameUtils, HookContainer $hookContainer)
For constructor parameters, \MediaWiki\MainConfigSchema::LocalisationCacheConf.
recache( $code)
Load localisation data for a given language for both core and extensions and save it to the persisten...
isExpired( $code)
Returns true if the cache identified by $code is missing or expired.
getSubitem( $code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
array< string, array > $data
The cache data.
static getStoreFromConf(array $conf, $fallbackCacheDir)
Return a suitable LCStore as specified by the given configuration.
const ALL_KEYS
All item keys.
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
getItem( $code, $key)
Get a cache item.
Depend on a MediaWiki configuration variable.
A class containing constants representing the names of configuration variables.
Interface for the persistence layer of LocalisationCache.