#!/usr/bin/env php $language */ public array $language; public bool $displayResult; public bool $help; public bool $displayReport; public bool $generateReadme; public function __construct() { $this->addOption('language', (new CliOption('language', 'l'))->typeOfArrayOfString()); $this->addOption('displayResult', (new CliOption('display-result', 'd'))->withValueNone()); $this->addOption('help', (new CliOption('help', 'h'))->withValueNone()); $this->addOption('displayReport', (new CliOption('display-report', 'r'))->withValueNone()); $this->addOption('generateReadme', (new CliOption('generate-readme', 'g'))->withValueNone()); parent::__construct(); } }; if (!empty($cliOptions->errors)) { fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage); } if ($cliOptions->help) { checkHelp(); } $i18nFile = new I18nFile(); $i18nData = new I18nData($i18nFile->load()); if (isset($cliOptions->language)) { $languages = $cliOptions->language; } else { $languages = $i18nData->getAvailableLanguages(); } $isValidated = true; $result = []; $report = []; $percentage = []; foreach ($languages as $language) { if ($language === $i18nData::REFERENCE_LANGUAGE) { $i18nValidator = new I18nUsageValidator($i18nData->getReferenceLanguage(), findUsedTranslations()); } else { $i18nValidator = new I18nCompletionValidator($i18nData->getReferenceLanguage(), $i18nData->getLanguage($language)); } $isValidated = $i18nValidator->validate() && $isValidated; $report[$language] = sprintf('%-5s - %s', $language, $i18nValidator->displayReport()); $percentage[$language] = $i18nValidator->displayReport(percentage_only: true); $result[$language] = $i18nValidator->displayResult(); } if ($cliOptions->displayResult) { foreach ($result as $lang => $value) { echo 'Language: ', $lang, PHP_EOL; print_r($value); echo PHP_EOL; } } if ($cliOptions->displayReport) { foreach ($report as $value) { echo $value; } } function writeToReadme(string $readmePath, string $markdownImgStr): void { $readme = file_get_contents($readmePath); if ($readme === false) { echo 'Error: Unable to open ' . $readmePath, PHP_EOL; exit(1); } if (file_put_contents($readmePath, preg_replace('/(.*?)<\/translations>/s', << $markdownImgStr EOF, $readme)) === false) { echo 'Error: Fail while writing to ' . $readmePath, PHP_EOL; exit(1); } echo 'Successfully written translation status into ' . $readmePath, PHP_EOL; } function embedSvg(string $contents): string { return preg_replace( '/]*?)(xmlns=["\'][^"\']+["\']))?(?:(?:[^>]*?)(viewBox=["\'][^"\']+["\']))?(?:[^>]*?)>/i', '', $contents ) ?? ''; } if ($cliOptions->generateReadme) { $supportedFormats = ['txt', 'svg']; $flagsDir = __DIR__ . '/../docs/i18n/flags'; $markdownImgStr = ''; foreach ($percentage as $lang => $value) { $percentageInt = intval(rtrim($value, '%')); $color = 'green'; if ($percentageInt < 90) { $color = 'gold'; } if ($percentageInt < 70) { $color = 'darkred'; } $svgFile = $flagsDir . '/' . $lang . '.svg'; $svg = ''; if (file_exists($svgFile)) { $svg = file_get_contents($svgFile); if ($svg === false) { echo 'Error: Unable to open ' . $svgFile, PHP_EOL; exit(1); } } $ghSearchUrl = 'https://github.com/search?q=' . urlencode("repo:FreshRSS/FreshRSS path:app/i18n/$lang /(TODO|DIRTY)$/"); $genPath = $flagsDir . '/gen/' . $lang . '.svg'; $template = '' . "\n"; if ($svg === '') { $i18nGen = include __DIR__ . "/../app/i18n/$lang/gen.php"; if (!is_array($i18nGen) || !is_string($i18nGen['flag'] ?? null)) { echo 'Error: No Unicode flag found for language ' . $lang, PHP_EOL; exit(1); } $unicodeFlag = $i18nGen['flag']; if ($lang !== 'en' && $unicodeFlag === '🇬🇧') { echo 'Error: Wrong Unicode flag for language ' . $lang, PHP_EOL; exit(1); } $value = $unicodeFlag . ' ' . $percentageInt . '%'; $template .= << $value EOF; } else { // An SVG file is available to override the Unicode flag $value = $percentageInt . '%'; $contents = embedSvg($svg); $template .= << $contents $value EOF; } if (file_put_contents($genPath, $template) === false) { echo 'Error: Fail while writing to ' . $genPath, PHP_EOL; exit(1); } $markdownImgStr .= "[![$lang](./docs/i18n/flags/gen/$lang.svg)]($ghSearchUrl) "; } // In case we're located in ./cli/ if (!file_exists('constants.php')) { chdir('..'); } foreach (array_merge(['README.md'], glob('README.*.md') ?: []) as $readmePath) { writeToReadme($readmePath, rtrim($markdownImgStr)); } exit(); } if (!$isValidated) { exit(1); } /** * Find used translation keys in the project * * Iterates through all php and phtml files in the whole project and extracts all * translation keys used. * * @return list */ function findUsedTranslations(): array { $directory = new RecursiveDirectoryIterator(__DIR__ . '/..'); $iterator = new RecursiveIteratorIterator($directory); $regex = new RegexIterator($iterator, '/^.+\.(php|phtml)$/i', RecursiveRegexIterator::GET_MATCH); $usedI18n = []; foreach (array_keys(iterator_to_array($regex)) as $file) { if (!is_string($file) || $file === '') { continue; } $fileContent = file_get_contents($file); if ($fileContent === false) { continue; } preg_match_all('/_t\([\'"](?P[^\'"]+)[\'"]/', $fileContent, $matches); $usedI18n = array_merge($usedI18n, $matches['strings']); } return $usedI18n; } /** * Output help message. */ function checkHelp(): never { $file = str_replace(__DIR__ . '/', '', __FILE__); echo <<