I18nCompletionValidator.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. declare(strict_types=1);
  3. require_once __DIR__ . '/I18nValidatorInterface.php';
  4. class I18nCompletionValidator implements I18nValidatorInterface {
  5. private int $totalEntries = 0;
  6. private int $passEntries = 0;
  7. private string $result = '';
  8. /**
  9. * @param array<string,array<string,I18nValue>> $reference
  10. * @param array<string,array<string,I18nValue>> $language
  11. */
  12. public function __construct(
  13. private readonly array $reference,
  14. private array $language,
  15. ) {
  16. }
  17. private static function isPluralVariantKey(string $key): bool {
  18. return preg_match('/\.\d+$/', $key) === 1;
  19. }
  20. /**
  21. * @return array{base:string,index:int}|null
  22. */
  23. private static function parsePluralVariantKey(string $key): ?array {
  24. if (preg_match('/^(?P<base>.+)\.(?P<index>\d+)$/', $key, $matches) !== 1) {
  25. return null;
  26. }
  27. return [
  28. 'base' => $matches['base'],
  29. 'index' => (int)$matches['index'],
  30. ];
  31. }
  32. #[\Override]
  33. public function displayReport(bool $percentage_only = false): string {
  34. if ($this->passEntries > $this->totalEntries) {
  35. throw new \RuntimeException('The number of translated strings cannot be higher than the number of strings');
  36. }
  37. if ($this->totalEntries === 0) {
  38. return 'There is no data.' . PHP_EOL;
  39. }
  40. $percentage = sprintf('%5.1f%%', $this->passEntries / $this->totalEntries * 100);
  41. if ($percentage_only) {
  42. return trim($percentage);
  43. }
  44. return 'Translation is ' . $percentage . ' complete.' . PHP_EOL;
  45. }
  46. #[\Override]
  47. public function displayResult(): string {
  48. return $this->result;
  49. }
  50. #[\Override]
  51. public function validate(): bool {
  52. foreach ($this->reference as $file => $data) {
  53. foreach ($data as $refKey => $refValue) {
  54. if (!$this->pluralVariantAppliesToLanguage($file, $refKey)) {
  55. continue;
  56. }
  57. $this->totalEntries++;
  58. if (!array_key_exists($file, $this->language) || !array_key_exists($refKey, $this->language[$file])) {
  59. $this->result .= "Missing key $refKey" . PHP_EOL;
  60. continue;
  61. }
  62. $this->validateValue($refKey, $refValue, $this->language[$file][$refKey]);
  63. }
  64. }
  65. foreach ($this->language as $file => $data) {
  66. $referenceValues = $this->reference[$file] ?? [];
  67. foreach ($data as $key => $value) {
  68. if (!self::isPluralVariantKey($key) || array_key_exists($key, $referenceValues)) {
  69. continue;
  70. }
  71. $referenceValue = $this->referenceValueForKey($referenceValues, $key);
  72. if ($referenceValue === null) {
  73. continue;
  74. }
  75. $this->totalEntries++;
  76. $this->validateValue($key, $referenceValue, $value);
  77. }
  78. }
  79. return $this->totalEntries === $this->passEntries;
  80. }
  81. /**
  82. * @param array<string,I18nValue> $referenceValues
  83. */
  84. private function referenceValueForKey(array $referenceValues, string $key): ?I18nValue {
  85. if (array_key_exists($key, $referenceValues)) {
  86. return $referenceValues[$key];
  87. }
  88. $parsedKey = self::parsePluralVariantKey($key);
  89. if ($parsedKey === null) {
  90. return null;
  91. }
  92. $pluralKey = $parsedKey['base'] . '.1';
  93. if (array_key_exists($pluralKey, $referenceValues)) {
  94. return $referenceValues[$pluralKey];
  95. }
  96. $singularKey = $parsedKey['base'] . '.0';
  97. return $referenceValues[$singularKey] ?? null;
  98. }
  99. private function validateValue(string $key, I18nValue $referenceValue, I18nValue $value): void {
  100. if ($value->isIgnore()) {
  101. $this->passEntries++;
  102. return;
  103. }
  104. if ($referenceValue->equal($value)) {
  105. $this->result .= "Untranslated key $key - $referenceValue" . PHP_EOL;
  106. return;
  107. }
  108. $this->passEntries++;
  109. }
  110. private function pluralVariantAppliesToLanguage(string $file, string $key): bool {
  111. $parsedKey = self::parsePluralVariantKey($key);
  112. if ($parsedKey === null) {
  113. return true;
  114. }
  115. $indexes = [];
  116. foreach ($this->language[$file] ?? [] as $languageKey => $value) {
  117. $parsedLanguageKey = self::parsePluralVariantKey($languageKey);
  118. if ($parsedLanguageKey === null || $parsedLanguageKey['base'] !== $parsedKey['base']) {
  119. continue;
  120. }
  121. $indexes[$parsedLanguageKey['index']] = true;
  122. }
  123. if ($indexes === []) {
  124. return true;
  125. }
  126. return array_key_exists($parsedKey['index'], $indexes);
  127. }
  128. }