getContentLanguage(); } elseif ( !( $lang instanceof Language ) ) { $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $lang ); } $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory() ->getLanguageConverter( $lang ); $data = []; $data['dateFormat'] = []; $dateFormat = $lang->getDateFormatString( 'both', $lang->dateFormat( false ) ); foreach ( $langConv->getVariants() as $variant ) { $convDateFormat = self::convertDateFormat( $dateFormat, $langConv, $variant ); $data['dateFormat'][$variant] = $convDateFormat; } $data['digits'] = []; foreach ( $langConv->getVariants() as $variant ) { $data['digits'][$variant] = []; foreach ( str_split( '0123456789' ) as $digit ) { if ( $config->get( 'TranslateNumerals' ) ) { $localDigit = $lang->formatNumNoSeparators( $digit ); } else { $localDigit = $digit; } $convLocalDigit = $langConv->translate( $localDigit, $variant ); $data['digits'][$variant][] = $convLocalDigit; } } // ApiQuerySiteinfo $data['localTimezone'] = $config->get( 'Localtimezone' ); $data['specialContributionsName'] = MediaWikiServices::getInstance() ->getSpecialPageFactory()->getLocalNameFor( 'Contributions' ); $data['specialNewSectionName'] = MediaWikiServices::getInstance() ->getSpecialPageFactory()->getLocalNameFor( 'NewSection' ); $localTimezone = $config->get( 'Localtimezone' ); // Return all timezone abbreviations for the local timezone (there will often be two, for // non-DST and DST timestamps, and sometimes more due to historical data, but that's okay). // Avoid DateTimeZone::listAbbreviations(), it returns some half-baked list that is different // from the timezone data used by everything else in PHP. $timezoneAbbrs = array_values( array_unique( array_map( static function ( $transition ) { return $transition['abbr']; }, ( new DateTimeZone( $localTimezone ) )->getTransitions() ) ) ); $data['timezones'] = []; foreach ( $langConv->getVariants() as $variant ) { $data['timezones'][$variant] = array_combine( array_map( static function ( string $tzMsg ) use ( $lang, $langConv, $variant ) { // MWTimestamp::getTimezoneMessage() // Parser::pstPass2() // Messages used here: 'timezone-utc' and so on $key = 'timezone-' . strtolower( trim( $tzMsg ) ); $msg = wfMessage( $key )->inLanguage( $lang ); // TODO: This probably causes a similar issue to https://phabricator.wikimedia.org/T221294, // but we *must* check the message existence in the database, because the messages are not // actually defined by MediaWiki core for any timezone other than UTC... if ( $msg->exists() ) { $text = $msg->text(); } else { $text = strtoupper( $tzMsg ); } $convText = $langConv->translate( $text, $variant ); return $convText; }, $timezoneAbbrs ), array_map( 'strtoupper', $timezoneAbbrs ) ); } // Messages in content language $messagesKeys = array_merge( Language::WEEKDAY_MESSAGES, Language::WEEKDAY_ABBREVIATED_MESSAGES, Language::MONTH_MESSAGES, Language::MONTH_GENITIVE_MESSAGES, Language::MONTH_ABBREVIATED_MESSAGES ); $data['contLangMessages'] = []; foreach ( $langConv->getVariants() as $variant ) { $data['contLangMessages'][$variant] = array_combine( $messagesKeys, array_map( static function ( $key ) use ( $lang, $langConv, $variant ) { $text = wfMessage( $key )->inLanguage( $lang )->text(); return $langConv->translate( $text, $variant ); }, $messagesKeys ) ); } // How far backwards we look for a signature associated with a timestamp before giving up. // Note that this is not a hard limit on the length of signatures we detect. $data['signatureScanLimit'] = 100; return $data; } /** * Convert a date format string to a different language variant, leaving all special characters * unchanged and applying language conversion to the plain text fragments. * * @param string $format * @param ILanguageConverter $langConv * @param string $variant * @return string */ private static function convertDateFormat( string $format, ILanguageConverter $langConv, string $variant ): string { $formatLength = strlen( $format ); $s = ''; // The supported codes must match CommentParser::getTimestampRegexp() for ( $p = 0; $p < $formatLength; $p++ ) { $num = false; $code = $format[ $p ]; if ( $code === 'x' && $p < $formatLength - 1 ) { $code .= $format[++$p]; } if ( $code === 'xk' && $p < $formatLength - 1 ) { $code .= $format[++$p]; } // LAZY SHORTCUTS that might cause bugs: // * We assume that result of $langConv->translate() doesn't produce any special codes/characters // * We assume that calling $langConv->translate() separately for each character is correct switch ( $code ) { case 'xx': case 'xg': case 'd': case 'D': case 'j': case 'l': case 'F': case 'M': case 'n': case 'Y': case 'xkY': case 'G': case 'H': case 'i': // Special code - pass through unchanged $s .= $code; break; case '\\': // Plain text (backslash escaping) - convert to language variant if ( $p < $formatLength - 1 ) { $s .= '\\' . $langConv->translate( $format[++$p], $variant ); } else { $s .= $code; } break; case '"': // Plain text (quoted literal) - convert to language variant if ( $p < $formatLength - 1 ) { $endQuote = strpos( $format, '"', $p + 1 ); if ( $endQuote === false ) { // No terminating quote, assume literal " $s .= $code; } else { $s .= '"' . $langConv->translate( substr( $format, $p + 1, $endQuote - $p - 1 ), $variant ) . '"'; $p = $endQuote; } } else { // Quote at end of string, assume literal " $s .= $code; } break; default: // Plain text - convert to language variant $s .= $langConv->translate( $format[$p], $variant ); } } return $s; } /** * Return messages in content language, for use in a ResourceLoader module. * * @param ResourceLoaderContext $context * @param Config $config * @param array $messagesKeys * @return array */ public static function getContentLanguageMessages( ResourceLoaderContext $context, Config $config, array $messagesKeys = [] ): array { return array_combine( $messagesKeys, array_map( static function ( $key ) { return wfMessage( $key )->inContentLanguage()->text(); }, $messagesKeys ) ); } /** * Return information about terms-of-use messages. * * @param MessageLocalizer $context * @param Config $config * @return array Map from internal name to array of parameters for MessageLocalizer::msg() */ private static function getTermsOfUseMessages( MessageLocalizer $context, Config $config ): array { $messages = [ 'reply' => [ 'discussiontools-replywidget-terms-click', $context->msg( 'discussiontools-replywidget-reply' )->text() ], 'newtopic' => [ 'discussiontools-replywidget-terms-click', $context->msg( 'discussiontools-replywidget-newtopic' )->text() ], ]; $hookContainer = MediaWikiServices::getInstance()->getHookContainer(); $hookContainer->run( 'DiscussionToolsTermsOfUseMessages', [ &$messages, $context, $config ] ); return $messages; } /** * Return parsed terms-of-use messages, for use in a ResourceLoader module. * * @param MessageLocalizer $context * @param Config $config * @return array */ public static function getTermsOfUseMessagesParsed( MessageLocalizer $context, Config $config ): array { $messages = self::getTermsOfUseMessages( $context, $config ); foreach ( $messages as &$msg ) { $msg = $context->msg( ...$msg )->parse(); } return $messages; } /** * Return information about terms-of-use messages, for use in a ResourceLoader module as * 'versionCallback'. This is to avoid calling the parser from version invalidation code. * * @param MessageLocalizer $context * @param Config $config * @return array */ public static function getTermsOfUseMessagesVersion( MessageLocalizer $context, Config $config ): array { $messages = self::getTermsOfUseMessages( $context, $config ); foreach ( $messages as &$msg ) { $message = $context->msg( ...$msg ); $msg = [ // Include the text of the message, in case the canonical translation changes $message->plain(), // Include the page touched time, in case the on-wiki override is invalidated Title::makeTitle( NS_MEDIAWIKI, ucfirst( $message->getKey() ) )->getTouched(), ]; } return $messages; } /** * Add optional dependencies to a ResourceLoader module definition depending on loaded extensions. * * @param array $info * @return ResourceLoaderModule */ public static function addOptionalDependencies( array $info ): ResourceLoaderModule { $extensionRegistry = ExtensionRegistry::getInstance(); foreach ( $info['optionalDependencies'] as $ext => $deps ) { if ( $extensionRegistry->isLoaded( $ext ) ) { $info['dependencies'] = array_merge( $info['dependencies'], (array)$deps ); } } $class = $info['class'] ?? ResourceLoaderFileModule::class; return new $class( $info ); } }