wikimedia/mediawiki-extensions-Wikibase

View on GitHub
lib/includes/LanguageWithConversion.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

namespace Wikibase\Lib;

use InvalidArgumentException;
use MediaWiki\Language\Language;
use MediaWiki\MediaWikiServices;

/**
 * Object representing either a verbatim language or a converted language.
 * Used for items in language fallback chain.
 *
 * @license GPL-2.0-or-later
 * @author Liangent < liangent@gmail.com >
 */
class LanguageWithConversion {

    /**
     * @var array[]
     */
    private static $objectCache = [];

    /**
     * @var string
     */
    private $languageCode;

    /**
     * @var string|null
     */
    private $sourceLanguageCode;

    /**
     * @var Language|null
     */
    private $parentLanguage;

    /**
     * @var string[]
     */
    private $translateCache = [];

    /**
     * @var bool[]
     */
    private $translatePool = [];

    /**
     * @param string $languageCode
     * @param null|string $sourceLanguageCode
     * @param null|Language $parentLanguage
     */
    private function __construct(
        $languageCode,
        $sourceLanguageCode = null,
        Language $parentLanguage = null
    ) {
        $this->languageCode = $languageCode;
        $this->sourceLanguageCode = $sourceLanguageCode;
        $this->parentLanguage = $parentLanguage;
    }

    /**
     * Validate a language code. Logic taken from class Language.
     *
     * @param string $code Language code
     *
     * @return string Validated and normalized code.
     * @throws InvalidArgumentException on invalid code
     */
    public static function validateLanguageCode( $code ) {
        global $wgDummyLanguageCodes;

        if ( isset( $wgDummyLanguageCodes[$code] ) ) {
            $code = $wgDummyLanguageCodes[$code];
        }

        if ( !MediaWikiServices::getInstance()->getLanguageNameUtils()->isValidCode( $code )
            || strcspn( $code, ":/\\\000" ) !== strlen( $code )
        ) {
            throw new InvalidArgumentException( __METHOD__ . ': Invalid language code ' . $code );
        }

        return $code;
    }

    /**
     * Get a LanguageWithConversion object.
     *
     * @param Language|string $language Language (code) for this object
     * @param Language|string|null $sourceLanguage
     *          Source language (code) if this is a converted language, or null
     *
     * @throws InvalidArgumentException
     * @return self
     */
    public static function factory( $language, $sourceLanguage = null ) {
        if ( is_string( $language ) ) {
            $languageCode = self::validateLanguageCode( $language );
        } else {
            $languageCode = $language->getCode();
        }

        if ( is_string( $sourceLanguage ) ) {
            $sourceLanguageCode = self::validateLanguageCode( $sourceLanguage );
        } elseif ( $sourceLanguage === null ) {
            $sourceLanguageCode = null;
        } else {
            $sourceLanguageCode = $sourceLanguage->getCode();
        }

        $sourceLanguageKey = $sourceLanguageCode === null ? '' : $sourceLanguageCode;
        if ( isset( self::$objectCache[$languageCode][$sourceLanguageKey] ) ) {
            return self::$objectCache[$languageCode][$sourceLanguageKey];
        }

        if ( $sourceLanguageCode !== null ) {
            $services = MediaWikiServices::getInstance();
            $langFactory = $services->getLanguageFactory();
            $langConvFactory = $services->getLanguageConverterFactory();
            $parentLanguage = $langFactory->getParentLanguage( $languageCode );

            if ( !$parentLanguage ) {
                throw new InvalidArgumentException( __METHOD__ . ': $language does not support conversion' );
            }

            if ( !$langConvFactory->getLanguageConverter( $parentLanguage )->hasVariant( $sourceLanguageCode ) ) {
                throw new InvalidArgumentException( __METHOD__ . ': given languages do not have the same parent language' );
            }
        } else {
            $parentLanguage = null;
        }

        $object = new self( $languageCode, $sourceLanguageCode, $parentLanguage );
        self::$objectCache[$languageCode][$sourceLanguageKey] = $object;
        return $object;
    }

    /**
     * Get the code of the language this object wraps.
     *
     * @return string
     */
    public function getLanguageCode() {
        return $this->languageCode;
    }

    /**
     * Get the code of the source language defined.
     *
     * @return string|null
     */
    public function getSourceLanguageCode() {
        return $this->sourceLanguageCode;
    }

    /**
     * Get the code of the language where data should be fetched.
     *
     * @return string
     */
    public function getFetchLanguageCode() {
        if ( $this->sourceLanguageCode !== null ) {
            return $this->sourceLanguageCode;
        } else {
            return $this->languageCode;
        }
    }

    /**
     * Translate data after fetching them.
     *
     * @param string $text Data to transform
     * @return string
     */
    public function translate( $text ) {
        if ( $this->parentLanguage ) {
            if ( isset( $this->translateCache[$text] ) ) {
                return $this->translateCache[$text];
            } else {
                $this->prepareForTranslate( $text );
                $this->executeTranslate();
                return $this->translateCache[$text];
            }
        } else {
            return $text;
        }
    }

    /**
     * Insert a text snippet which will be translated later.
     *
     * Due to the implementation of language converter, massive
     * calls with short text snippets may introduce big overhead.
     * If it's foreseeable that some text will be translated
     * later, add it here for batched translation.
     *
     * Does nothing if this is not a converted language.
     *
     * @param string $text
     */
    private function prepareForTranslate( $text ) {
        if ( $this->parentLanguage ) {
            $this->translatePool[$text] = true;
        }
    }

    /**
     * Really execute translation.
     */
    private function executeTranslate() {
        if ( $this->parentLanguage && count( $this->translatePool ) ) {
            $pieces = array_keys( $this->translatePool );
            $block = implode( "\0", $pieces );
            $translatedBlock = MediaWikiServices::getInstance()->getLanguageConverterFactory()
                ->getLanguageConverter( $this->parentLanguage )
                ->translate( $block, $this->languageCode );
            $translatedPieces = explode( "\0", $translatedBlock );
            $this->translateCache += array_combine( $pieces, $translatedPieces );
            $this->translatePool = [];
        }
    }

}