HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/momsecurity.komma.nl/tests/Unit/TranslationFilesTest.php
<?php

namespace Tests\Unit;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Tests\TestCase;

/**
 * Class TranslationFiles
 *
 * Tests translation files for the other languages contains the same keys
 *
 * @package Tests\Unit
 */
class TranslationFilesTest extends TestCase
{

    private array $translationKeys = [];

    private array $languageFunctionPatterns = [
        "/__\('(.*?)'[),]/",
        "/trans\('(.*?)'[),]/",
        "/lang\('(.*?)'[),]/"
    ];

    private array $skippableStartKeys = [
        'KMS::',
        'form.',
        'calendar.',
        'validation.',
        'passwords.',
        'company.',
        'routes',
        'errors.',
    ];

    /**
     * Setup the test environment.
     *
     * @throws \Exception
     */
    protected function setUp(): void
    {
        parent::setUp();

        $localLanguageFolder = resource_path('lang/' . app()->getLocale());
        $this->assertFileExists($localLanguageFolder);

        $languageFiles = File::allFiles( resource_path('lang/' . app()->getLocale()));

        foreach ($languageFiles as $languageFile) {

            $fileName = $languageFile->getRelativePathname();
            $translationFileKey = Str::replaceLast('.' . $languageFile->getExtension(), '', $fileName);

            $this->getKeysFromArray($translationFileKey);
        }
    }

    /**
     * Recurring function for grabbing the translations out of an array till it's a string
     *
     * @param  string  $translationParentKey
     * @throws \Exception
     */
    private function getKeysFromArray(string $translationParentKey)
    {
        foreach (__($translationParentKey) as $translationKey => $translationValue) {
            if(is_array($translationValue)) {
                $this->getKeysFromArray($translationParentKey . '.' . $translationKey);
                continue;
            }

            $this->assertIsString($translationValue);
            $this->translationKeys[] = $translationParentKey . '.' . $translationKey;
        }
    }

    /**
     * @group TranslationFilesTest
     * @test
     * @throws \Exception
     */
    public function checkOtherLocalesIfTranslationsKeysExists()
    {
        $missingTranslations = collect();

        foreach (config('languages.available') as $availableAppLanguage)
        {
            // We can skip the local languages, because we generated the test out of the keys of that translation.
            if($availableAppLanguage == app()->getLocale()) continue;

            $foundTranslations = [];

            foreach ($this->translationKeys as $translationKey) {

                try {
                    $translation = __($translationKey, [], $availableAppLanguage);
                    $this->assertIsString($translation);
                    $foundTranslations[$translationKey] = $translation; // Put it into the foundTranslation for the length check
//                    echo 'Found translation key for locale - ' . $availableAppLanguage .' | ' . $translationKey . PHP_EOL;
                }
                catch (\Exception $exception) {
                    $missingTranslations->push((object) [
                        'message' => $exception->getMessage(),
                    ]);
                }
            }
        }

        if($missingTranslations->isNotEmpty()) {
            echo PHP_EOL;
            foreach ($missingTranslations as $missingTranslation) echo $missingTranslation->message . PHP_EOL;
            echo PHP_EOL;
            $this->assertEmpty($missingTranslations->count(), 'Not all the translations from the global language are present in the other available languages.');
        }
    }

    /**
     * @group TranslationFilesTest
     * @test
     * @throws \Exception
     */
    public function checkUsageOfTranslations()
    {

        $fileList = [];
        $this->addFilesContainLanguageFunctionsOutOfDirectory($fileList, app_path());
        $this->addFilesContainLanguageFunctionsOutOfDirectory($fileList, resource_path());

        // Create a list of all the used translation keys
        $usedTranslationKeys = $this->createUsedTranslationKeysList($fileList);

        // Copy the translations keys so we track if they are used
        $unusedTranslationsKeys = $this->translationKeys;
        $resolvedTranslationKeys = [];

        // Loop through the used translations and validate that they are defined.
        foreach ($usedTranslationKeys as $usedTranslationKey ) {

//            echo $usedTranslationKey.PHP_EOL;

            // Skip translations keys starting with, contains variables or already resolved keys
            if(Str::startsWith($usedTranslationKey, $this->skippableStartKeys) || Str::contains($usedTranslationKey, ['.$', '. $', '($']) || in_array($usedTranslationKey, $resolvedTranslationKeys)) continue;

            // Validate that the translation is found
            $this->assertContains($usedTranslationKey, $unusedTranslationsKeys, 'Found translation "' . $usedTranslationKey .'" is not defined in the languages files.');

            // Remove this translation key from the unusedTranslationsKeys because it is used
            unset($unusedTranslationsKeys[array_search($usedTranslationKey, $unusedTranslationsKeys)]);

            // Append the resolve translation, in case it is used multiple times
            $resolvedTranslationKeys[] = $usedTranslationKey;
        }

        $unusedTranslationErrors = collect();

        // Loop through the remaining defined translations keys
        foreach ($unusedTranslationsKeys as $unusedTranslationsKey) {

            // Dont show through errors for the skippable start keys
            if(Str::startsWith($unusedTranslationsKey, $this->skippableStartKeys)) continue;

            $unusedTranslationErrors->push((object) [
                'message' => 'The translation key "' . $unusedTranslationsKey .'" is not used within the application. If this is not correct and if used in a undetectable way, then exclude it from this test.',
            ]);
        }

        if($unusedTranslationErrors->isNotEmpty()) {
            echo PHP_EOL;
            foreach ($unusedTranslationErrors as $unusedTranslationError) echo $unusedTranslationError->message . PHP_EOL;
            echo PHP_EOL;
            $this->assertEmpty($unusedTranslationErrors->count(), 'Not defined translations are used in the application or you should extends skippable keys.');
        }
    }

    /**
     * Map all the files out of the index into the file list
     *
     * @param  array  $fileList
     * @param  string  $directoryPath
     */
    private function addFilesContainLanguageFunctionsOutOfDirectory(array &$fileList, string $directoryPath)
    {

//        echo 'Directory indexed: "' . $directoryPath . '"' . PHP_EOL;
        $directory = new \DirectoryIterator($directoryPath);

        foreach ($directory as $file) {

            // Exclude itself
            if($file->isDot()) continue;

            // If directory then search within that directory
            if($file->isDir()) {
                $this->addFilesContainLanguageFunctionsOutOfDirectory($fileList, $file->getPathname());
                continue;
            }

            $fileContent = file_get_contents($file->getPathname());
            if(!Str::contains($fileContent, ['__(', 'trans(', '@lang('])) continue;

//            echo 'File added that contains translation function: "' . $file->getPathname() . '"' . PHP_EOL ;
            $fileList[] = $file->getPathname();
        }
    }

    /**
     * Generate a list of used translations
     *
     * @param  array  $fileList
     * @return array
     */
    private function createUsedTranslationKeysList(array $fileList): array
    {
        $usedTranslationKeys = [];

        foreach ($fileList as $localizedFile)
        {
            $fileContent = file_get_contents($localizedFile);

            foreach ($this->languageFunctionPatterns as $pattern) {
                preg_match_all($pattern, $fileContent, $matches);
                $usedTranslationKeys = array_merge($usedTranslationKeys, $matches[1]);
            }
        }

        return $usedTranslationKeys;
    }

}