<?php
/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         1.2.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\I18n;

use Aura\Intl\FormatterLocator;
use Aura\Intl\PackageLocator;
use Cake\Cache\Cache;
use Cake\I18n\Formatter\IcuFormatter;
use Cake\I18n\Formatter\SprintfFormatter;
use Locale;

/**
 * I18n handles translation of Text and time format strings.
 */
class I18n
{

    /**
     * Default locale
     *
     * @var string
     */
    const DEFAULT_LOCALE = 'en_US';

    /**
     * The translators collection
     *
     * @var \Cake\I18n\TranslatorRegistry|null
     */
    protected static $_collection;

    /**
     * The environment default locale
     *
     * @var string
     */
    protected static $_defaultLocale;

    /**
     * Returns the translators collection instance. It can be used
     * for getting specific translators based of their name and locale
     * or to configure some aspect of future translations that are not yet constructed.
     *
     * @return \Cake\I18n\TranslatorRegistry The translators collection.
     */
    public static function translators()
    {
        if (static::$_collection !== null) {
            return static::$_collection;
        }

        static::$_collection = new TranslatorRegistry(
            new PackageLocator,
            new FormatterLocator([
                'sprintf' => function () {
                    return new SprintfFormatter();
                },
                'default' => function () {
                    return new IcuFormatter();
                },
            ]),
            new TranslatorFactory,
            static::getLocale()
        );

        if (class_exists('Cake\Cache\Cache')) {
            static::$_collection->setCacher(Cache::engine('_cake_core_'));
        }

        return static::$_collection;
    }

    /**
     * Returns an instance of a translator that was configured for the name and passed
     * locale. If no locale is passed then it takes the value returned by the `getLocale()` method.
     *
     * This method can be used to configure future translators, this is achieved by passing a callable
     * as the last argument of this function.
     *
     * ### Example:
     *
     * ```
     *  I18n::setTranslator('default', function () {
     *      $package = new \Aura\Intl\Package();
     *      $package->setMessages([
     *          'Cake' => 'Gâteau'
     *      ]);
     *      return $package;
     *  }, 'fr_FR');
     *
     *  $translator = I18n::translator('default', 'fr_FR');
     *  echo $translator->translate('Cake');
     * ```
     *
     * You can also use the `Cake\I18n\MessagesFileLoader` class to load a specific
     * file from a folder. For example for loading a `my_translations.po` file from
     * the `src/Locale/custom` folder, you would do:
     *
     * ```
     * I18n::translator(
     *  'default',
     *  'fr_FR',
     *  new MessagesFileLoader('my_translations', 'custom', 'po');
     * );
     * ```
     *
     * @deprecated 3.5 Use getTranslator() and setTranslator()
     * @param string $name The domain of the translation messages.
     * @param string|null $locale The locale for the translator.
     * @param callable|null $loader A callback function or callable class responsible for
     * constructing a translations package instance.
     * @return \Aura\Intl\TranslatorInterface|null The configured translator.
     * @throws \Aura\Intl\Exception
     */
    public static function translator($name = 'default', $locale = null, callable $loader = null)
    {
        deprecationWarning(
            'I18n::translator() is deprecated. ' .
            'Use I18n::setTranslator()/getTranslator() instead.'
        );
        if ($loader !== null) {
            static::setTranslator($name, $loader, $locale);

            return null;
        }

        return self::getTranslator($name, $locale);
    }

    /**
     * Sets a translator.
     *
     * Configures future translators, this is achieved by passing a callable
     * as the last argument of this function.
     *
     * ### Example:
     *
     * ```
     *  I18n::setTranslator('default', function () {
     *      $package = new \Aura\Intl\Package();
     *      $package->setMessages([
     *          'Cake' => 'Gâteau'
     *      ]);
     *      return $package;
     *  }, 'fr_FR');
     *
     *  $translator = I18n::getTranslator('default', 'fr_FR');
     *  echo $translator->translate('Cake');
     * ```
     *
     * You can also use the `Cake\I18n\MessagesFileLoader` class to load a specific
     * file from a folder. For example for loading a `my_translations.po` file from
     * the `src/Locale/custom` folder, you would do:
     *
     * ```
     * I18n::setTranslator(
     *  'default',
     *  new MessagesFileLoader('my_translations', 'custom', 'po'),
     *  'fr_FR'
     * );
     * ```
     *
     * @param string $name The domain of the translation messages.
     * @param callable $loader A callback function or callable class responsible for
     *   constructing a translations package instance.
     * @param string|null $locale The locale for the translator.
     * @return void
     */
    public static function setTranslator($name, callable $loader, $locale = null)
    {
        $locale = $locale ?: static::getLocale();

        $translators = static::translators();
        $loader = $translators->setLoaderFallback($name, $loader);
        $packages = $translators->getPackages();
        $packages->set($name, $locale, $loader);
    }

    /**
     * Returns an instance of a translator that was configured for the name and locale.
     *
     * If no locale is passed then it takes the value returned by the `getLocale()` method.
     *
     * @param string $name The domain of the translation messages.
     * @param string|null $locale The locale for the translator.
     * @return \Aura\Intl\TranslatorInterface The configured translator.
     * @throws \Aura\Intl\Exception
     */
    public static function getTranslator($name = 'default', $locale = null)
    {
        $translators = static::translators();

        if ($locale) {
            $currentLocale = $translators->getLocale();
            $translators->setLocale($locale);
        }

        $translator = $translators->get($name);

        if (isset($currentLocale)) {
            $translators->setLocale($currentLocale);
        }

        return $translator;
    }

    /**
     * Registers a callable object that can be used for creating new translator
     * instances for the same translations domain. Loaders will be invoked whenever
     * a translator object is requested for a domain that has not been configured or
     * loaded already.
     *
     * Registering loaders is useful when you need to lazily use translations in multiple
     * different locales for the same domain, and don't want to use the built-in
     * translation service based of `gettext` files.
     *
     * Loader objects will receive two arguments: The domain name that needs to be
     * built, and the locale that is requested. These objects can assemble the messages
     * from any source, but must return an `Aura\Intl\Package` object.
     *
     * ### Example:
     *
     * ```
     *  use Cake\I18n\MessagesFileLoader;
     *  I18n::config('my_domain', function ($name, $locale) {
     *      // Load src/Locale/$locale/filename.po
     *      $fileLoader = new MessagesFileLoader('filename', $locale, 'po');
     *      return $fileLoader();
     *  });
     * ```
     *
     * You can also assemble the package object yourself:
     *
     * ```
     *  use Aura\Intl\Package;
     *  I18n::config('my_domain', function ($name, $locale) {
     *      $package = new Package('default');
     *      $messages = (...); // Fetch messages for locale from external service.
     *      $package->setMessages($message);
     *      $package->setFallback('default');
     *      return $package;
     *  });
     * ```
     *
     * @param string $name The name of the translator to create a loader for
     * @param callable $loader A callable object that should return a Package
     * instance to be used for assembling a new translator.
     * @return void
     */
    public static function config($name, callable $loader)
    {
        static::translators()->registerLoader($name, $loader);
    }

    /**
     * Sets the default locale to use for future translator instances.
     * This also affects the `intl.default_locale` PHP setting.
     *
     * When called with no arguments it will return the currently configure
     * locale as stored in the `intl.default_locale` PHP setting.
     *
     * @deprecated 3.5 Use setLocale() and getLocale().
     * @param string|null $locale The name of the locale to set as default.
     * @return string|null The name of the default locale.
     */
    public static function locale($locale = null)
    {
        deprecationWarning(
            'I18n::locale() is deprecated. ' .
            'Use I18n::setLocale()/getLocale() instead.'
        );
        if (!empty($locale)) {
            static::setLocale($locale);

            return null;
        }

        return self::getLocale();
    }

    /**
     * Sets the default locale to use for future translator instances.
     * This also affects the `intl.default_locale` PHP setting.
     *
     * @param string $locale The name of the locale to set as default.
     * @return void
     */
    public static function setLocale($locale)
    {
        static::getDefaultLocale();
        Locale::setDefault($locale);
        if (isset(static::$_collection)) {
            static::translators()->setLocale($locale);
        }
    }

    /**
     * Will return the currently configure locale as stored in the
     * `intl.default_locale` PHP setting.
     *
     * @return string The name of the default locale.
     */
    public static function getLocale()
    {
        static::getDefaultLocale();
        $current = Locale::getDefault();
        if ($current === '') {
            $current = static::DEFAULT_LOCALE;
            Locale::setDefault($current);
        }

        return $current;
    }

    /**
     * This returns the default locale before any modifications, i.e.
     * the value as stored in the `intl.default_locale` PHP setting before
     * any manipulation by this class.
     *
     * @deprecated 3.5 Use getDefaultLocale()
     * @return string
     */
    public static function defaultLocale()
    {
        deprecationWarning('I18n::defaultLocale() is deprecated. Use I18n::getDefaultLocale() instead.');

        return static::getDefaultLocale();
    }

    /**
     * Returns the default locale.
     *
     * This returns the default locale before any modifications, i.e.
     * the value as stored in the `intl.default_locale` PHP setting before
     * any manipulation by this class.
     *
     * @return string
     */
    public static function getDefaultLocale()
    {
        if (static::$_defaultLocale === null) {
            static::$_defaultLocale = Locale::getDefault() ?: static::DEFAULT_LOCALE;
        }

        return static::$_defaultLocale;
    }

    /**
     * Sets the name of the default messages formatter to use for future
     * translator instances.
     *
     * By default the `default` and `sprintf` formatters are available.
     *
     * If called with no arguments, it will return the currently configured value.
     *
     * @deprecated 3.5 Use getDefaultFormatter() and setDefaultFormatter().
     * @param string|null $name The name of the formatter to use.
     * @return string The name of the formatter.
     */
    public static function defaultFormatter($name = null)
    {
        deprecationWarning(
            'I18n::defaultFormatter() is deprecated. ' .
            'Use I18n::setDefaultFormatter()/getDefaultFormatter() instead.'
        );

        return static::translators()->defaultFormatter($name);
    }

    /**
     * Returns the currently configured default formatter.
     *
     * @return string The name of the formatter.
     */
    public static function getDefaultFormatter()
    {
        return static::translators()->defaultFormatter();
    }

    /**
     * Sets the name of the default messages formatter to use for future
     * translator instances. By default the `default` and `sprintf` formatters
     * are available.
     *
     * @param string $name The name of the formatter to use.
     * @return void
     */
    public static function setDefaultFormatter($name)
    {
        static::translators()->defaultFormatter($name);
    }

    /**
     * Set if the domain fallback is used.
     *
     * @param bool $enable flag to enable or disable fallback
     * @return void
     */
    public static function useFallback($enable = true)
    {
        static::translators()->useFallback($enable);
    }

    /**
     * Destroys all translator instances and creates a new empty translations
     * collection.
     *
     * @return void
     */
    public static function clear()
    {
        static::$_collection = null;
    }
}
