diff options
| -rw-r--r-- | cli/i18n/I18nCompletionValidator.php | 8 | ||||
| -rw-r--r-- | cli/i18n/I18nFile.php | 11 | ||||
| -rw-r--r-- | cli/i18n/I18nUsageValidator.php | 6 | ||||
| -rwxr-xr-x | cli/manipulate.translation.php | 2 | ||||
| -rw-r--r-- | constants.php | 1 | ||||
| -rw-r--r-- | tests/cli/i18n/I18nCompletionValidatorTest.php | 143 | ||||
| -rw-r--r-- | tests/cli/i18n/I18nFileTest.php | 38 | ||||
| -rw-r--r-- | tests/cli/i18n/I18nUsageValidatorTest.php | 122 | ||||
| -rw-r--r-- | tests/cli/i18n/I18nValueTest.php | 83 |
9 files changed, 404 insertions, 10 deletions
diff --git a/cli/i18n/I18nCompletionValidator.php b/cli/i18n/I18nCompletionValidator.php index ee4ab9f78..000629f8d 100644 --- a/cli/i18n/I18nCompletionValidator.php +++ b/cli/i18n/I18nCompletionValidator.php @@ -16,6 +16,12 @@ class I18nCompletionValidator implements I18nValidatorInterface { } public function displayReport() { + if ($this->passEntries > $this->totalEntries) { + throw new \RuntimeException('The number of translated strings cannot be higher than the number of strings'); + } + if ($this->totalEntries === 0) { + return 'There is no data.' . PHP_EOL; + } return sprintf('Translation is %5.1f%% complete.', $this->passEntries / $this->totalEntries * 100) . PHP_EOL; } @@ -27,7 +33,7 @@ class I18nCompletionValidator implements I18nValidatorInterface { foreach ($this->reference as $file => $data) { foreach ($data as $refKey => $refValue) { $this->totalEntries++; - if (!array_key_exists($refKey, $this->language[$file])) { + if (!array_key_exists($file, $this->language) || !array_key_exists($refKey, $this->language[$file])) { $this->result .= "Missing key $refKey" . PHP_EOL; continue; } diff --git a/cli/i18n/I18nFile.php b/cli/i18n/I18nFile.php index a7045459c..fca31d662 100644 --- a/cli/i18n/I18nFile.php +++ b/cli/i18n/I18nFile.php @@ -3,16 +3,9 @@ require_once __DIR__ . '/I18nValue.php'; class I18nFile { - - private $i18nPath; - - public function __construct() { - $this->i18nPath = __DIR__ . '/../../app/i18n'; - } - public function load() { $i18n = array(); - $dirs = new DirectoryIterator($this->i18nPath); + $dirs = new DirectoryIterator(I18N_PATH); foreach ($dirs as $dir) { if ($dir->isDot()) { continue; @@ -32,7 +25,7 @@ class I18nFile { public function dump(array $i18n) { foreach ($i18n as $language => $file) { - $dir = $this->i18nPath . DIRECTORY_SEPARATOR . $language; + $dir = I18N_PATH . DIRECTORY_SEPARATOR . $language; if (!file_exists($dir)) { mkdir($dir); } diff --git a/cli/i18n/I18nUsageValidator.php b/cli/i18n/I18nUsageValidator.php index 2e402faf0..681e17326 100644 --- a/cli/i18n/I18nUsageValidator.php +++ b/cli/i18n/I18nUsageValidator.php @@ -16,6 +16,12 @@ class I18nUsageValidator implements I18nValidatorInterface { } public function displayReport() { + if ($this->failedEntries > $this->totalEntries) { + throw new \RuntimeException('The number of unused strings cannot be higher than the number of strings'); + } + if ($this->totalEntries === 0) { + return 'There is no data.' . PHP_EOL; + } return sprintf('%5.1f%% of translation keys are unused.', $this->failedEntries / $this->totalEntries * 100) . PHP_EOL; } diff --git a/cli/manipulate.translation.php b/cli/manipulate.translation.php index 2b53b8606..801aa30b6 100755 --- a/cli/manipulate.translation.php +++ b/cli/manipulate.translation.php @@ -3,6 +3,8 @@ require_once __DIR__ . '/i18n/I18nData.php'; require_once __DIR__ . '/i18n/I18nFile.php'; +require_once __DIR__ . '/../constants.php'; + $options = getopt("a:hk:l:o:rv:"); diff --git a/constants.php b/constants.php index a440d18ec..b38fba930 100644 --- a/constants.php +++ b/constants.php @@ -16,6 +16,7 @@ define('INDEX_PATH', PUBLIC_PATH . PUBLIC_TO_INDEX_PATH); define('PUBLIC_RELATIVE', '..'); define('LIB_PATH', FRESHRSS_PATH . '/lib'); define('APP_PATH', FRESHRSS_PATH . '/app'); +define('I18N_PATH', APP_PATH . '/i18n'); define('CORE_EXTENSIONS_PATH', LIB_PATH . '/core-extensions'); define('TESTS_PATH', FRESHRSS_PATH . '/tests'); //</Not customisable> diff --git a/tests/cli/i18n/I18nCompletionValidatorTest.php b/tests/cli/i18n/I18nCompletionValidatorTest.php new file mode 100644 index 000000000..1aa871f20 --- /dev/null +++ b/tests/cli/i18n/I18nCompletionValidatorTest.php @@ -0,0 +1,143 @@ +<?php + +require_once __DIR__ . '/../../../cli/i18n/I18nCompletionValidator.php'; +require_once __DIR__ . '/../../../cli/i18n/I18nValue.php'; + +class I18nCompletionValidatorTest extends PHPUnit\Framework\TestCase { + private $value; + + public function setUp(): void { + $this->value = $this->getMockBuilder(I18nValue::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testDisplayReport() { + $validator = new I18nCompletionValidator([], []); + + $this->assertEquals("There is no data.\n", $validator->displayReport()); + + $reflectionTotalEntries = new ReflectionProperty(I18nCompletionValidator::class, 'totalEntries'); + $reflectionTotalEntries->setAccessible(true); + $reflectionTotalEntries->setValue($validator, 100); + + $this->assertEquals("Translation is 0.0% complete.\n", $validator->displayReport()); + + $reflectionPassEntries = new ReflectionProperty(I18nCompletionValidator::class, 'passEntries'); + $reflectionPassEntries->setAccessible(true); + $reflectionPassEntries->setValue($validator, 25); + + $this->assertEquals("Translation is 25.0% complete.\n", $validator->displayReport()); + + $reflectionPassEntries->setValue($validator, 100); + + $this->assertEquals("Translation is 100.0% complete.\n", $validator->displayReport()); + + $reflectionPassEntries->setValue($validator, 200); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The number of translated strings cannot be higher than the number of strings'); + $validator->displayReport(); + } + + public function testValidateWhenNoData() { + $validator = new I18nCompletionValidator([], []); + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenKeyIsMissing() { + $validator = new I18nCompletionValidator([ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], []); + + $this->assertFalse($validator->validate()); + $this->assertEquals("Missing key file1.l1.l2.k1\nMissing key file2.l1.l2.k1\n", $validator->displayResult()); + } + + public function testValidateWhenKeyIsIgnored() { + $this->value->expects($this->exactly(2)) + ->method('isIgnore') + ->willReturn(true); + + $validator = new I18nCompletionValidator([ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], [ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ]); + + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenValueIsEqual() { + $this->value->expects($this->exactly(2)) + ->method('isIgnore') + ->willReturn(false); + $this->value->expects($this->exactly(2)) + ->method('equal') + ->willReturn(true); + + $validator = new I18nCompletionValidator([ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], [ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ]); + + $this->assertFalse($validator->validate()); + $this->assertEquals("Untranslated key file1.l1.l2.k1 - \nUntranslated key file2.l1.l2.k1 - \n", $validator->displayResult()); + } + + public function testValidateWhenValueIsDifferent() { + $this->value->expects($this->exactly(2)) + ->method('isIgnore') + ->willReturn(false); + $this->value->expects($this->exactly(2)) + ->method('equal') + ->willReturn(false); + + $validator = new I18nCompletionValidator([ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], [ + 'file1.php' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2.php' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ]); + + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } +} diff --git a/tests/cli/i18n/I18nFileTest.php b/tests/cli/i18n/I18nFileTest.php new file mode 100644 index 000000000..26566ddf1 --- /dev/null +++ b/tests/cli/i18n/I18nFileTest.php @@ -0,0 +1,38 @@ +<?php + +require_once __DIR__ . '/../../../cli/i18n/I18nFile.php'; + +class I18nFileTest extends PHPUnit\Framework\TestCase { + public function test() { + $before = $this->computeFilesHash(); + + $file = new I18nFile(); + $data = $file->load(); + $file->dump($data); + + $after = $this->computeFilesHash(); + + $this->assertEquals($before, $after); + } + + private function computeFilesHash() { + $hashes = []; + + $dirs = new DirectoryIterator(I18N_PATH); + foreach ($dirs as $dir) { + if ($dir->isDot()) { + continue; + } + $files = new DirectoryIterator($dir->getPathname()); + foreach ($files as $file) { + if (!$file->isFile()) { + continue; + } + + $hashes[$file->getPathName()] = sha1_file($file->getPathName()); + } + } + + return $hashes; + } +} diff --git a/tests/cli/i18n/I18nUsageValidatorTest.php b/tests/cli/i18n/I18nUsageValidatorTest.php new file mode 100644 index 000000000..531e77c5d --- /dev/null +++ b/tests/cli/i18n/I18nUsageValidatorTest.php @@ -0,0 +1,122 @@ +<?php + +require_once __DIR__ . '/../../../cli/i18n/I18nValue.php'; +require_once __DIR__ . '/../../../cli/i18n/I18nUsageValidator.php'; + +class I18nUsageValidatorTest extends PHPUnit\Framework\TestCase { + private $value; + + public function setUp(): void { + $this->value = $this->getMockBuilder(I18nValue::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testDisplayReport() { + $validator = new I18nUsageValidator([], []); + + $this->assertEquals("There is no data.\n", $validator->displayReport()); + + $reflectionTotalEntries = new ReflectionProperty(I18nUsageValidator::class, 'totalEntries'); + $reflectionTotalEntries->setAccessible(true); + $reflectionTotalEntries->setValue($validator, 100); + + $this->assertEquals(" 0.0% of translation keys are unused.\n", $validator->displayReport()); + + $reflectionFailedEntries = new ReflectionProperty(I18nUsageValidator::class, 'failedEntries'); + $reflectionFailedEntries->setAccessible(true); + $reflectionFailedEntries->setValue($validator, 25); + + $this->assertEquals(" 25.0% of translation keys are unused.\n", $validator->displayReport()); + + $reflectionFailedEntries->setValue($validator, 100); + + $this->assertEquals("100.0% of translation keys are unused.\n", $validator->displayReport()); + + $reflectionFailedEntries->setValue($validator, 200); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The number of unused strings cannot be higher than the number of strings'); + $validator->displayReport(); + } + + public function testValidateWhenNoData() { + $validator = new I18nUsageValidator([], []); + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenParentKeyExistsWithoutTransformation() { + $validator = new I18nUsageValidator([ + 'file1' => [ + 'file1.l1.l2._' => $this->value, + ], + 'file2' => [ + 'file2.l1.l2._' => $this->value, + ], + ], [ + 'file1.l1.l2._', + 'file2.l1.l2._', + ]); + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenParentKeyExistsWithTransformation() { + $validator = new I18nUsageValidator([ + 'file1' => [ + 'file1.l1.l2._' => $this->value, + ], + 'file2' => [ + 'file2.l1.l2._' => $this->value, + ], + ], [ + 'file1.l1.l2', + 'file2.l1.l2', + ]); + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenParentKeyDoesNotExist() { + $validator = new I18nUsageValidator([ + 'file1' => [ + 'file1.l1.l2._' => $this->value, + ], + 'file2' => [ + 'file2.l1.l2._' => $this->value, + ], + ], []); + $this->assertFalse($validator->validate()); + $this->assertEquals("Unused key file1.l1.l2._ - \nUnused key file2.l1.l2._ - \n", $validator->displayResult()); + } + + public function testValidateWhenChildKeyExists() { + $validator = new I18nUsageValidator([ + 'file1' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], [ + 'file1.l1.l2.k1', + 'file2.l1.l2.k1', + ]); + $this->assertTrue($validator->validate()); + $this->assertEquals('', $validator->displayResult()); + } + + public function testValidateWhenChildKeyDoesNotExist() { + $validator = new I18nUsageValidator([ + 'file1' => [ + 'file1.l1.l2.k1' => $this->value, + ], + 'file2' => [ + 'file2.l1.l2.k1' => $this->value, + ], + ], []); + $this->assertFalse($validator->validate()); + $this->assertEquals("Unused key file1.l1.l2.k1 - \nUnused key file2.l1.l2.k1 - \n", $validator->displayResult()); + } +} diff --git a/tests/cli/i18n/I18nValueTest.php b/tests/cli/i18n/I18nValueTest.php new file mode 100644 index 000000000..cabc8e072 --- /dev/null +++ b/tests/cli/i18n/I18nValueTest.php @@ -0,0 +1,83 @@ +<?php + +require_once __DIR__ . '/../../../cli/i18n/I18nValue.php'; + +class I18nValueTest extends PHPUnit\Framework\TestCase { + public function testConstructorWithoutState() { + $value = new I18nValue('some value'); + $this->assertEquals('some value', $value->getValue()); + $this->assertFalse($value->isIgnore()); + $this->assertFalse($value->isTodo()); + } + + public function testConstructorWithUnknownState() { + $value = new I18nValue('some value -> unknown'); + $this->assertEquals('some value', $value->getValue()); + $this->assertFalse($value->isIgnore()); + $this->assertFalse($value->isTodo()); + } + + public function testConstructorWithTodoState() { + $value = new I18nValue('some value -> todo'); + $this->assertEquals('some value', $value->getValue()); + $this->assertFalse($value->isIgnore()); + $this->assertTrue($value->isTodo()); + } + + public function testConstructorWithIgnoreState() { + $value = new I18nValue('some value -> ignore'); + $this->assertEquals('some value', $value->getValue()); + $this->assertTrue($value->isIgnore()); + $this->assertFalse($value->isTodo()); + } + + public function testClone() { + $value = new I18nValue('some value'); + $clonedValue = clone $value; + $this->assertEquals('some value', $value->getValue()); + $this->assertEquals('some value', $clonedValue->getValue()); + $this->assertFalse($value->isIgnore()); + $this->assertFalse($clonedValue->isIgnore()); + $this->assertFalse($value->isTodo()); + $this->assertTrue($clonedValue->isTodo()); + } + + public function testEqualWhenValueIsIdentical() { + $value = new I18nValue('some value'); + $clonedValue = clone $value; + $this->assertTrue($value->equal($clonedValue)); + $this->assertTrue($clonedValue->equal($value)); + } + + public function testEqualWhenValueIsDifferent() { + $value = new I18nValue('some value'); + $otherValue = new I18nValue('some other value'); + $this->assertFalse($value->equal($otherValue)); + $this->assertFalse($otherValue->equal($value)); + } + + public function testStates() { + $reflectionProperty = new ReflectionProperty(I18nValue::class, 'state'); + $reflectionProperty->setAccessible(true); + + $value = new I18nValue('some value'); + $this->assertNull($reflectionProperty->getValue($value)); + $value->markAsDirty(); + $this->assertEquals('dirty', $reflectionProperty->getValue($value)); + $value->unmarkAsIgnore(); + $this->assertEquals('dirty', $reflectionProperty->getValue($value)); + $value->markAsIgnore(); + $this->assertEquals('ignore', $reflectionProperty->getValue($value)); + $value->unmarkAsIgnore(); + $this->assertNull($reflectionProperty->getValue($value)); + $value->markAsTodo(); + $this->assertEquals('todo', $reflectionProperty->getValue($value)); + } + + public function testToString() { + $value = new I18nValue('some value'); + $this->assertEquals('some value', $value->__toString()); + $value->markAsTodo(); + $this->assertEquals('some value -> todo', $value->__toString()); + } +} |
