Files
FreshRSS/tests/cli/i18n/I18nCompletionValidatorTest.php
Christian Weiske 1acc646222 Show time since when a feed has problems + new timeago() method and i18n plurals (#8670)
Closes https://github.com/FreshRSS/FreshRSS/issues/8508

Changes proposed in this pull request:

- Use an integer for `Feed::error` everywhere (follow up to #8646)
- Extract `Entry::machineReadableDate()` into function for use in HTML templates
- Add `timeago()` function that converts a unix timestamp into a "4 weeks ago" string
- Show the last successful feed update, and the last erroneous update

How to test the feature manually:

1. Update a feed
2. Modify the feed URL in the database and set it to a non-existing URL
3. Update the feed again
4. Open the "Manage feed" and see the expanded error message:

>  Blast! This feed has encountered a problem. If this situation persists, please verify that it is still reachable.
> Last successful update 3 hours ago, last erroneous update 1 hour ago. 

You can hover the relative dates to see the timestamp.

* Make Feed::error an int everywhere

Related: https://github.com/FreshRSS/FreshRSS/pull/8646

* Extract timestamptomachinedate()

.. for later usage in the feed error time display.

* Show time since when a feed has problems

We add our own "timeago" function that converts a unix timestamp
into a "4 weeks ago" string.

Resolves: https://github.com/FreshRSS/FreshRSS/issues/8508

* Add new translation keys

* i18n fr, en-US

* Minor XHTML preference

* Slightly shorter rewrite, also hopefully easier to read

* Rewrite to allow (simple) plural
I also moved some functions around for hopefully a more generic and better structure.
I made some changes for the sake of speed (e.g. second-based logic instead of datetime intervals).
Note: I used automatic translation as I was worried it would be too complicated to explain to translators... I proofread the few languages I have some familiarity with.

* Add reference to CLDR

* Slightly more compact syntax

* Always show last update, fix case of unknown error date

* Remove forgotten span

* No need for multi-lines anymore

* Fix error date thresshold

* plurals forms

* Extract gettext formula conversion script to cli

* Simplify a bit

* Escort excess parentheses to the door

* Simplify

* Avoid being too clever in localization

* Fix German

* Fix plural TODO parsing

* Ignore en-US translation

* make fix-all

* git update-index --chmod=+x cli/compile.plurals.php

* Heredoc indent PHP 7.3+

* compileAll: Continue on error

* PHP strict comparisons

* Light logical simplification

* Cache plural_message_families

* Avoid case of empty value

* A bit of documentation

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Co-authored-by: Frans de Jonge <frans@clevercast.com>
Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>
2026-04-07 22:56:02 +02:00

180 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__, 3) . '/cli/i18n/I18nCompletionValidator.php';
require_once dirname(__DIR__, 3) . '/cli/i18n/I18nValue.php';
final class I18nCompletionValidatorTest extends \PHPUnit\Framework\TestCase {
/** @var I18nValue&PHPUnit\Framework\MockObject\MockObject */
private $value;
#[\Override]
public function setUp(): void {
$this->value = $this->getMockBuilder(I18nValue::class)
->disableOriginalConstructor()
->getMock();
}
public function testDisplayReport(): void {
$validator = new I18nCompletionValidator([], []);
self::assertSame("There is no data.\n", $validator->displayReport());
$reflectionTotalEntries = new ReflectionProperty(I18nCompletionValidator::class, 'totalEntries');
$reflectionTotalEntries->setValue($validator, 100);
self::assertSame("Translation is 0.0% complete.\n", $validator->displayReport());
$reflectionPassEntries = new ReflectionProperty(I18nCompletionValidator::class, 'passEntries');
$reflectionPassEntries->setValue($validator, 25);
self::assertSame("Translation is 25.0% complete.\n", $validator->displayReport());
$reflectionPassEntries->setValue($validator, 100);
self::assertSame("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 static function testValidateWhenNoData(): void {
$validator = new I18nCompletionValidator([], []);
self::assertTrue($validator->validate());
self::assertSame('', $validator->displayResult());
}
public function testValidateWhenKeyIsMissing(): void {
$validator = new I18nCompletionValidator([
'file1.php' => [
'file1.l1.l2.k1' => $this->value,
],
'file2.php' => [
'file2.l1.l2.k1' => $this->value,
],
], []);
self::assertFalse($validator->validate());
self::assertSame("Missing key file1.l1.l2.k1\nMissing key file2.l1.l2.k1\n", $validator->displayResult());
}
public function testValidateWhenKeyIsIgnored(): void {
$this->value->expects(self::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,
],
]);
self::assertTrue($validator->validate());
self::assertSame('', $validator->displayResult());
}
public function testValidateWhenValueIsEqual(): void {
$this->value->expects(self::exactly(2))
->method('isIgnore')
->willReturn(false);
$this->value->expects(self::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,
],
]);
self::assertFalse($validator->validate());
self::assertSame("Untranslated key file1.l1.l2.k1 - \nUntranslated key file2.l1.l2.k1 - \n", $validator->displayResult());
}
public function testValidateWhenValueIsDifferent(): void {
$this->value->expects(self::exactly(2))
->method('isIgnore')
->willReturn(false);
$this->value->expects(self::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,
],
]);
self::assertTrue($validator->validate());
self::assertSame('', $validator->displayResult());
}
public function testValidateFlagsHigherPluralVariantWhenEqualToEnglishPlural(): void {
$validator = new I18nCompletionValidator([
'gen.php' => [
'gen.interval.day.0' => new I18nValue('%d day ago'),
'gen.interval.day.1' => new I18nValue('%d days ago'),
],
], [
'gen.php' => [
'gen.interval.day.0' => new I18nValue('%d dzień temu'),
'gen.interval.day.1' => new I18nValue('%d dni temu'),
'gen.interval.day.2' => new I18nValue('%d days ago'),
],
]);
self::assertFalse($validator->validate());
self::assertSame("Untranslated key gen.interval.day.2 - %d days ago\n", $validator->displayResult());
self::assertSame("Translation is 66.7% complete.\n", $validator->displayReport());
}
public function testValidateSkipsEnglishPluralVariantsMissingFromOneFormLanguage(): void {
$validator = new I18nCompletionValidator([
'gen.php' => [
'gen.interval.day.0' => new I18nValue('%d day ago'),
'gen.interval.day.1' => new I18nValue('%d days ago'),
],
], [
'gen.php' => [
'gen.interval.day.0' => new I18nValue('%d hari yang lalu'),
],
]);
self::assertTrue($validator->validate());
self::assertSame('', $validator->displayResult());
self::assertSame("Translation is 100.0% complete.\n", $validator->displayReport());
}
}