4
0

I18nFileTest.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <?php
  2. declare(strict_types=1);
  3. require_once dirname(__DIR__, 3) . '/cli/i18n/I18nFile.php';
  4. final class I18nFileTest extends \PHPUnit\Framework\TestCase {
  5. public function testDirtyCommentsContainTheMatchingEnglishText(): void {
  6. $file = new I18nFile();
  7. $format = new ReflectionMethod(I18nFile::class, 'format');
  8. $translation = [
  9. 'example.second' => new I18nValue('Identique -> dirty'),
  10. 'example.first' => new I18nValue('Identique -> dirty'),
  11. 'example.missing' => new I18nValue('Absent -> dirty'),
  12. 'example.empty' => new I18nValue('Vide -> dirty'),
  13. 'example.todo' => new I18nValue('Not translated -> todo'),
  14. 'example.ignore' => new I18nValue('Ignored -> ignore'),
  15. 'example.reviewed' => new I18nValue('Reviewed'),
  16. ];
  17. $reference = [
  18. 'example.first' => new I18nValue('First English string'),
  19. 'example.second' => new I18nValue('Second English string'),
  20. 'example.empty' => new I18nValue(" \t\r\n"),
  21. 'example.todo' => new I18nValue('Not translated'),
  22. 'example.ignore' => new I18nValue('Ignored'),
  23. 'example.reviewed' => new I18nValue('Reviewed English string'),
  24. ];
  25. $output = $format->invoke($file, $translation, $reference);
  26. self::assertIsString($output);
  27. self::assertStringContainsString("'first' => 'Identique',\t// DIRTY - First English string", $output);
  28. self::assertStringContainsString("'second' => 'Identique',\t// DIRTY - Second English string", $output);
  29. self::assertStringContainsString("'missing' => 'Absent',\t// DIRTY\n", $output);
  30. self::assertStringContainsString("'empty' => 'Vide',\t// DIRTY\n", $output);
  31. self::assertStringContainsString("'todo' => 'Not translated',\t// TODO\n", $output);
  32. self::assertStringContainsString("'ignore' => 'Ignored',\t// IGNORE\n", $output);
  33. self::assertStringContainsString("'reviewed' => 'Reviewed',\n", $output);
  34. }
  35. public function testDirtyCommentsAreSafeAndIdempotent(): void {
  36. $file = new I18nFile();
  37. $format = new ReflectionMethod(I18nFile::class, 'format');
  38. $process = new ReflectionMethod(I18nFile::class, 'process');
  39. $flatten = new ReflectionMethod(I18nFile::class, 'flatten');
  40. $translation = ['example.key' => new I18nValue("L’apostrophe ' et \\ -> dirty")];
  41. $reference = ['example.key' => new I18nValue("English ' and \\ %s <b>HTML</b>\r\nnext\tline ?> trailing ")];
  42. $output = $format->invoke($file, $translation, $reference);
  43. self::assertIsString($output);
  44. self::assertStringContainsString("// DIRTY - English ' and \\ %s <b>HTML</b> next line ? > trailing\n", $output);
  45. self::assertStringNotContainsString('?>', $output);
  46. $filename = tempnam(sys_get_temp_dir(), 'freshrss-i18n-');
  47. self::assertIsString($filename);
  48. try {
  49. file_put_contents($filename, $output);
  50. $values = include $filename;
  51. self::assertSame(['key' => "L’apostrophe ' et \\"], $values);
  52. $loaded = $process->invoke($file, $filename);
  53. self::assertIsArray($loaded);
  54. $roundTrip = $flatten->invoke($file, $loaded, 'example');
  55. self::assertSame($output, $format->invoke($file, $roundTrip, $reference));
  56. } finally {
  57. unlink($filename);
  58. }
  59. $reference['example.key'] = new I18nValue('Updated English');
  60. $updated = $format->invoke($file, $translation, $reference);
  61. self::assertIsString($updated);
  62. self::assertStringContainsString('// DIRTY - Updated English', $updated);
  63. self::assertStringNotContainsString('trailing', $updated);
  64. }
  65. public function testDirtyCommentsUseEnglishFallbackForAdditionalPluralForms(): void {
  66. $format = new ReflectionMethod(I18nFile::class, 'format');
  67. $output = $format->invoke(new I18nFile(), [
  68. 'example.count.0' => new I18nValue('Un article -> dirty'),
  69. 'example.count.1' => new I18nValue('Des articles -> dirty'),
  70. 'example.count.2' => new I18nValue('Plusieurs articles -> dirty'),
  71. 'example.only.2' => new I18nValue('Autre forme -> dirty'),
  72. ], [
  73. 'example.count.0' => new I18nValue('One article'),
  74. 'example.count.1' => new I18nValue('Many articles'),
  75. 'example.only.0' => new I18nValue('One form'),
  76. ]);
  77. self::assertIsString($output);
  78. self::assertStringContainsString("0 => 'Un article',\t// DIRTY - One article", $output);
  79. self::assertStringContainsString("1 => 'Des articles',\t// DIRTY - Many articles", $output);
  80. self::assertStringContainsString("2 => 'Plusieurs articles',\t// DIRTY - Many articles", $output);
  81. self::assertStringContainsString("2 => 'Autre forme',\t// DIRTY - One form", $output);
  82. }
  83. public function testDirtyCommentsShowBothEnglishFormsForSingleFormTranslations(): void {
  84. $format = new ReflectionMethod(I18nFile::class, 'format');
  85. $output = $format->invoke(new I18nFile(), [
  86. 'example.count.0' => new I18nValue('記事が %d 件あります -> dirty'),
  87. 'example.only.0' => new I18nValue('一つの形 -> dirty'),
  88. 'example.fallback.0' => new I18nValue('別の形 -> dirty'),
  89. ], [
  90. 'example.count.0' => new I18nValue("One article\n?>"),
  91. 'example.count.1' => new I18nValue("Many articles\t?>"),
  92. 'example.only.0' => new I18nValue('One form'),
  93. 'example.fallback.1' => new I18nValue('Plural only'),
  94. ]);
  95. self::assertIsString($output);
  96. self::assertStringContainsString("0 => '記事が %d 件あります',\t// DIRTY - Singular: One article ? > | Plural: Many articles ? >\n", $output);
  97. self::assertStringContainsString("0 => '一つの形',\t// DIRTY - One form\n", $output);
  98. self::assertStringContainsString("0 => '別の形',\t// DIRTY - Plural only\n", $output);
  99. }
  100. public function test(): void {
  101. $before = $this->computeFilesHash();
  102. $file = new I18nFile();
  103. $data = $file->load();
  104. self::assertArrayNotHasKey('plurals.php', $data['en']);
  105. $file->dump($data);
  106. $after = $this->computeFilesHash();
  107. self::assertSame($before, $after);
  108. }
  109. /** @return array<string,string|false> */
  110. private function computeFilesHash(): array {
  111. $hashes = [];
  112. $dirs = new DirectoryIterator(I18N_PATH);
  113. foreach ($dirs as $dir) {
  114. if ($dir->isDot()) {
  115. continue;
  116. }
  117. $files = new DirectoryIterator($dir->getPathname());
  118. foreach ($files as $file) {
  119. if (!$file->isFile()) {
  120. continue;
  121. }
  122. $hashes[$file->getPathname()] = sha1_file($file->getPathname());
  123. }
  124. }
  125. return $hashes;
  126. }
  127. }