From 8ca6cc32b2c789a3149861159ad258f2cb9491e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:39:39 +0200 Subject: Adding upstream version 2.11.4. Signed-off-by: Daniel Baumann --- .../Translation/Util/GettextTranslationHelper.php | 442 +++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 modules/translation/library/Translation/Util/GettextTranslationHelper.php (limited to 'modules/translation/library/Translation/Util/GettextTranslationHelper.php') diff --git a/modules/translation/library/Translation/Util/GettextTranslationHelper.php b/modules/translation/library/Translation/Util/GettextTranslationHelper.php new file mode 100644 index 0000000..d1e6ac2 --- /dev/null +++ b/modules/translation/library/Translation/Util/GettextTranslationHelper.php @@ -0,0 +1,442 @@ +moduleMgr = $bootstrap->getModuleManager(); + $this->locale = $locale; + } + + /** + * Cleanup temporary files + */ + public function __destruct() + { + if ($this->catalogPath !== null && file_exists($this->catalogPath)) { + unlink($this->catalogPath); + } + + if ($this->templatePath !== null && file_exists($this->templatePath)) { + unlink($this->templatePath); + } + } + + /** + * Get the config + * + * @return Config + */ + public function getConfig() + { + return $this->config; + } + + /** + * Set the config + * + * @param Config $config + * + * @return $this + */ + public function setConfig(Config $config) + { + $this->config = $config; + return $this; + } + + /** + * Update the translation table for a particular module + * + * @param string $module The name of the module for which to update the translation table + */ + public function updateModuleTranslations($module) + { + $this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_'); + $this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_'); + $this->version = $this->moduleMgr->getModule($module)->getVersion(); + $this->moduleName = $this->moduleMgr->getModule($module)->getName(); + + $this->moduleDir = $this->moduleMgr->getModuleDir($module); + $this->tablePath = implode( + DIRECTORY_SEPARATOR, + array( + $this->moduleDir, + 'application', + 'locale', + $this->locale, + 'LC_MESSAGES', + $module . '.po' + ) + ); + + $this->createFileCatalog(); + $this->createTemplateFile(); + $this->updateTranslationTable(); + } + + /** + * Compile the translation table for a particular module + * + * @param string $module The name of the module for which to compile the translation table + */ + public function compileModuleTranslation($module) + { + $this->moduleDir = $this->moduleMgr->getModuleDir($module); + $this->tablePath = implode( + DIRECTORY_SEPARATOR, + array( + $this->moduleDir, + 'application', + 'locale', + $this->locale, + 'LC_MESSAGES', + $module . '.po' + ) + ); + + $this->compileTranslationTable(); + } + + /** + * Update any existing or create a new translation table using the gettext tools + * + * @throws Exception In case the translation table does not yet exist and cannot be created + */ + private function updateTranslationTable() + { + if (is_file($this->tablePath)) { + shell_exec(sprintf( + '%s --update --backup=none %s %s 2>&1', + $this->getConfig()->get('translation', 'msgmerge', '/usr/bin/env msgmerge'), + $this->tablePath, + $this->templatePath + )); + } else { + if ((!is_dir(dirname($this->tablePath)) && !@mkdir(dirname($this->tablePath), 0755, true)) || + !rename($this->templatePath, $this->tablePath)) { + throw new IcingaException( + 'Unable to create %s', + $this->tablePath + ); + } + } + $this->updateHeader($this->tablePath); + $this->fixSourceLocations($this->tablePath); + } + + /** + * Create the template file using the gettext tools + */ + private function createTemplateFile() + { + shell_exec( + implode( + ' ', + array( + $this->getConfig()->get('translation', 'xgettext', '/usr/bin/env xgettext'), + '--language=PHP', + '--keyword=translate', + '--keyword=translate:1,2c', + '--keyword=translateInDomain:2', + '--keyword=translateInDomain:2,3c', + '--keyword=translatePlural:1,2', + '--keyword=translatePlural:1,2,4c', + '--keyword=translatePluralInDomain:2,3', + '--keyword=translatePluralInDomain:2,3,5c', + '--keyword=mt:2', + '--keyword=mt:2,3c', + '--keyword=mtp:2,3', + '--keyword=mtp:2,3,5c', + '--keyword=t', + '--keyword=t:1,2c', + '--keyword=tp:1,2', + '--keyword=tp:1,2,4c', + '--keyword=N_', + '--sort-output', + '--force-po', + '--omit-header', + '--from-code=' . self::FILE_ENCODING, + '--files-from="' . $this->catalogPath . '"', + '--output="' . $this->templatePath . '"' + ) + ) + ); + } + + /** + * Create or update a gettext conformant header in the given file + * + * @param string $path The path to the file + */ + private function updateHeader($path) + { + $headerInfo = array( + 'title' => $this->moduleMgr->getModule($this->moduleName)->getTitle(), + 'copyright_holder' => 'TEAM NAME', + 'copyright_year' => date('Y'), + 'author_name' => 'FIRST AUTHOR', + 'author_mail' => 'EMAIL@ADDRESS', + 'author_year' => 'YEAR', + 'project_name' => ucfirst($this->moduleName) . ' Module', + 'project_version' => $this->version, + 'project_bug_mail' => 'ISSUE TRACKER', + 'pot_creation_date' => date('Y-m-d H:iO'), + 'po_revision_date' => 'YEAR-MO-DA HO:MI+ZONE', + 'translator_name' => 'FULL NAME', + 'translator_mail' => 'EMAIL@ADDRESS', + 'language' => $this->locale, + 'language_team_name' => 'LANGUAGE', + 'language_team_url' => 'LL@li.org', + 'charset' => self::FILE_ENCODING + ); + + $content = file_get_contents($path); + if (strpos($content, '# ') === 0) { + $authorInfo = array(); + if (preg_match('@# (.+) <(.+)>, (\d+|YEAR)\.@', $content, $authorInfo)) { + $headerInfo['author_name'] = $authorInfo[1]; + $headerInfo['author_mail'] = $authorInfo[2]; + $headerInfo['author_year'] = $authorInfo[3]; + } + $revisionInfo = array(); + if (preg_match('@Revision-Date: (\d{4}-\d{2}-\d{2} \d{2}:\d{2}\+\d{4})@', $content, $revisionInfo)) { + $headerInfo['po_revision_date'] = $revisionInfo[1]; + } + $translatorInfo = array(); + if (preg_match('@Last-Translator: (.+) <(.+)>@', $content, $translatorInfo)) { + $headerInfo['translator_name'] = $translatorInfo[1]; + $headerInfo['translator_mail'] = $translatorInfo[2]; + } + $languageTeamInfo = array(); + if (preg_match('@Language-Team: (.+) <(.+)>@', $content, $languageTeamInfo)) { + $headerInfo['language_team_name'] = $languageTeamInfo[1]; + $headerInfo['language_team_url'] = $languageTeamInfo[2]; + } + $languageInfo = array(); + if (preg_match('@Language: ([a-z]{2}_[A-Z]{2})@', $content, $languageInfo)) { + $headerInfo['language'] = $languageInfo[1]; + } + } + + file_put_contents( + $path, + implode( + PHP_EOL, + array( + '# ' . $headerInfo['title'] . '.', + '# Copyright (C) ' . $headerInfo['copyright_year'] . ' ' . $headerInfo['copyright_holder'], + '# This file is distributed under the same license as ' . $headerInfo['project_name'] . '.', + '# ' . $headerInfo['author_name'] . ' <' . $headerInfo['author_mail'] + . '>, ' . $headerInfo['author_year'] . '.', + '# ', + '#, fuzzy', + 'msgid ""', + 'msgstr ""', + '"Project-Id-Version: ' . $headerInfo['project_name'] . ' (' + . $headerInfo['project_version'] . ')\n"', + '"Report-Msgid-Bugs-To: ' . $headerInfo['project_bug_mail'] . '\n"', + '"POT-Creation-Date: ' . $headerInfo['pot_creation_date'] . '\n"', + '"PO-Revision-Date: ' . $headerInfo['po_revision_date'] . '\n"', + '"Last-Translator: ' . $headerInfo['translator_name'] . ' <' + . $headerInfo['translator_mail'] . '>\n"', + '"Language: ' . $headerInfo['language'] . '\n"', + '"Language-Team: ' . $headerInfo['language_team_name'] . ' <' + . $headerInfo['language_team_url'] . '>\n"', + '"MIME-Version: 1.0\n"', + '"Content-Type: text/plain; charset=' . $headerInfo['charset'] . '\n"', + '"Content-Transfer-Encoding: 8bit\n"', + '"Plural-Forms: nplurals=2; plural=(n != 1);\n"', + '"X-Poedit-Basepath: .\n"', + '"X-Poedit-SearchPath-0: .\n"', + '' + ) + ) . PHP_EOL . substr($content, strpos($content, '#: ')) + ); + } + + /** + * Adjust all absolute source file paths so that they're all relative to the catalog's location + * + * @param string $path + */ + protected function fixSourceLocations($path) + { + shell_exec(sprintf( + "sed -i 's;%s;../../../..;g' %s", + $this->moduleDir, + $path + )); + } + + /** + * Create the file catalog + * + * @throws Exception In case the catalog-file cannot be created + */ + private function createFileCatalog() + { + $catalog = new File($this->catalogPath, 'w'); + + try { + $this->getSourceFileNames($this->moduleDir, $catalog); + } catch (Exception $error) { + throw $error; + } + + $catalog->fflush(); + } + + /** + * Recursively scan the given directory for translatable source files + * + * @param string $directory The directory where to search for sources + * @param File $file The file where to write the results + * @param array $blacklist A list of directories to omit + * + * @throws Exception In case the given directory is not readable + */ + private function getSourceFileNames($directory, File $file) + { + $directoryHandle = opendir($directory); + if (!$directoryHandle) { + throw new IcingaException( + 'Unable to read files from %s', + $directory + ); + } + + $subdirs = array(); + while (($filename = readdir($directoryHandle)) !== false) { + if ($filename[0] === '.' || $filename === 'vendor') { + continue; + } + $filepath = $directory . DIRECTORY_SEPARATOR . $filename; + if (preg_match('@^[^\.].+\.(' . implode('|', $this->sourceExtensions) . ')$@', $filename)) { + $file->fwrite($filepath . PHP_EOL); + } elseif (! is_link($filepath) && is_dir($filepath)) { + $subdirs[] = $filepath; + } + } + closedir($directoryHandle); + + foreach ($subdirs as $subdir) { + $this->getSourceFileNames($subdir, $file); + } + } + + /** + * Compile the translation table + */ + private function compileTranslationTable() + { + $targetPath = substr($this->tablePath, 0, strrpos($this->tablePath, '.')) . '.mo'; + shell_exec( + implode( + ' ', + array( + $this->getConfig()->get('translation', 'msgfmt', '/usr/bin/env msgfmt'), + '-o ' . $targetPath, + $this->tablePath + ) + ) + ); + } +} -- cgit v1.2.3