19declare(strict_types=1);
28use PHPUnit\Framework\TestCase;
39 private const string MODULE =
'ptest';
46 protected function setUp(): void
52 if (!defined(
'ILIAS_ABSOLUTE_PATH')) {
53 define(
'ILIAS_ABSOLUTE_PATH', realpath(__DIR__ .
'/../../../../../'));
56 $this->fixture_directory = __DIR__ .
'/tmp-plural-sync-fixtures-' . bin2hex(random_bytes(4));
57 mkdir($this->fixture_directory, 0775,
true);
58 $this->client_data_dir = sys_get_temp_dir() .
'/ilias_mlfs_plural_test_' . bin2hex(random_bytes(4));
59 mkdir($this->client_data_dir, 0775,
true);
63 'components/ILIAS/Language/tests/ComponentTranslation/' . basename($this->fixture_directory) .
'/'
76 private function shippedPo(
string $lang_key =
'de'): string
78 return $this->fixture_directory .
'/' . self::MODULE .
'_' . $lang_key .
'.po';
95 private function seedShipped(array $entries,
string $header =
'nplurals=2; plural=(n != 1);'): void
98 $catalog->setHeader(
'Content-Type',
'text/plain; charset=UTF-8');
99 $catalog->setHeader(
'Plural-Forms', $header);
100 foreach ($entries as $identifier => $value) {
102 if (is_array($value)) {
103 $entry->setPlural($identifier .
'_plural', $value);
105 $entry->translate($value);
107 $catalog->add($entry);
116 private function sync(array $entries,
bool $refresh =
false): void
118 MigratedLanguageFileSync::sync(
124 $this->client_data_dir,
131 return $this->
overlayPo()->find(
null, $identifier);
139 MigratedLanguageFileSync::sync(
144 [
'item [0]' =>
'Eintrag NEU',
'item [1]' =>
'Einträge NEU'],
145 $this->client_data_dir,
160 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
163 'item [0]' =>
'2021-05-06 07:08:09',
164 'item [1]' =>
'2020-01-02 03:04:05',
165 'itemx [0]' =>
'2023-01-01 00:00:00',
168 $this->assertSame(
'2021-05-06T07:08:09Z', LocalChangeComments::getLocalChange($this->
overlayEntry(
'item')));
177 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
181 $this->assertSame(
'2019-02-03T04:05:06Z', LocalChangeComments::getLocalChange($this->
overlayEntry(
'item')));
186 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
187 $before = new \DateTimeImmutable(
'now',
new \DateTimeZone(
'UTC'));
190 $this->assertSame(
'2021-05-06T07:08:09Z', LocalChangeComments::getLocalChange($this->
overlayEntry(
'item')));
194 $change = LocalChangeComments::getLocalChange($this->
overlayEntry(
'item'));
195 $this->assertNotNull($change);
196 $this->assertGreaterThanOrEqual($before->format(
'Y-m-d\TH:i:s\Z'), $change);
202 mkdir($this->client_data_dir, 0775,
true);
212 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
214 $this->
sync([
'item [0]' =>
'Eintrag',
'item [1]' =>
'Einträge NEU']);
217 $this->assertNotNull($entry,
'the plural message must be in the overlay');
218 $this->assertTrue($entry->isPlural());
219 $this->assertSame([
'Eintrag',
'Einträge NEU'], $entry->getPluralTranslations());
220 $this->assertSame(
'Eintrag', LocalChangeComments::getOriginal($entry, 0));
221 $this->assertSame(
'Einträge', LocalChangeComments::getOriginal($entry, 1));
230 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge'],
'plain' =>
'X']);
231 $this->
sync([
'item [0]' =>
'Eintrag',
'item [1]' =>
'GEÄNDERT',
'plain' =>
'Anders']);
232 $this->assertNotNull($this->
overlayEntry(
'item'),
'precondition: the plural message is in the overlay');
234 $this->
sync([
'item [0]' =>
'Eintrag',
'item [1]' =>
'Einträge',
'plain' =>
'Anders']);
238 $this->assertNotNull($plain,
'the unrelated local change must survive');
239 $this->assertSame(
'Anders', $plain->getTranslation());
249 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
250 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'FORM1-LOKAL']);
252 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'']);
255 $this->assertNotNull($entry);
256 $this->assertSame([
'FORM0-LOKAL',
'Einträge'], $entry->getPluralTranslations());
267 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
268 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'Einträge']);
270 $this->assertNotNull($entry);
271 $this->assertSame(
'Einträge', LocalChangeComments::getOriginal($entry, 1),
'precondition');
274 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge NEU']]);
275 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'Einträge NEU'], refresh:
true);
278 $this->assertNotNull($entry);
279 $this->assertSame([
'FORM0-LOKAL',
'Einträge NEU'], $entry->getPluralTranslations());
280 $this->assertSame(
'Eintrag', LocalChangeComments::getOriginal($entry, 0));
281 $this->assertSame(
'Einträge NEU', LocalChangeComments::getOriginal($entry, 1));
291 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge']]);
292 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'Einträge']);
294 $this->
seedShipped([
'item' => [
'Eintrag',
'Einträge NEU']]);
297 $this->
sync([
'item [0]' =>
'FORM0-LOKAL',
'item [1]' =>
'Einträge NEU'], refresh:
false);
300 $this->assertNotNull($entry);
301 $this->assertSame(
'Einträge', LocalChangeComments::getOriginal($entry, 1),
'original stays the OLD shipped value');
trait RestoresProcessState
Whatever a test does to the process (locale, LANGUAGE/LC_*, LOCPATH, the static state of the runtime ...
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
One message of a TranslationCatalog (a msgctxt/msgid/msgstr block of a .po file).
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
MigratedLanguageFileSync::sync() for a plural message: what the overlay .po holds when only some form...
testAnEmptyFormValueResetsOnlyThatFormToShippedKeepingTheOtherFormsLocalChange()
Submitting an empty value for one form resets exactly that form to its shipped value - even while the...
testWithoutRefreshTheExistingOriginalIsLeftUntouchedWhenTheShippedValueChanges()
Without $refresh_original_from_shipped (an ordinary form save), the "original" of an existing overlay...
sync(array $entries, bool $refresh=false)
overlayEntry(string $identifier)
LanguageFileDirectoryManager $manager
testRefreshOriginalFromShippedUpdatesOriginalOfEveryFormToTheNewShippedValue()
$refresh_original_from_shipped=true (a Setup update) updates every form's "original" to the new shipp...
testADateUnderThePluralIdentifierWinsOverTheFormDates()
A date under the identifier itself wins over the dates of the forms; an unreadable date of a form is ...
string $fixture_directory
seedShipped(array $entries, string $header='nplurals=2;plural=(n !=1);')
syncWithLocalChangeDates(array $dates)
shippedPo(string $lang_key='de')
testAnUnreadableFormDateIsIgnoredAndWithoutAnyReadableDateTheEntryGetsTheCurrentTime()
testAllFormsBackToShippedRemovesTheEntryButKeepsOtherLocalChanges()
Once every form is back to its shipped value, the whole entry disappears from the overlay - a still-l...
testOnlyOneFormChangedWritesTheWholeEntryWithAnOriginalPerForm()
Only one of two forms is locally changed: the overlay still carries the whole plural entry (both form...
overlayBase(string $lang_key='de')
testANewPluralEntryTakesTheLatestLocalChangeDateOfItsForms()
A new plural overlay entry whose dates are known only per form (rows "item [0]", "item [1]" of lng_da...
LanguageFileDirectory $directory
static directory(string $prefix, string $path, bool $local=false)
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static readPo(string $file)
static overlayBase(string $client_data_dir, LanguageFileDirectory $directory, string $lang_key)
The overlay of $directory's module for $lang_key without the .po extension.
static resetRuntime(?string $artifact_directory=null)
Forgets the static state of the runtime read path and points build and runtime at $artifact_directory...
A directory of language files with a path and a prefix for them.