abbadon1334/atk4-i18next-php

View on GitHub
src/I18Next/Locale/Translations.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace I18Next\Locale;

use atk4\core\ConfigTrait;
use atk4\core\Exception;
use DirectoryIterator;
use I18Next\Exception\TranslationSyntaxError;
use I18Next\Locale\Processor\Key\AbstractProcessorKey;

/**
 * @internal
 */
final class Translations
{
    use ConfigTrait {
        setConfig as protected;
        readConfig as protected;
    }

    /** @var string[] */
    protected $namespace_priority = [];

    /** @var bool */
    protected $use_filename_as_namespace = false;

    /** @var string */
    private $loader_format = 'json';

    /** @var string */
    private $loader_format_ext = 'json';

    /**
     * @param string      $path
     * @param bool        $use_filename_as_namespace
     * @param string|null ...$namespace_priority
     *
     * @throws Exception
     */
    public function load(string $path, bool $use_filename_as_namespace, ?string ...$namespace_priority): void
    {
        $this->useFilenameAsNamespace($use_filename_as_namespace);
        $this->setNamespacePriority(...$namespace_priority);

        $configs = $this->loadSingle($path);

        // if namespaces are used set loaded configs and return
        if ($this->useNamespaces()) {
            $this->config = $configs;

            return;
        }

        // if not using namespaces replace keys config
        $this->config = call_user_func_array('array_replace_recursive', $configs);
    }

    /**
     * @param string $path
     *
     * @return array
     * @throws Exception
     * @throws TranslationSyntaxError
     */
    private function loadSingle(string $path): array
    {
        $configs = [];
        foreach (new DirectoryIterator($path) as $fileInfo) {
            if (! $fileInfo->isFile()) {
                continue;
            }

            // read config
            $this->readConfig($fileInfo->getPathname(), $this->loader_format);

            // normalizing
            $this->afterReadProcessForKeyCounters();
            $this->afterReadProcessForKeyDeepInline();

            $this->afterReadAddNamespaceIfNeeded($configs, $fileInfo->getBasename('.'.$this->loader_format_ext));

            // always reset config after every load
            // to prevent merging with other config in ConfigTrait
            $this->config = [];
        }

        return $configs;
    }

    /**
     * @param bool|null $enabled
     */
    public function useFilenameAsNamespace(?bool $enabled): void
    {
        $this->use_filename_as_namespace = $enabled ?? true;
    }

    /**
     * @param string|null ...$namespace_priority
     */
    private function setNamespacePriority(?string ...$namespace_priority): void
    {
        $namespace_priority = array_filter($namespace_priority, function ($value) {
            return ! empty($value);
        });

        // exit if no namespace priority
        if (empty($namespace_priority)) {
            return;
        }

        // force use of filename as namespaces
        $this->useFilenameAsNamespace(true);
        $this->namespace_priority = $namespace_priority;
    }

    private function processKeyVariation(string $processorClass, string $key, $value)
    {
        $key_plural_definition = explode('_', $key);
        $key_plural_definition = end($key_plural_definition);

        $processorClass = '\I18Next\Locale\Processor\Key\\'.$processorClass;

        /** @var AbstractProcessorKey $processorClass */
        $processor = new $processorClass($key_plural_definition, $key, $value);
        if (! $processorClass::willBeProcessed($key_plural_definition)) {
            return;
        }

        $cleared_key = $processor->getKeyCleared();
        $key_plural_one = $this->getConfig($cleared_key);
        if (null !== $key_plural_one && ! is_array($key_plural_one)) {
            $this->setConfig($cleared_key, []);
            $this->setConfig($cleared_key.'/1', $key_plural_one);
        }

        foreach ($processor->getResult() as $processed_key => $processed_value) {
            $this->setConfig($processed_key, $processed_value);
        }
    }

    /**
     * @throws Exception
     */
    private function afterReadProcessForKeyCounters(): void
    {
        foreach ($this->config as $key => $value) {
            $this->processKeyVariation('KeyPlural', $key, $value);
            $this->processKeyVariation('KeyCounter', $key, $value);
            $this->processKeyVariation('KeyInterval', $key, $value);
        }
    }

    private function afterReadProcessForKeyDeepInline(): void
    {
        $filtered = array_filter($this->config, function ($key) {
            return false !== strpos($key, '.');
        }, ARRAY_FILTER_USE_KEY);

        foreach ($filtered as $key => $value) {
            unset($this->config[$key]);
            $this->setConfig(str_replace('.', '/', $key), $value);
        }
    }

    /**
     * @param array  $config
     * @param string $namespace
     */
    private function afterReadAddNamespaceIfNeeded(array &$config, string $namespace): void
    {
        if (! $this->useNamespaces()) {
            $config[] = $this->config;

            return;
        }

        $namespace_key = $this->useNamespaces() ? $namespace : null;
        // use array_filter to remove references
        $config[$namespace_key] = array_filter($this->config, function ($v) {
            return $v;
        });
    }

    /**
     * @return bool
     */
    public function useNamespaces(): bool
    {
        return $this->use_filename_as_namespace;
    }

    /**
     * @return string[]
     */
    public function getNamespaceRanked()
    {
        return $this->namespace_priority;
    }

    /**
     * @param string $format
     */
    public function setLoaderFormat(string $format): void
    {
        $this->loader_format = $format;

        // @TODO Feature - Loading different format : set extension definition here based on $format
        $this->loader_format_ext = $format;
    }
}