23use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
24use CLDRPluralRuleParser\Evaluator;
29use Psr\Log\LoggerInterface;
124 'fallback',
'namespaceNames',
'bookstoreList',
125 'magicWords',
'messages',
'rtl',
'capitalizeAllNouns',
126 'digitTransformTable',
'separatorTransformTable',
127 'minimumGroupingDigits',
'fallback8bitEncoding',
128 'linkPrefixExtension',
'linkTrail',
'linkPrefixCharset',
129 'namespaceAliases',
'dateFormats',
'datePreferences',
130 'datePreferenceMigrationMap',
'defaultDateFormat',
131 'specialPageAliases',
'imageFiles',
'preloadedMessages',
132 'namespaceGenderAliases',
'digitGroupingPattern',
'pluralRules',
133 'pluralRuleTypes',
'compiledPluralRules',
141 'namespaceAliases',
'dateFormats',
'imageFiles',
'preloadedMessages'
209 $storeArg[
'directory'] =
210 $conf[
'storeDirectory'] ?: $fallbackCacheDir;
212 if ( !empty( $conf[
'storeClass'] ) ) {
213 $storeClass = $conf[
'storeClass'];
214 } elseif ( $conf[
'store'] ===
'files' || $conf[
'store'] ===
'file' ||
215 ( $conf[
'store'] ===
'detect' && $storeArg[
'directory'] )
217 $storeClass = LCStoreCDB::class;
218 } elseif ( $conf[
'store'] ===
'db' || $conf[
'store'] ===
'detect' ) {
219 $storeClass = LCStoreDB::class;
220 $storeArg[
'server'] = $conf[
'storeServer'] ?? [];
221 } elseif ( $conf[
'store'] ===
'array' ) {
222 $storeClass = LCStoreStaticArray::class;
225 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
229 return new $storeClass( $storeArg );
236 public const CONSTRUCTOR_OPTIONS = [
240 'ExtensionMessagesFiles',
263 LoggerInterface $logger,
264 array $clearStoreCallbacks,
270 $this->options = $options;
271 $this->store = $store;
272 $this->logger = $logger;
273 $this->clearStoreCallbacks = $clearStoreCallbacks;
274 $this->langNameUtils = $langNameUtils;
275 $this->hookRunner =
new HookRunner( $hookContainer );
278 $this->manualRecache = $options->
get(
'manualRecache' );
288 if ( $this->mergeableKeys ===
null ) {
289 $this->mergeableKeys = array_flip( array_merge(
290 self::$mergeableMapKeys,
291 self::$mergeableListKeys,
292 self::$mergeableAliasListKeys,
293 self::$optionalMergeKeys,
298 return isset( $this->mergeableKeys[$key] );
311 if ( !isset( $this->loadedItems[$code][$key] ) ) {
312 $this->loadItem( $code, $key );
315 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
316 return $this->shallowFallbacks[$code];
319 return $this->data[$code][$key];
330 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
331 !isset( $this->loadedItems[$code][$key] )
333 $this->loadSubitem( $code, $key, $subkey );
336 return $this->data[$code][$key][$subkey] ??
null;
352 if ( in_array( $key, self::$splitKeys ) ) {
353 return $this->getSubitem( $code,
'list', $key );
355 $item = $this->getItem( $code, $key );
356 if ( is_array( $item ) ) {
357 return array_keys( $item );
370 if ( !isset( $this->initialisedLangs[$code] ) ) {
371 $this->initLanguage( $code );
375 if ( isset( $this->loadedItems[$code][$key] ) ) {
379 if ( isset( $this->shallowFallbacks[$code] ) ) {
380 $this->loadItem( $this->shallowFallbacks[$code], $key );
385 if ( in_array( $key, self::$splitKeys ) ) {
386 $subkeyList = $this->getSubitem( $code,
'list', $key );
387 foreach ( $subkeyList as $subkey ) {
388 if ( isset( $this->data[$code][$key][$subkey] ) ) {
391 $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
394 $this->data[$code][$key] = $this->store->get( $code, $key );
397 $this->loadedItems[$code][$key] =
true;
407 if ( !in_array( $key, self::$splitKeys ) ) {
408 $this->loadItem( $code, $key );
413 if ( !isset( $this->initialisedLangs[$code] ) ) {
414 $this->initLanguage( $code );
418 if ( isset( $this->loadedItems[$code][$key] ) ||
419 isset( $this->loadedSubitems[$code][$key][$subkey] )
424 if ( isset( $this->shallowFallbacks[$code] ) ) {
425 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
430 $value = $this->store->get( $code,
"$key:$subkey" );
431 $this->data[$code][$key][$subkey] = $value;
432 $this->loadedSubitems[$code][$key][$subkey] =
true;
443 if ( $this->options->get(
'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
444 $this->logger->debug( __METHOD__ .
"($code): forced reload" );
449 $deps = $this->store->get( $code,
'deps' );
450 $keys = $this->store->get( $code,
'list' );
451 $preload = $this->store->get( $code,
'preload' );
453 if ( $deps ===
null ||
$keys ===
null || $preload ===
null ) {
454 $this->logger->debug( __METHOD__ .
"($code): cache missing, need to make one" );
459 foreach ( $deps as $dep ) {
465 $this->logger->debug( __METHOD__ .
"($code): cache for $code expired due to " .
481 if ( isset( $this->initialisedLangs[$code] ) ) {
485 $this->initialisedLangs[$code] =
true;
487 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
488 if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
489 $this->initShallowFallback( $code,
'en' );
494 # Recache the data if necessary
495 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
496 if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
497 $this->recache( $code );
498 } elseif ( $code ===
'en' ) {
499 throw new MWException(
'MessagesEn.php is missing.' );
501 $this->initShallowFallback( $code,
'en' );
508 $preload = $this->getItem( $code,
'preload' );
509 if ( $preload ===
null ) {
510 if ( $this->manualRecache ) {
512 if ( $code ===
'en' ) {
513 throw new MWException(
'No localisation cache found for English. ' .
514 'Please run maintenance/rebuildLocalisationCache.php.' );
516 $this->initShallowFallback( $code,
'en' );
520 throw new MWException(
'Invalid or missing localisation cache.' );
523 $this->data[$code] = $preload;
524 foreach ( $preload as $key => $item ) {
525 if ( in_array( $key, self::$splitKeys ) ) {
526 foreach ( $item as $subkey => $subitem ) {
527 $this->loadedSubitems[$code][$key][$subkey] =
true;
530 $this->loadedItems[$code][$key] =
true;
542 $this->data[$primaryCode] =& $this->data[$fallbackCode];
543 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
544 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
545 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
559 if ( $_fileType ==
'core' || $_fileType ==
'extension' ) {
560 foreach ( self::$allKeys as $key ) {
563 if ( isset( $$key ) ) {
567 } elseif ( $_fileType ==
'aliases' ) {
569 if ( isset( $aliases ) ) {
570 $data[
'aliases'] = $aliases;
573 throw new MWException( __METHOD__ .
": Invalid file type: $_fileType" );
586 if ( !is_readable( $fileName ) ) {
590 $json = file_get_contents( $fileName );
591 if ( $json ===
false ) {
595 $data = FormatJson::decode( $json,
true );
596 if ( $data ===
null ) {
597 throw new MWException( __METHOD__ .
": Invalid JSON file: $fileName" );
601 foreach ( $data as $key => $unused ) {
602 if ( $key ===
'' || $key[0] ===
'@' ) {
603 unset( $data[$key] );
608 return [
'messages' => $data ];
618 $rules = $this->getPluralRules( $code );
619 if ( $rules ===
null ) {
623 $compiledRules = Evaluator::compile( $rules );
624 }
catch ( CLDRPluralRuleError $e ) {
625 $this->logger->debug( $e->getMessage() );
630 return $compiledRules;
641 if ( $this->pluralRules ===
null ) {
642 $this->loadPluralFiles();
644 return $this->pluralRules[$code] ??
null;
655 if ( $this->pluralRuleTypes ===
null ) {
656 $this->loadPluralFiles();
658 return $this->pluralRuleTypes[$code] ??
null;
666 $cldrPlural =
"$IP/languages/data/plurals.xml";
667 $mwPlural =
"$IP/languages/data/plurals-mediawiki.xml";
669 $this->loadPluralFile( $cldrPlural );
670 if ( file_exists( $mwPlural ) ) {
672 $this->loadPluralFile( $mwPlural );
685 $xml = file_get_contents( $fileName );
687 throw new MWException(
"Unable to read plurals file $fileName" );
689 $doc =
new DOMDocument;
690 $doc->loadXML( $xml );
691 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
692 foreach ( $rulesets as $ruleset ) {
693 $codes = $ruleset->getAttribute(
'locales' );
696 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
697 foreach ( $ruleElements as $elt ) {
698 $ruleType = $elt->getAttribute(
'count' );
699 if ( $ruleType ===
'other' ) {
703 $rules[] = $elt->nodeValue;
704 $ruleTypes[] = $ruleType;
706 foreach ( explode(
' ', $codes ) as $code ) {
707 $this->pluralRules[$code] = $rules;
708 $this->pluralRuleTypes[$code] = $ruleTypes;
726 $fileName = $this->langNameUtils->getMessagesFileName( $code );
727 if ( !file_exists( $fileName ) ) {
731 $data = $this->readPHPFile( $fileName,
'core' );
734 # Load CLDR plural rules for JavaScript
735 $data[
'pluralRules'] = $this->getPluralRules( $code );
737 $data[
'compiledPluralRules'] = $this->getCompiledPluralRules( $code );
738 # Load plural rule types
739 $data[
'pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
741 $deps[
'plurals'] =
new FileDependency(
"$IP/languages/data/plurals.xml" );
742 $deps[
'plurals-mw'] =
new FileDependency(
"$IP/languages/data/plurals-mediawiki.xml" );
754 protected function mergeItem( $key, &$value, $fallbackValue ) {
755 if ( $value !==
null ) {
756 if ( $fallbackValue !==
null ) {
757 if ( in_array( $key, self::$mergeableMapKeys ) ) {
758 $value += $fallbackValue;
759 } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
760 $value = array_unique( array_merge( $fallbackValue, $value ) );
761 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
762 $value = array_merge_recursive( $value, $fallbackValue );
763 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
764 if ( !empty( $value[
'inherit'] ) ) {
765 $value = array_merge( $fallbackValue, $value );
768 if ( isset( $value[
'inherit'] ) ) {
769 unset( $value[
'inherit'] );
771 } elseif ( in_array( $key, self::$magicWordKeys ) ) {
772 $this->mergeMagicWords( $value, $fallbackValue );
776 $value = $fallbackValue;
785 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
786 if ( !isset( $value[$magicName] ) ) {
787 $value[$magicName] = $fallbackInfo;
789 $oldSynonyms = array_slice( $fallbackInfo, 1 );
790 $newSynonyms = array_slice( $value[$magicName], 1 );
791 $synonyms = array_values( array_unique( array_merge(
792 $newSynonyms, $oldSynonyms ) ) );
793 $value[$magicName] = array_merge( [ $fallbackInfo[0] ], $synonyms );
813 foreach ( $codeSequence as $code ) {
814 if ( isset( $fallbackValue[$code] ) ) {
815 $this->mergeItem( $key, $value, $fallbackValue[$code] );
834 'core' =>
"$IP/languages/i18n",
835 'exif' =>
"$IP/languages/i18n/exif",
836 'api' =>
"$IP/includes/api/i18n",
837 'rest' =>
"$IP/includes/Rest/i18n",
838 'oojs-ui' =>
"$IP/resources/lib/ooui/i18n",
839 'paramvalidator' =>
"$IP/includes/libs/ParamValidator/i18n",
840 ] + $this->options->get(
'MessagesDirs' );
851 throw new MWException(
"Invalid language code requested" );
853 $this->recachedLangs[ $code ] =
true;
856 $initialData = array_fill_keys( self::$allKeys,
null );
857 $coreData = $initialData;
860 # Load the primary localisation from the source file
861 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
862 $this->logger->debug( __METHOD__ .
": got localisation for $code from source" );
864 # Merge primary localisation
865 foreach ( $data as $key => $value ) {
866 $this->mergeItem( $key, $coreData[ $key ], $value );
869 # Fill in the fallback if it's not there already
871 if ( ( $coreData[
'fallback'] ===
null || $coreData[
'fallback'] ===
false ) && $code ===
'en' ) {
872 $coreData[
'fallback'] =
false;
873 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'] = [];
875 if ( $coreData[
'fallback'] !==
null ) {
876 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
878 $coreData[
'fallbackSequence'] = [];
880 $len = count( $coreData[
'fallbackSequence'] );
882 # Before we add the 'en' fallback for messages, keep a copy of
883 # the original fallback sequence
884 $coreData[
'originalFallbackSequence'] = $coreData[
'fallbackSequence'];
886 # Ensure that the sequence ends at 'en' for messages
887 if ( !$len || $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
888 $coreData[
'fallbackSequence'][] =
'en';
892 $codeSequence = array_merge( [ $code ], $coreData[
'fallbackSequence'] );
893 $messageDirs = $this->getMessagesDirs();
895 # Load non-JSON localisation data for extensions
896 $extensionData = array_fill_keys( $codeSequence, $initialData );
897 foreach ( $this->options->get(
'ExtensionMessagesFiles' ) as $extension => $fileName ) {
898 if ( isset( $messageDirs[$extension] ) ) {
899 # This extension has JSON message data; skip the PHP shim
903 $data = $this->readPHPFile( $fileName,
'extension' );
906 foreach ( $data as $key => $item ) {
907 foreach ( $codeSequence as $csCode ) {
908 if ( isset( $item[$csCode] ) ) {
909 $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
920 # Load the localisation data for each fallback, then merge it into the full array
921 $allData = $initialData;
922 foreach ( $codeSequence as $csCode ) {
923 $csData = $initialData;
925 # Load core messages and the extension localisations.
926 foreach ( $messageDirs as $dirs ) {
927 foreach ( (array)$dirs as $dir ) {
928 $fileName =
"$dir/$csCode.json";
929 $data = $this->readJSONFile( $fileName );
931 foreach ( $data as $key => $item ) {
932 $this->mergeItem( $key, $csData[$key], $item );
939 # Merge non-JSON extension data
940 if ( isset( $extensionData[$csCode] ) ) {
941 foreach ( $extensionData[$csCode] as $key => $item ) {
942 $this->mergeItem( $key, $csData[$key], $item );
946 if ( $csCode === $code ) {
947 # Merge core data into extension data
948 foreach ( $coreData as $key => $item ) {
949 $this->mergeItem( $key, $csData[$key], $item );
952 # Load the secondary localisation from the source file to
953 # avoid infinite cycles on cyclic fallbacks
954 $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
955 # Only merge the keys that make sense to merge
956 foreach ( self::$allKeys as $key ) {
957 if ( !isset( $fbData[ $key ] ) ) {
961 if ( ( $coreData[ $key ] ) ===
null || $this->isMergeableKey( $key ) ) {
962 $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
967 # Allow extensions an opportunity to adjust the data for this
969 $this->hookRunner->onLocalisationCacheRecacheFallback( $this, $csCode, $csData );
971 # Merge the data for this fallback into the final array
972 if ( $csCode === $code ) {
975 foreach ( self::$allKeys as $key ) {
976 if ( !isset( $csData[$key] ) ) {
981 if ( $allData[$key] ===
null || $this->isMergeableKey( $key ) ) {
982 $this->mergeItem( $key, $allData[$key], $csData[$key] );
988 # Add cache dependencies for any referenced globals
989 $deps[
'wgExtensionMessagesFiles'] =
new GlobalDependency(
'wgExtensionMessagesFiles' );
995 # Add dependencies to the cache entry
996 $allData[
'deps'] = $deps;
998 # Replace spaces with underscores in namespace names
999 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
1001 # And do the same for special page aliases. $page is an array.
1002 foreach ( $allData[
'specialPageAliases'] as &$page ) {
1003 $page = str_replace(
' ',
'_', $page );
1005 # Decouple the reference to prevent accidental damage
1008 # If there were no plural rules, return an empty array
1009 if ( $allData[
'pluralRules'] ===
null ) {
1010 $allData[
'pluralRules'] = [];
1012 if ( $allData[
'compiledPluralRules'] ===
null ) {
1013 $allData[
'compiledPluralRules'] = [];
1015 # If there were no plural rule types, return an empty array
1016 if ( $allData[
'pluralRuleTypes'] ===
null ) {
1017 $allData[
'pluralRuleTypes'] = [];
1021 $allData[
'list'] = [];
1022 foreach ( self::$splitKeys as $key ) {
1023 $allData[
'list'][$key] = array_keys( $allData[$key] );
1027 $this->hookRunner->onLocalisationCacheRecache( $this, $code, $allData, $unused );
1029 if ( $allData[
'namespaceNames'] ===
null ) {
1030 throw new MWException( __METHOD__ .
': Localisation data failed sanity check! ' .
1031 'Check that your languages/messages/MessagesEn.php file is intact.' );
1034 # Set the preload key
1035 $allData[
'preload'] = $this->buildPreload( $allData );
1037 # Save to the process cache and register the items loaded
1038 $this->data[$code] = $allData;
1039 foreach ( $allData as $key => $item ) {
1040 $this->loadedItems[$code][$key] =
true;
1043 # Save to the persistent cache
1044 $this->store->startWrite( $code );
1045 foreach ( $allData as $key => $value ) {
1046 if ( in_array( $key, self::$splitKeys ) ) {
1047 foreach ( $value as $subkey => $subvalue ) {
1048 $this->store->set(
"$key:$subkey", $subvalue );
1051 $this->store->set( $key, $value );
1054 $this->store->finishWrite();
1056 # Clear out the MessageBlobStore
1057 # HACK: If using a null (i.e. disabled) storage backend, we
1058 # can't write to the MessageBlobStore either
1060 foreach ( $this->clearStoreCallbacks as $callback ) {
1075 $preload = [
'messages' => [] ];
1076 foreach ( self::$preloadedKeys as $key ) {
1077 $preload[$key] = $data[$key];
1080 foreach ( $data[
'preloadedMessages'] as $subkey ) {
1081 $subitem = $data[
'messages'][$subkey] ??
null;
1082 $preload[
'messages'][$subkey] = $subitem;
1094 unset( $this->data[$code] );
1095 unset( $this->loadedItems[$code] );
1096 unset( $this->loadedSubitems[$code] );
1097 unset( $this->initialisedLangs[$code] );
1098 unset( $this->shallowFallbacks[$code] );
1100 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1101 if ( $fbCode === $code ) {
1102 $this->unload( $shallowCode );
1111 foreach ( $this->initialisedLangs as
$lang => $unused ) {
1112 $this->unload(
$lang );
1121 $this->manualRecache =
false;
isExpired()
Returns true if the dependency is expired, false otherwise.
Null store backend, used to avoid DB errors during install.
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
$manualRecache
True if recaching should only be done on an explicit call to recache().
static $magicWordKeys
Keys for items that are formatted like $magicWords.
buildPreload( $data)
Build the preload item from the given pre-cache data.
initLanguage( $code)
Initialise a language in this object.
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence.
readSourceFilesAndRegisterDeps( $code, &$deps)
Read the data from the source files for a given language, and register the relevant dependencies in t...
isMergeableKey( $key)
Returns true if the given key is mergeable, that is, if it is an associative array which can be merge...
loadPluralFile( $fileName)
Load a plural XML file with the given filename, compile the relevant rules, and save the compiled rul...
getPluralRules( $code)
Get the plural rules for a given language from the XML files.
$loadedItems
A 2-d associative array, code/key, where presence indicates that the item is loaded.
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
$initialisedLangs
An array where presence of a key indicates that that language has been initialised.
readJSONFile( $fileName)
Read a JSON file containing localisation messages.
loadPluralFiles()
Load the plural XML files.
unload( $code)
Unload the data for a given language from the object cache.
unloadAll()
Unload all data.
disableBackend()
Disable the storage backend.
static $mergeableAliasListKeys
Keys for items which contain an array of arrays of equivalent aliases for each subitem.
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
$recachedLangs
An array where the keys are codes that have been recached by this instance.
$loadedSubitems
A 3-d associative array, code/key/subkey, where presence indicates that the subitem is loaded.
__construct(ServiceOptions $options, LCStore $store, LoggerInterface $logger, array $clearStoreCallbacks, LanguageNameUtils $langNameUtils, HookContainer $hookContainer)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
static $mergeableListKeys
Keys for items which are a numbered array.
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.
mergeItem( $key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place.
getSubitem( $code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
mergeMagicWords(&$value, $fallbackValue)
$shallowFallbacks
An array mapping non-existent pseudo-languages to fallback languages.
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache.
static getStoreFromConf(array $conf, $fallbackCacheDir)
Return a suitable LCStore as specified by the given configuration.
LanguageNameUtils $langNameUtils
static $allKeys
All item keys.
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
static $optionalMergeKeys
Keys for items which contain an associative array, and may be merged if the primary value contains th...
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
callable[] $clearStoreCallbacks
See comment for parameter in constructor.
static $preloadedKeys
Keys which are loaded automatically by initLanguage()
getItem( $code, $key)
Get a cache item.
static $splitKeys
Keys for items where the subitems are stored in the backend separately.
loadItem( $code, $key)
Load an item into the cache.
$pluralRuleTypes
Associative array of cached plural rule types.
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
LCStore $store
The persistent store object.
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
$pluralRules
Associative array of cached plural rules.
mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue)
Given an array mapping language code to localisation value, such as is found in extension *....
Interface for the persistence layer of LocalisationCache.
if(!isset( $args[0])) $lang