diff --git a/bin/configure b/bin/configure index 313bc26aaf..5616b12616 100755 --- a/bin/configure +++ b/bin/configure @@ -7,15 +7,23 @@ done cd "${git_dir}" || exit -. ./docker/bootstrap/bootstrap.env +if [ -e ./docker/bootstrap/bootstrap.env ]; then + . ./docker/bootstrap/bootstrap.env +fi +# TODO Add configuration +cp docker-compose.yaml.in docker-compose.yaml while :; do printf "DBMS (postgres|mariadb): " && read -r dbms - [ $(echo "${dbms}" | grep -E 'postgres|mariadb') ] && break + echo "${dbms}" | grep -Eq 'postgres|mariadb' && break +done + +while :; do + printf "Social database name: " && read -r db + echo "${db}" | grep -vEq 'postgres' && break done -printf "Social database name: " && read -r db [ "${dbms}" = 'mariadb' ] && printf "Database user: " && read -r user printf "Database password: " && read -r password printf "Sitename: " && read -r sitename @@ -24,7 +32,7 @@ printf "Admin password: " && read -r admin_password while :; do printf "Site profile (public|private|community|single_user): " && read -r profile - [ $(echo "${profile}" | grep -E 'public|private|community|single_user') ] && break + echo "${profile}" | grep -Eq 'public|private|community|single_user' && break done mkdir -p ./docker/db diff --git a/config/services.yaml b/config/services.yaml index a951097f27..2dad7fc5d0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -32,3 +32,7 @@ services: class: App\Core\DB\SchemaDefDriver arguments: - '%kernel.project_dir%/src/Entity' + + App\Core\I18n\TransExtractor: + tags: + - { name: translation.extractor, alias: social } diff --git a/docker-compose.yaml b/docker-compose.yaml.in similarity index 100% rename from docker-compose.yaml rename to docker-compose.yaml.in diff --git a/src/Core/I18n/TransExtractor.php b/src/Core/I18n/TransExtractor.php new file mode 100644 index 0000000000..5e595381dc --- /dev/null +++ b/src/Core/I18n/TransExtractor.php @@ -0,0 +1,279 @@ +. +// }}} + +/** + * Extracts translation messages from PHP code. + * + * @package GNUsocial + * @category I18n + * + * @author Symfony project + * @author Michel Salib + * @author Fabien Potencier + * @copyright 2011-2019 Symfony project + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace App\Core\I18n; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\Extractor\AbstractFileExtractor; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\Extractor\PhpExtractor; +use Symfony\Component\Translation\Extractor\PhpStringTokenParser; +use Symfony\Component\Translation\MessageCatalogue; + +class TransExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + /** + * The sequence that captures translation messages. + * + * @todo add support for all the cases we use + * + * @var array + */ + protected $sequences = [ + [ + '_m', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '_m', + '(', + self::MESSAGE_TOKEN, + ], + ]; + + // {{{Code from PhpExtractor + + const MESSAGE_TOKEN = 300; + const METHOD_ARGUMENTS_TOKEN = 1000; + const DOMAIN_TOKEN = 1001; + + /** + * Prefix for new found message. + * + * @var string + */ + private $prefix = ''; + + /** + * {@inheritdoc} + */ + public function extract($resource, MessageCatalogue $catalog) + { + if (($dir = strstr($resource, '/Core/GNUsocial.php', true)) === false) { + return; + } + + $files = $this->extractFiles($dir); + foreach ($files as $file) { + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); + + gc_mem_caches(); + } + } + + /** + * {@inheritdoc} + */ + public function setPrefix(string $prefix) + { + $this->prefix = $prefix; + } + + /** + * Normalizes a token. + * + * @param mixed $token + * + * @return null|string + */ + protected function normalizeToken($token) + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + + return $token; + } + + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator) + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (T_WHITESPACE !== $t[0]) { + break; + } + } + } + + private function skipMethodArgument(\Iterator $tokenIterator) + { + $openBraces = 0; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + + if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { + break; + } + } + } + + /** + * @throws \InvalidArgumentException + * + * @return bool + * + */ + protected function canBeExtracted(string $file) + { + return $this->isFile($file) + && 'php' === pathinfo($file, PATHINFO_EXTENSION) + && strstr($file, '/src/') !== false; + } + + /** + * {@inheritdoc} + */ + protected function extractFromDirectory($directory) + { + $finder = new Finder(); + return $finder->files()->name('*.php')->in($directory); + } + + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator) + { + $message = ''; + $docToken = ''; + $docPart = ''; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + + switch ($t[0]) { + case T_START_HEREDOC: + $docToken = $t[1]; + break; + case T_ENCAPSED_AND_WHITESPACE: + case T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case T_END_HEREDOC: + $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case T_WHITESPACE: + break; + default: + break 2; + } + } + + return $message; + } + + // }}} + + /** + * Extracts trans message from PHP tokens. + */ + protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename) + { + $tokenIterator = new \ArrayIterator($tokens); + + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = I18nHelper::_mdomain($filename); + $tokenIterator->seek($key); + + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + + if (\count($sequence) === ($sequenceKey + 1)) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + + break; + } else { + break; + } + } + + if ($message) { + $catalog->set($message, $this->prefix . $message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename . ':' . $tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } +}