File: D:/HostingSpaces/SBogers68/otium-gebiedsontwikkeling.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;
}
}