<?php
namespace Newland\NeosViewHelpers\ViewHelpers;

/*
 * This file is part of the "neos-viewhelpers" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\Flow\Annotations as Flow;
use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException;
use Neos\Flow\I18n\Locale;
use Neos\Flow\I18n\Service;
use Neos\Flow\Mvc\ActionRequest;
use Neos\FluidAdaptor\Core\Rendering\RenderingContext;
use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException;

/**
 * Returns translated message using source message or key ID.
 * For missing translations it will walk up the locale chain and return the first match.
 *
 * Also replaces all placeholders with formatted versions of provided values.
 *
 * = Examples =
 *
 * <code title="Translation by id">
 * <f:translate id="user.unregistered">Unregistered User</f:translate>
 * </code>
 * <output>
 * translation of label with the id "user.unregistered" and a fallback to "Unregistered User"
 * </output>
 *
 * <code title="Inline notation">
 * {f:translate(id: 'some.label.id', default: 'fallback result')}
 * </code>
 * <output>
 * translation of label with the id "some.label.id" and a fallback to "fallback result"
 * </output>
 *
 * <code title="Custom source and locale">
 * <f:translate id="some.label.id" somesource="SomeLabelsCatalog" locale="de_DE"/>
 * </code>
 * <output>
 * translation from custom source "SomeLabelsCatalog" for locale "de_DE"
 * </output>
 *
 * <code title="Custom source from other package">
 * <f:translate id="some.label.id" source="LabelsCatalog" package="OtherPackage"/>
 * </code>
 * <output>
 * translation from custom source "LabelsCatalog" in "OtherPackage"
 * </output>
 *
 * <code title="Arguments">
 * <f:translate arguments="{0: 'foo', 1: '99.9'}">Untranslated {0} and {1,number}</f:translate>
 * </code>
 * <output>
 * translation of the label "Untranslated foo and 99.9"
 * </output>
 *
 * <code title="Translation by label">
 * <f:translate>Untranslated label</f:translate>
 * </code>
 * <output>
 * translation of the label "Untranslated label"
 * </output>
 *
 * @see https://gist.github.com/khuppenbauer/4702897
 */
class TranslateViewHelper extends \Neos\FluidAdaptor\ViewHelpers\TranslateViewHelper
{
    /**
     * @var Service
     * @Flow\Inject()
     */
    protected $localizationService;

    /**
     * Renders the translated label.
     * Replaces all placeholders with corresponding values if they exist in the
     * translated label.
     *
     * @param array $parsedLanguages
     * @return string Translated label or source label / ID key
     */
    public function render($parsedLanguages = [])
    {
        $id = $this->arguments['id'];
        $value = $this->arguments['value'];
        $arguments = $this->arguments['arguments'];
        $source = $this->arguments['source'];
        $package = $this->arguments['package'];
        $quantity = $this->arguments['quantity'];
        $locale = $this->arguments['locale'];

        $localeObject = $this->getLocaleObject($locale);

        if ($package === null) {
            /** @var RenderingContext $renderingContext */
            $renderingContext = $this->renderingContext;
            $request = $renderingContext->getControllerContext()->getRequest();
            if ($request instanceof ActionRequest) {
                $package = $request->getControllerPackageKey();
            }
            if ($package === null) {
                throw new ViewHelperException(
                    'The current package key can\'t be resolved. Make sure to initialize the Fluid' .
                    ' view with a proper ActionRequest and/or specify the "package" argument when' .
                    ' using the f:translate ViewHelper',
                    1416832309
                );
            }
        }
        $originalLabel = $value ?? $this->renderChildren();

        if ($id === null) {
            return (string) $this->translator->translateByOriginalLabel(
                $originalLabel,
                $arguments,
                $quantity,
                $localeObject,
                $source,
                $package
            );
        }

        $translation = $this->translator->translateById($id, $arguments, $quantity, $localeObject, $source, $package);
        if ($translation !== null) {
            return $translation;
        }

        // Walk up the locale chain recursively and avoid an infinite loop.
        // The locale chain automatically includes possible parent locales (`de` vs `de_CH`)
        // and the default locale, `en` by default.
        $localeChain = $this->localizationService->getLocaleChain($localeObject);
        $localeObject = next($localeChain);
        if ($localeObject !== false && in_array($localeObject->getLanguage(), $parsedLanguages, true) === false) {
            $this->arguments['locale'] = $localeObject->getLanguage();
            $parsedLanguages[] = $localeObject->getLanguage();
            return $this->render($parsedLanguages);
        }

        if ($originalLabel) {
            return (string) $this->translator->translateByOriginalLabel(
                $originalLabel,
                $arguments,
                $quantity,
                $localeObject,
                $source,
                $package
            );
        }

        return $id;
    }

    private function getLocaleObject(string $locale = null): Locale
    {
        $localeObject = null;
        if ($locale !== null) {
            try {
                $localeObject = new Locale($locale);
            } catch (InvalidLocaleIdentifierException $e) {
                throw new ViewHelperException(sprintf('"%s" is not a valid locale identifier.', $locale), 1279815885);
            }
        } else {
            $localeObject = $this->localizationService->getConfiguration()->getCurrentLocale();
        }
        return $localeObject;
    }
}
