19declare(strict_types=1);
25use InvalidArgumentException;
26use PHPUnit\Framework\Attributes\DataProvider;
27use PHPUnit\Framework\TestCase;
45 if ($this->directory !==
null) {
52 if ($this->directory ===
null) {
53 $this->directory = sys_get_temp_dir() .
'/ilias_translation_catalog_' . bin2hex(random_bytes(4));
54 mkdir($this->directory, 0775);
68 $catalog->add($entry);
76 $entry->translate($translation);
81 #[DataProvider('values')]
84 $read =
self::roundTrip(self::singleEntryCatalog(self::entry(
'mod',
'id', $value)))->find(
'mod',
'id');
86 $this->assertNotNull($read);
87 $this->assertSame($value, $read->getTranslation());
90 public static function values(): array
95 'zero with a line break' => [
"0\n"],
96 'double zero' => [
'00'],
98 'double quote' => [
'Klick "hier"'],
99 'backslash' => [
'C:\\temp\\'],
100 'backslash before quote' => [
'a\\"b'],
101 'only a backslash' => [
'\\'],
102 'tab and carriage return' => [
"a\tb\rc"],
103 'multiline' => [
"Zeile 1\nZeile 2\nZeile 3"],
104 'trailing newline' => [
"Zeile\n"],
105 'only newlines' => [
"\n\n"],
106 'leading and trailing spaces' => [
' Hallo '],
107 'unicode' => [
'Ünïcödé – 日本語 🙂'],
108 'placeholder and html' => [
'<a href="%1$s">%s</a>'],
109 'literal escape sequence text' => [
'\\n is not a newline'],
110 'hash at start' => [
'# not a comment'],
111 'msgid-like text' => [
"msgid \"x\"\nmsgstr \"y\""],
123 $this->assertStringContainsString(
"msgctxt \"mod\"\nmsgid \"zero\"\nmsgstr \"0\"\n", $po);
124 $this->assertStringNotContainsString(
"\0", $po);
136 $first_po = $catalog->toPoString();
137 $first_mo = $catalog->toMoString();
139 $this->assertSame(
'0', $entry->getTranslation());
140 $this->assertSame(
'0', $catalog->find(
'mod',
'zero')?->getTranslation());
141 $this->assertSame($first_po, $catalog->toPoString());
142 $this->assertSame($first_mo, $catalog->toMoString());
149 $this->assertSame(
'v', self::roundTrip($catalog)->find(
"ctx \"q\"",
"id\\with\nnewline")?->getTranslation());
159 $this->assertSame(
'null', self::roundTrip($catalog)->find(
'mod',
'0')?->getTranslation());
167 #[DataProvider('unrepresentableMessages')]
172 $this->expectException(RuntimeException::class);
173 $mo ? $catalog->toMoString() : $catalog->toPoString();
179 'empty context' =>
"msgctxt \"\"\nmsgid \"a\"\nmsgstr \"b\"\n",
180 'context zero' =>
"msgctxt \"0\"\nmsgid \"a\"\nmsgstr \"b\"\n",
181 'empty plural id' =>
"msgid \"a\"\nmsgid_plural \"\"\nmsgstr[0] \"b\"\nmsgstr[1] \"c\"\n",
182 'plural id zero' =>
"msgid \"a\"\nmsgid_plural \"0\"\nmsgstr[0] \"b\"\nmsgstr[1] \"c\"\n",
185 foreach ($cases as $name => $po) {
186 $data[$name .
', .po'] = [$po,
false];
187 $data[$name .
', .mo'] = [$po,
true];
197 $this->expectException(RuntimeException::class);
198 $catalog->toPoString();
208 $entry->addTranslatorComment(
'original: Hallo ');
209 $entry->addTranslatorComment(
'');
210 $entry->addTranslatorComment(
' leading space');
211 $entry->addTranslatorComment(
'trailing space ');
212 $entry->addTranslatorComment(
'.looks like an extracted marker');
213 $entry->addExtractedComment(
' extracted ');
214 $entry->addFlag(
'php-format');
215 $entry->addFlag(
'fuzzy');
217 $read =
self::roundTrip(self::singleEntryCatalog($entry))->find(
'mod',
'id');
219 $this->assertNotNull($read);
221 [
'original: Hallo ',
'',
' leading space',
'trailing space ',
'.looks like an extracted marker'],
222 $read->getTranslatorComments()
224 $this->assertSame([
' extracted '], $read->getExtractedComments());
225 $this->assertSame([
'fuzzy',
'php-format'], $read->getFlags());
226 $this->assertTrue($read->hasFlag(
'fuzzy'));
236 $entry->addTranslatorComment(
'');
237 $entry->addTranslatorComment(
' note ');
238 $entry->addExtractedComment(
'ext');
239 $entry->addFlag(
'php-format');
240 $entry->addFlag(
'fuzzy');
242 $this->assertStringContainsString(
243 "# \n# note \n#. ext\n#, fuzzy,php-format\nmsgctxt \"mod\"\nmsgid \"id\"\nmsgstr \"v\"\n",
244 self::singleEntryCatalog($entry)->toPoString()
252 $this->assertNotNull($entry);
253 $this->assertSame([
'note',
''], $entry->getTranslatorComments());
254 $this->assertSame([
'ext'], $entry->getExtractedComments());
264 $entry->addTranslatorComment(
"eins\nzwei\r\ndrei\rvier");
265 $entry->addExtractedComment(
"a\nb");
267 $read =
self::roundTrip(self::singleEntryCatalog($entry))->find(
'mod',
'id');
269 $this->assertSame([
'eins zwei drei vier'], $entry->getTranslatorComments());
270 $this->assertNotNull($read);
271 $this->assertSame([
'eins zwei drei vier'], $read->getTranslatorComments());
272 $this->assertSame([
'a b'], $read->getExtractedComments());
281 $po =
"msgid \"\"\nmsgstr \"\"\n\"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 ? 1 : 2);\\n\"\n\n"
282 .
"msgctxt \"mod\"\nmsgid \"item\"\nmsgid_plural \"items\"\nmsgstr[0] \"%d Eintrag\"\nmsgstr[1] \"%d Einträge\"\n"
283 .
"msgstr[2] \"%d Einträgen\"\n";
287 $this->assertSame($po, $catalog->toPoString());
288 $entry = $catalog->find(
'mod',
'item');
289 $this->assertNotNull($entry);
290 $this->assertSame(
'%d Eintrag', $entry->getTranslation());
292 $entry->translate(
'%d Stück');
293 $entry->addTranslatorComment(
'note');
296 str_replace(
"msgctxt \"mod\"\nmsgid \"item\"",
"# note\nmsgctxt \"mod\"\nmsgid \"item\"", str_replace(
'"%d Eintrag"',
'"%d Stück"', $po)),
297 $catalog->toPoString()
305 #[DataProvider('pluralFormCounts')]
308 $po =
"msgid \"\"\nmsgstr \"\"\n" . $header .
"\n"
309 .
"msgid \"item\"\nmsgid_plural \"items\"\n" . $forms;
312 "msgid \"\"\nmsgstr \"\"\n" . $header .
"\nmsgid \"item\"\nmsgid_plural \"items\"\n" . $expected,
319 $two =
"msgstr[0] \"a\"\nmsgstr[1] \"b\"\n";
320 $three = $two .
"msgstr[2] \"c\"\n";
323 'as many as declared' => [
"\"Plural-Forms: nplurals=3; plural=(n != 1);\\n\"\n", $three, $three],
324 'fewer than declared' => [
"\"Plural-Forms: nplurals=3; plural=(n != 1);\\n\"\n", $two, $two .
"msgstr[2] \"\"\n"],
325 'more than declared' => [
"\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n", $three, $two],
326 'no header' => [
'', $two, $two],
332 $po =
"msgid \"\"\nmsgstr \"\"\n\nmsgid \"a\"\nmsgid_plural \"as\"\nmsgstr[0] \"0\"\nmsgstr[1] \"0\"\n";
340 $catalog->setHeader(
'X-Domain',
'tos');
341 $catalog->setHeader(
'Plural-Forms',
'nplurals=2; plural=(n != 1);');
342 $catalog->setHeader(
'Content-Type',
'text/plain; charset=UTF-8');
343 $catalog->setHeader(
'Language',
'de');
346 'Content-Type' =>
'text/plain; charset=UTF-8',
348 'Plural-Forms' =>
'nplurals=2; plural=(n != 1);',
351 $this->assertSame($expected, $catalog->getHeaders());
353 "msgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Language: de\\n\"\n"
354 .
"\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Domain: tos\\n\"\n",
355 $catalog->toPoString()
358 $this->assertSame($expected, $read->getHeaders());
359 $this->assertSame([], $read->getEntries(),
'the header is not an entry');
365 $catalog->setHeader(
'Language',
'fr');
366 $catalog->setHeader(
'Language',
" \t de ");
368 $this->assertSame(
'de', $catalog->getHeader(
'Language'));
369 $this->assertSame([
'Language' =>
'de'], $catalog->getHeaders());
370 $this->assertNull($catalog->getHeader(
'X-Domain'));
377 #[DataProvider('headerValuesWithALineBreak')]
381 $catalog->setHeader(
'X-Note',
'bisher');
384 $catalog->setHeader(
'X-Note', $value);
385 $this->fail(
'Expected an InvalidArgumentException');
386 }
catch (InvalidArgumentException
$e) {
387 $this->assertStringContainsString(
'X-Note',
$e->getMessage());
390 $this->assertSame([
'X-Note' =>
'bisher'], $catalog->getHeaders());
396 'LF inside' => [
"a\nb"],
397 'CR inside' => [
"a\rb"],
398 'CRLF inside' => [
"a\r\nb"],
399 'trailing LF' => [
"a\n"],
400 'leading CR' => [
"\ra"],
401 'only CRLF' => [
"\r\n"],
412 $catalog->setHeader(
'X-Note',
'a "quoted" C:\\pfad\\');
413 $catalog->setHeader(
'X-Zero',
'0');
415 $po = $catalog->toPoString();
417 $this->assertStringContainsString(
"\"X-Note: a \\\"quoted\\\" C:\\\\pfad\\\\\\n\"\n", $po);
419 [
'X-Note' =>
'a "quoted" C:\\pfad\\',
'X-Zero' =>
'0'],
428 #[DataProvider('headersWithALineBreakAfterLoading')]
431 $this->expectException(RuntimeException::class);
432 $this->expectExceptionMessage(
'The value of header "X-A" contains a line break.');
439 'CR inside' => [
"msgid \"\"\nmsgstr \"\"\n\"X-A: a\\rb\\n\"\n"],
440 'CR inside, last header without a trailing \\n' => [
"msgid \"\"\nmsgstr \"\"\n\"X-A: a\\rb\"\n"],
441 'CR inside, after a valid header' => [
442 "msgid \"\"\nmsgstr \"\"\n\"Language: de\\n\"\n\"X-A: a\\r\\rb\\n\"\n\nmsgid \"id\"\nmsgstr \"v\"\n",
450 file_put_contents($file,
"msgid \"\"\nmsgstr \"\"\n\"X-A: a\\rb\\n\"\n");
452 $this->expectException(RuntimeException::class);
453 $this->expectExceptionMessageMatches(
'/^Invalid PO file ".*header_de\.po": The value of header "X-A" contains a line break\.$/');
463 #[DataProvider('headersLosingTheirLineBreakWhenLoaded')]
468 $this->assertSame([
'X-A' => $expected], $catalog->getHeaders());
469 $this->assertSame(
"msgid \"\"\nmsgstr \"\"\n\"X-A: " . $expected .
"\\n\"\n", $catalog->toPoString());
475 'trailing CR' => [
'"X-A: tail\\r\\n"',
'tail'],
476 'leading CR' => [
'"X-A: \\rhead\\n"',
'head'],
477 'escaped LF inside' => [
'"X-A: a\\nb\\n"',
'ab'],
478 'no line break' => [
'"X-A: v\\n"',
'v'],
485 $catalog->setHeader(
'Language',
'de');
486 $catalog->add(self::entry(
'tos',
'tos_agreement',
"Zeile 1\nZeile 2"));
487 $catalog->add(self::entry(
'tos',
'tos_empty',
''));
490 "msgid \"\"\nmsgstr \"\"\n\"Language: de\\n\"\n\n"
491 .
"msgctxt \"tos\"\nmsgid \"tos_agreement\"\nmsgstr \"\"\n\"Zeile 1\\n\"\n\"Zeile 2\"\n\n"
492 .
"msgctxt \"tos\"\nmsgid \"tos_empty\"\nmsgstr \"\"\n",
493 $catalog->toPoString()
504 $files = glob(self::TOS_LANG_DIRECTORY .
'/*.{po,pot}', GLOB_BRACE) ?: [];
505 $this->assertNotSame([],
$files,
'the shipped tos files were not found');
507 foreach (
$files as $file) {
509 $this->assertSame(file_get_contents($file), $catalog->toPoString(), basename($file));
512 foreach ($catalog->getEntries() as $entry) {
514 $this->assertNull($entry->getContext(), basename($file));
515 if ($entry->getTranslation() !==
'') {
516 $expected[$entry->getId()] = $entry->getTranslation();
520 ksort($expected, SORT_STRING);
521 ksort($actual, SORT_STRING);
522 $this->assertSame($expected, $actual, basename($file));
528 $po =
"\xEF\xBB\xBFmsgid \"\"\r\nmsgstr \"\"\r\n\"Language: de\\n\"\r\n\r\n"
529 .
"msgctxt \"mod\"\r\nmsgid \"id\"\r\nmsgstr \"\"\r\n\"eins \"\r\n\"zwei\"\r\n";
533 $this->assertSame(
'de', $catalog->getHeader(
'Language'));
534 $this->assertSame(
'eins zwei', $catalog->find(
'mod',
'id')?->getTranslation());
535 $this->assertStringStartsWith(
"msgid \"\"\nmsgstr \"\"\n", $catalog->toPoString(),
'no BOM, LF only');
543 $this->expectException(RuntimeException::class);
554 $this->expectException(RuntimeException::class);
555 $this->expectExceptionMessage(
'not valid UTF-8');
567 $this->expectException(RuntimeException::class);
568 $this->expectExceptionMessage(
'Unsupported charset "ISO-8859-1"');
570 "msgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=ISO-8859-1\\n\"\n\n"
571 .
"msgid \"id\"\nmsgstr \"value\"\n"
579 $this->assertSame(
"AB\x07\x08\x0c\x0b\t\r\n", $catalog->find(
null,
'id')?->getTranslation());
588 "msgid \"id\"\nmsgstr \"v\"\n\n#~ msgctxt \"mod\"\n#~ msgid \"gone\"\n#~ msgstr \"weg\"\n"
591 $this->assertCount(1, $catalog->getEntries());
592 $this->assertNull($catalog->find(
'mod',
'gone'));
593 $this->assertNull($catalog->find(
null,
'gone'));
594 $this->assertStringNotContainsString(
'gone', $catalog->toPoString());
602 $po =
"msgid \"\"\nmsgstr \"\"\n\n#: file.php:12\n#: other.php\n#, fuzzy\n#| msgid \"old\"\nmsgid \"id\"\nmsgstr \"v\"\n";
606 $this->assertSame([], $catalog->find(
null,
'id')?->getTranslatorComments());
607 $this->assertSame($po, $catalog->toPoString());
613 "msgctxt \"a\"\nmsgid \"id\"\nmsgstr \"A\"\n\nmsgctxt \"b\"\nmsgid \"id\"\nmsgstr \"B\"\n\nmsgid \"id\"\nmsgstr \"ohne\"\n"
616 $this->assertSame(
'A', $catalog->find(
'a',
'id')?->getTranslation());
617 $this->assertSame(
'B', $catalog->find(
'b',
'id')?->getTranslation());
618 $this->assertSame(
'ohne', $catalog->find(
null,
'id')?->getTranslation());
619 $this->assertSame([
'A',
'B',
'ohne'], array_map(
621 $catalog->getEntries()
628 #[DataProvider('validButEmptyPo')]
633 $this->assertCount($expected_entries, $catalog->getEntries());
634 $this->assertSame([], $catalog->getHeaders());
641 'only a BOM' => [
"\xEF\xBB\xBF", 0],
642 'only blank lines' => [
"\n\r\n\n", 0],
643 'only a comment' => [
"# just a comment\n", 0],
644 'no header' => [
"msgid \"a\"\nmsgstr \"b\"\n", 1],
645 'empty header' => [
"msgid \"\"\nmsgstr \"\"\n", 0],
653 #[DataProvider('brokenPo')]
656 $this->expectException(RuntimeException::class);
663 $mo_catalog->add(self::entry(
'mod',
'id',
'v'));
666 'unterminated string' => [
"msgid \"abc\nmsgstr \"\"\n"],
667 'escaped closing quote' => [
"msgid \"abc\\\"\nmsgstr \"\"\n"],
668 'unquoted value' => [
"msgid abc\nmsgstr \"\"\n"],
669 'unknown keyword' => [
"msgfoo \"abc\"\n"],
670 'keyword without value' => [
"msgid\n"],
671 'continuation without keyword' => [
"\"dangling\"\n"],
672 'text after the closing quote' => [
"msgid \"a\" trailing\nmsgstr \"\"\n"],
673 'invalid escape sequence' => [
"msgid \"a\\q\"\nmsgstr \"\"\n"],
674 'octal escape above 127' => [
"msgid \"a\"\nmsgstr \"\\303\\244\"\n"],
675 'missing msgstr before the next message' => [
"msgid \"a\"\n\nmsgid \"b\"\nmsgstr \"B\"\n"],
676 'missing msgstr at the end' => [
"msgid \"a\"\nmsgstr \"A\"\n\nmsgid \"b\"\n"],
677 'msgstr without msgid' => [
"msgstr \"A\"\n"],
678 'duplicate message' => [
"msgid \"a\"\nmsgstr \"first\"\n\nmsgid \"a\"\nmsgstr \"second\"\n"],
679 'duplicate message with context' => [
"msgctxt \"m\"\nmsgid \"a\"\nmsgstr \"1\"\n\nmsgctxt \"m\"\nmsgid \"a\"\nmsgstr \"2\"\n"],
680 'gap in the plural forms' => [
"msgid \"a\"\nmsgid_plural \"as\"\nmsgstr[0] \"x\"\nmsgstr[2] \"z\"\n"],
681 'plural forms not starting at 0' => [
"msgid \"a\"\nmsgid_plural \"as\"\nmsgstr[1] \"y\"\n"],
682 'binary data' => [
"\x00\x01\x02\xff\xfe binary"],
683 'a .mo file' => [$mo_catalog->toMoString()],
684 'truncated inside a string' => [
"msgctxt \"mod\"\nmsgid \"id\"\nmsgstr \"Hal"],
685 'truncated inside a keyword' => [
"msgctxt \"mod\"\nmsgid \"id\"\nmsg"],
686 'truncated after msgctxt' => [
"msgctxt \"mod\"\n"],
692 $this->expectException(RuntimeException::class);
693 $this->expectExceptionMessageMatches(
'/line 3\b/');
703 $handler =
static fn():
bool =>
false;
709 $this->fail(
'Expected a RuntimeException');
710 }
catch (RuntimeException) {
713 $current = set_error_handler(
null);
714 restore_error_handler();
715 $this->assertSame(
$handler, $current);
717 restore_error_handler();
724 file_put_contents($file,
"msgid \"\"\nmsgstr \"\"\n\"Language: de\\n\"\n\nmsgctxt \"mod\"\nmsgid \"id\"\nmsgstr \"v\"\n");
728 $this->assertSame(
'de', $catalog->getHeader(
'Language'));
729 $this->assertSame(
'v', $catalog->find(
'mod',
'id')?->getTranslation());
735 file_put_contents($file,
"msgid \"a\"\nmsgstr \"A\"\nbroken\n");
737 $this->expectException(RuntimeException::class);
738 $this->expectExceptionMessageMatches(
'/^Invalid PO file ".*broken_de\.po": .*line 3\b/');
744 $this->expectException(RuntimeException::class);
745 $this->expectExceptionMessageMatches(
'/Could not read PO file/');
751 $this->expectException(RuntimeException::class);
752 $this->expectExceptionMessageMatches(
'/Could not read PO file/');
759 file_put_contents($file,
"msgid \"a\"\nmsgstr \"b\"\n");
761 if (is_readable($file)) {
762 $this->markTestSkipped(
'File permissions are not enforced for this user (root).');
765 $this->expectException(RuntimeException::class);
766 $this->expectExceptionMessageMatches(
'/Could not read PO file/');
777 $original->setHeader(
'Language',
'de');
778 $original_po = $original->toPoString();
780 $clone = clone $original;
781 $cloned_entry = $clone->find(
'common',
'save');
782 $cloned_entry->translate(
'Sichern');
783 $cloned_entry->addFlag(
'fuzzy');
784 $cloned_entry->setExtractedComments([
'changed']);
785 $clone->add($cloned_entry);
786 $clone->add(self::entry(
'common',
'cancel',
'Abbrechen'));
787 $clone->setHeader(
'Language',
'en');
789 $this->assertSame($original_po, $original->toPoString());
790 $this->assertSame(
'Speichern', $original->find(
'common',
'save')->getTranslation());
791 $this->assertNull($original->find(
'common',
'cancel'));
792 $this->assertSame(
'Sichern', $clone->find(
'common',
'save')->getTranslation());
793 $this->assertSame(
'en', $clone->getHeader(
'Language'));
807 foreach ($ids as
$id) {
808 $catalog->add(self::entry(
'common',
$id,
$id));
818 $catalog->addInIdOrder(self::entry(
'common',
'a',
'A'));
820 $this->assertSame([
'a',
'm',
'z'], self::idsOf($catalog));
827 $catalog->addInIdOrder(self::entry(
'common',
'm',
'M'));
829 $this->assertSame([
'a',
'm',
'z'], self::idsOf($catalog));
836 $catalog->addInIdOrder(self::entry(
'common',
'z',
'Z'));
838 $this->assertSame([
'a',
'm',
'z'], self::idsOf($catalog));
849 $catalog->addInIdOrder(self::entry(
'common',
'm',
'replaced'));
851 $this->assertSame([
'a',
'm',
'z'], self::idsOf($catalog));
852 $this->assertSame(
'replaced', $catalog->find(
'common',
'm')->getTranslation());
859 $catalog->addInIdOrder(self::entry(
'common',
'a',
'A'));
861 $this->assertSame([
'a'], self::idsOf($catalog));
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
static fromPoString(string $content)
static fromPoFile(string $file)
One message of a TranslationCatalog (a msgctxt/msgid/msgstr block of a .po file).
TranslationCatalog's .po side (fromPoString()/fromPoFile()/toPoString()), i.e.
testAddInIdOrderInsertsAtTheFront()
testEveryShippedTosFileRoundTripsByteIdentically()
Property: every shipped .po/.pot of the migrated tos module is read by fromPoFile() and re-serialized...
const string TOS_LANG_DIRECTORY
testReferencesAndPreviousMessageLinesAreWrittenBackAsLoaded()
References and previous-message lines have no API any more, but are carried along untouched.
testTheLineOfTheErrorIsReported()
testAnEntryCreatedWithAContextZeroThrowsWhenGenerating()
testDropsObsoleteMessages()
Obsolete messages are dropped on load - and therefore never written back.
testFromPoFileNamesTheFileOfABrokenPo()
testFromPoFileThrowsForAnUnreadableFile()
testReadsMultilineStringsCrLfLineEndingsAndABom()
testGeneratingDoesNotChangeTheCatalog()
The workaround works on a copy: generating must not change the catalog it was called on (a leaked "0\...
testRejectsContentThatIsNotValidUtf8()
Every value is used as UTF-8 as it is - no charset conversion takes place anywhere.
static headerValuesWithALineBreak()
testThrowsARuntimeExceptionForBrokenContent(string $po)
Everything StrictPoLoader rejects - and all of it as RuntimeException (the library's own exceptions a...
static headersWithALineBreakAfterLoading()
testASingularTranslationZeroOfAPluralMessageIsWrittenToThePo()
testCommentsAndFlagsRoundTripWithoutTrimming()
"original: ..." comments are compared with the value byte for byte - surrounding whitespace must surv...
testCloneIsIndependentOfTheOriginal()
MigratedLanguageFileSync seeds a new overlay from a clone of the shipped catalog and relies on the sh...
testAnUnrepresentableContextOrPluralIdThrowsWhenGenerating(string $po, bool $mo)
The library cannot represent a context or msgid_plural of "" or "0" (PoGenerator/MoGenerator skip fal...
testAnIdentifierZeroRoundTrips()
Boundary of the "falsy" workaround: an identifier "0" is not special.
static entry(?string $context, string $id, string $translation)
testTheErrorHandlerIsRestoredAfterReading()
The adapter's temporary error handler must be removed again on success and on failure - otherwise eve...
testFromPoFileReadsAFile()
static unrepresentableMessages()
testTheCommentAndFlagLayout()
Layout of comments and flags as the library writes them (characterization): an empty comment is "# " ...
testFromPoFileNamesTheFileOfAHeaderWithALineBreak()
testAddInIdOrderInsertsAtTheEnd()
static singleEntryCatalog(TranslationEntry $entry)
testFromPoFileThrowsForADirectory()
testAddInIdOrderReplacesAnExistingEntryInPlace()
An entry with an id (and context) the catalog already has is replaced in place, like add() - neither ...
testHeadersAreSortedByNameRegardlessOfTheOrderTheyWereSetIn()
testFromPoFileThrowsForAMissingFile()
static headersLosingTheirLineBreakWhenLoaded()
testTheWriterLayoutMatchesTheShippedFiles()
static pluralFormCounts()
testAddInIdOrderInsertsInTheMiddle()
testSetHeaderReplacesAValueTrimsItAndGetHeaderReturnsNullForAMissingOne()
testALineBreakInACommentIsReplacedByASpace()
A comment is a single .po line: a line break inside it would end the comment and corrupt the file,...
testTheSameIdInDifferentContextsIsNoDuplicate()
testIdentifierAndContextRoundTripIncludingSpecialCharacters()
testAHeaderValueKeepingALineBreakAfterLoadingIsRejected(string $po)
An escaped CR that stays inside a header value after loading could be read but never written back (se...
static idsOf(TranslationCatalog $catalog)
testAnEmptyOrHeaderlessPoIsNoError(string $po, int $expected_entries)
An empty file or one without a header block is a valid (empty or header-less) catalog.
testCommentsWrittenWithoutASpaceAfterTheMarkerAreReadAsIs()
testHeaderValuesWithQuotesAndBackslashesAreEscaped()
PoGenerator writes header values verbatim - a quote or backslash would end the string early and corru...
testTheNumberOfWrittenPluralFormsFollowsThePluralFormsHeader(string $header, string $forms, string $expected)
Documented deviation from the GNU tools: the number of msgstr[n] written follows the Plural-Forms hea...
testDecodesControlEscapes()
static catalogOfIds(string ... $ids)
testTheValueZeroIsWrittenAsExactlyZero()
PoGenerator treats a falsy translation as absent - without the adapter's workaround "0" (it occurs in...
testOnlyALeadingBomIsSkipped()
A BOM that is not at the very start is not skipped - it is data, i.e.
testAddInIdOrderOnAnEmptyCatalogAddsTheOnlyEntry()
testSetHeaderRefusesAValueWithALineBreak(string $value)
The .po header block is split at line breaks when read - a value containing one could not be read bac...
testAHeaderWhoseLineBreakIsRemovedWhenLoadedStaysValid(string $header_line, string $expected)
Boundary: line breaks the loader removes itself leave a valid header - a CR at either end is trimmed,...
testRejectsAContentTypeHeaderDeclaringAnyCharsetOtherThanUtf8()
A .po can be valid UTF-8 while its "Content-Type" header declares another charset (e....
testValuesRoundTripExactly(string $value)
static roundTrip(TranslationCatalog $catalog)
testALoadedPluralMessageRoundTripsByteIdentically()
ILIAS itself has no plural API any more, but a loaded plural message is carried along and written bac...
static removeDirectory(string $directory)
static readMoTranslations(string $data)
Messages of the .mo $data by identifier (contexts dropped), a plural message with its default form (P...