aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/i18n/I18nCompletionValidator.php8
-rw-r--r--cli/i18n/I18nFile.php11
-rw-r--r--cli/i18n/I18nUsageValidator.php6
-rwxr-xr-xcli/manipulate.translation.php2
-rw-r--r--constants.php1
-rw-r--r--tests/cli/i18n/I18nCompletionValidatorTest.php143
-rw-r--r--tests/cli/i18n/I18nFileTest.php38
-rw-r--r--tests/cli/i18n/I18nUsageValidatorTest.php122
-rw-r--r--tests/cli/i18n/I18nValueTest.php83
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());
+ }
+}