diff --git a/.travis.yml b/.travis.yml index ba56a4c0b..d36161487 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,13 @@ before_install: install: - travis_retry composer install - (cd extra/html-extra && travis_retry composer install) + - (cd extra/intl-extra && travis_retry composer install) - (cd extra/markdown-extra && travis_retry composer install) script: - ./vendor/bin/simple-phpunit - (cd extra/html-extra && ./vendor/bin/simple-phpunit) + - (cd extra/intl-extra && ./vendor/bin/simple-phpunit) - (cd extra/markdown-extra && ./vendor/bin/simple-phpunit) jobs: diff --git a/CHANGELOG b/CHANGELOG index c999e2563..b577bb9d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,14 @@ * 2.12.0 (2019-XX-XX) - * added the Markdown extension in the "extra" repositories - * added the HtmlExtension extension in the "extra" repositories + * added Intl extension in the "extra" repositories: "country_name", + "currency_name", "currency_symbol", "language_name", "locale_name", + "timezone_name", "country_timezones", "format_currency", "format_number", + "format_*_number", "format_datetime", "format_date", and "format_time" + filters + * added the Markdown extension in the "extra" repositories: "markdown_to_html", + and "html_to_markdown" filters + * added the HtmlExtension extension in the "extra" repositories: "date_uri" + filter, and "html_classes" function * fixed cache when opcache is installed but disabled * fixed using macros in arrow functions diff --git a/doc/filters/country_name.rst b/doc/filters/country_name.rst new file mode 100644 index 000000000..9bc7a059b --- /dev/null +++ b/doc/filters/country_name.rst @@ -0,0 +1,41 @@ +``country_name`` +================ + +.. versionadded:: 2.12 + The ``country_name`` filter was added in Twig 2.12. + +The ``country_name`` filter returns the country name given its ISO-3166 +two-letter code: + +.. code-block:: twig + + {# France #} + {{ 'FR'|country_name }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# États-Unis #} + {{ 'US'|country_name('fr') }} + +.. note:: + + The ``country_name`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/doc/filters/country_timezones.rst b/doc/filters/country_timezones.rst new file mode 100644 index 000000000..dc987753c --- /dev/null +++ b/doc/filters/country_timezones.rst @@ -0,0 +1,29 @@ +``country_timezones`` +===================== + +.. versionadded:: 2.12 + The ``country_timezones`` filter was added in Twig 2.12. + +The ``country_timezones`` filter returns the names of the countries associated +with a given timezone: + +.. code-block:: twig + + {# Europe/Paris #} + {{ 'FR'|country_timezones|join(', ') }} + +.. note:: + + The ``country_timezones`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); diff --git a/doc/filters/currency_name.rst b/doc/filters/currency_name.rst new file mode 100644 index 000000000..ca46ec9c7 --- /dev/null +++ b/doc/filters/currency_name.rst @@ -0,0 +1,44 @@ +``currency_name`` +================ + +.. versionadded:: 2.12 + The ``currency_name`` filter was added in Twig 2.12. + +The ``currency_name`` filter returns the currency name given its three-letter +code: + +.. code-block:: twig + + {# Euro #} + {{ 'EUR'|currency_name }} + + {# Japanese Yen #} + {{ 'JPY'|currency_name }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# yen japonais #} + {{ 'JPY'|currency_name('fr_FR') }} + +.. note:: + + The ``currency_name`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/doc/filters/currency_symbol.rst b/doc/filters/currency_symbol.rst new file mode 100644 index 000000000..bf13bbbb9 --- /dev/null +++ b/doc/filters/currency_symbol.rst @@ -0,0 +1,44 @@ +``currency_symbol`` +=================== + +.. versionadded:: 2.12 + The ``currency_symbol`` filter was added in Twig 2.12. + +The ``currency_symbol`` filter returns the currency name given its three-letter +code: + +.. code-block:: twig + + {# € #} + {{ 'EUR'|currency_symbol }} + + {# ¥ #} + {{ 'JPY'|currency_symbol }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# ¥ #} + {{ 'JPY'|currency_symbol('fr') }} + +.. note:: + + The ``currency_symbol`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/doc/filters/format_currency.rst b/doc/filters/format_currency.rst new file mode 100644 index 000000000..8b91a7e0d --- /dev/null +++ b/doc/filters/format_currency.rst @@ -0,0 +1,73 @@ +``format_currency`` +=================== + +.. versionadded:: 2.12 + The ``format_currency`` filter was added in Twig 2.12. + +The ``format_currency`` filter formats a number as a currency: + +.. code-block:: twig + + {# €1,000,000.00 #} + {{ '1000000'|format_currency('EUR') }} + +You can pass attributes to tweak the output: + +.. code-block:: twig + + {# €12.34 #} + {{ '12.345'|format_currency('EUR', {rounding_mode: 'floor'}) }} + + {# €1,000,000.0000 #} + {{ '1000000'|format_currency('EUR', {fraction_digit: 4}) }} + +The list of supported options: + +* ``grouping_used``; +* ``decimal_always_shown``; +* ``max_integer_digit``; +* ``min_integer_digit``; +* ``integer_digit``; +* ``max_fraction_digit``; +* ``min_fraction_digit``; +* ``fraction_digit``; +* ``multiplier``; +* ``grouping_size``; +* ``rounding_mode``; +* ``rounding_increment``; +* ``format_width``; +* ``padding_position``; +* ``secondary_grouping_size``; +* ``significant_digits_used``; +* ``min_significant_digits_used``; +* ``max_significant_digits_used``; +* ``lenient_parse``. + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# 1.000.000,00 € #} + {{ '1000000'|format_currency('EUR', locale='de') }} + +.. note:: + + The ``format_currency`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale +* ``attrs``: A map of attributes diff --git a/doc/filters/format_date.rst b/doc/filters/format_date.rst new file mode 100644 index 000000000..ccbfbf427 --- /dev/null +++ b/doc/filters/format_date.rst @@ -0,0 +1,31 @@ +``format_date`` +=============== + +.. versionadded:: 2.12 + The ``format_date`` filter was added in Twig 2.12. + +The ``format_date`` filter formats a date. It behaves in the exact same way as +the ``format_datetime`` filter, but without the time. + +.. note:: + + The ``format_date`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale +* ``dateFormat``: The date format +* ``pattern``: A date time pattern diff --git a/doc/filters/format_datetime.rst b/doc/filters/format_datetime.rst new file mode 100644 index 000000000..4ffb13d7c --- /dev/null +++ b/doc/filters/format_datetime.rst @@ -0,0 +1,67 @@ +``format_datetime`` +=================== + +.. versionadded:: 2.12 + The ``format_datetime`` filter was added in Twig 2.12. + +The ``format_datetime`` filter formats a date time: + + public function formatDateTime(Environment $env, $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): string + +.. code-block:: twig + + {# Aug 7, 2019, 11:39:12 PM #} + {{ '2019-08-07 23:39:12'|format_datetime() }} + +You can tweak the output for the date part and the time part: + +.. code-block:: twig + + {# 23:39 #} + {{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale='fr') }} + + {# 07/08/2019 #} + {{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale='fr') }} + + {# mercredi 7 août 2019 23:39:12 UTC #} + {{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale='fr') }} + +Supported values are: ``none``, ``short``, ``medium``, ``long``, and ``full``. + +For greater flexiblity, you can even define your own pattern: + +.. code-block:: twig + + {# 11 oclock PM, GMT #} + {{ '2019-08-07 23:39:12'|format_datetime(pattern="hh 'oclock' a, zzzz") }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# 7 août 2019 23:39:12 #} + {{ '2019-08-07 23:39:12'|format_datetime(locale='fr') }} + +.. note:: + + The ``format_datetime`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale +* ``dateFormat``: The date format +* ``timeFormat``: The time format +* ``pattern``: A date time pattern diff --git a/doc/filters/format_number.rst b/doc/filters/format_number.rst new file mode 100644 index 000000000..3c32725eb --- /dev/null +++ b/doc/filters/format_number.rst @@ -0,0 +1,117 @@ +``format_number`` +================= + +.. versionadded:: 2.12 + The ``format_number`` filter was added in Twig 2.12. + +The ``format_number`` filter formats a number: + +.. code-block:: twig + + {{ '12.345'|format_number }} + +You can pass attributes to tweak the output: + +.. code-block:: twig + + {# 12.34 #} + {{ '12.345'|format_number({rounding_mode: 'floor'}) }} + + {# 1000000.0000 #} + {{ '1000000'|format_number({fraction_digit: 4}) }} + +The list of supported options: + +* ``grouping_used``; +* ``decimal_always_shown``; +* ``max_integer_digit``; +* ``min_integer_digit``; +* ``integer_digit``; +* ``max_fraction_digit``; +* ``min_fraction_digit``; +* ``fraction_digit``; +* ``multiplier``; +* ``grouping_size``; +* ``rounding_mode``; +* ``rounding_increment``; +* ``format_width``; +* ``padding_position``; +* ``secondary_grouping_size``; +* ``significant_digits_used``; +* ``min_significant_digits_used``; +* ``max_significant_digits_used``; +* ``lenient_parse``. + +Besides plain numbers, the filter can also format numbers in various styles: + +.. code-block:: twig + + {# 1,234% #} + {{ '12.345'|format_number(style='percent') }} + + {# twelve point three four five #} + {{ '12.345'|format_number(style='spellout') }} + + {# 12 sec. #} + {{ '12'|format_duration_number }} + +The list of supported styles: + +* ``decimal``; +* ``currency``; +* ``percent``; +* ``scientific``; +* ``spellout``; +* ``ordinal``; +* ``duration``. + +As a shortcut, you can use the ``format_*_number`` filters by replacing `*` with +a style: + +.. code-block:: twig + + {# 1,234% #} + {{ '12.345'|format_percent_number }} + + {# twelve point three four five #} + {{ '12.345'|format_spellout_number }} + +You can pass attributes to tweak the output: + +.. code-block:: twig + + {# €12.34 #} + {{ '12.345'|format_number('EUR', {rounding_mode: 'floor'}) }} + + {# €1,000,000.0000 #} + {{ '1000000'|format_number('EUR', {fraction_digit: 4}) }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# 12,345 #} + {{ '12.345'|format_number(locale='fr') }} + +.. note:: + + The ``format_number`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale +* ``attrs``: A map of attributes +* ``style``: The style of the number output diff --git a/doc/filters/format_time.rst b/doc/filters/format_time.rst new file mode 100644 index 000000000..34ddac289 --- /dev/null +++ b/doc/filters/format_time.rst @@ -0,0 +1,31 @@ +``format_time`` +=============== + +.. versionadded:: 2.12 + The ``format_time`` filter was added in Twig 2.12. + +The ``format_time`` filter formats a time. It behaves in the exact same way as +the ``format_timetime`` filter, but without the date. + +.. note:: + + The ``format_time`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale +* ``timeFormat``: The time format +* ``pattern``: A date time pattern diff --git a/doc/filters/index.rst b/doc/filters/index.rst index ae8e8ee84..08a4250c0 100644 --- a/doc/filters/index.rst +++ b/doc/filters/index.rst @@ -9,6 +9,10 @@ Filters capitalize column convert_encoding + country_name + country_timezones + currency_name + currency_symbol data_uri date date_modify @@ -17,12 +21,19 @@ Filters filter first format + format_currency + format_date + format_datetime + format_number + format_time html_to_markdown join json_encode keys + language_name last length + locale_name lower map markdown_to_html @@ -39,6 +50,7 @@ Filters spaceless split striptags + timezone_name title trim upper diff --git a/doc/filters/language_name.rst b/doc/filters/language_name.rst new file mode 100644 index 000000000..ddc57656a --- /dev/null +++ b/doc/filters/language_name.rst @@ -0,0 +1,44 @@ +``language_name`` +================= + +.. versionadded:: 2.12 + The ``language_name`` filter was added in Twig 2.12. + +The ``language_name`` filter returns the language name given its two-letter +code: + +.. code-block:: twig + + {# German #} + {{ 'de'|language_name }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# allemand #} + {{ 'de'|language_name('fr') }} + + {# français canadien #} + {{ 'fr_CA'|language_name('fr_FR') }} + +.. note:: + + The ``language_name`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/doc/filters/locale_name.rst b/doc/filters/locale_name.rst new file mode 100644 index 000000000..d77b70db4 --- /dev/null +++ b/doc/filters/locale_name.rst @@ -0,0 +1,44 @@ +``locale_name`` +=============== + +.. versionadded:: 2.12 + The ``locale_name`` filter was added in Twig 2.12. + +The ``locale_name`` filter returns the locale name given its two-letter +code: + +.. code-block:: twig + + {# German #} + {{ 'de'|locale_name }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# allemand #} + {{ 'de'|locale_name('fr') }} + + {# français (Canada) #} + {{ 'fr_CA'|locale_name('fr_FR') }} + +.. note:: + + The ``locale_name`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/doc/filters/timezone_name.rst b/doc/filters/timezone_name.rst new file mode 100644 index 000000000..f97f7e4ef --- /dev/null +++ b/doc/filters/timezone_name.rst @@ -0,0 +1,44 @@ +``timezone_name`` +================= + +.. versionadded:: 2.12 + The ``timezone_name`` filter was added in Twig 2.12. + +The ``timezone_name`` filter returns the timezone name given its two-letter +code: + +.. code-block:: twig + + {# Central European Time (Paris) #} + {{ 'Europe/Paris'|timezone_name }} + + {# Pacific Time (Los Angeles) #} + {{ 'America/Los_Angeles'|timezone_name }} + +By default, the filter uses the current locale. You can pass it explicitly: + +.. code-block:: twig + + {# heure du Pacifique nord-américain (Los Angeles) #} + {{ 'America/Los_Angeles'|timezone_name('fr') }} + +.. note:: + + The ``timezone_name`` filter is part of the ``IntlExtension`` which is not + installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/intl-extra + + Then, add it on the Twig environment:: + + use Twig\Extra\Intl\IntlExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new IntlExtension()); + +Arguments +--------- + +* ``locale``: The locale diff --git a/extra/intl-extra/.gitignore b/extra/intl-extra/.gitignore new file mode 100644 index 000000000..76367ee5b --- /dev/null +++ b/extra/intl-extra/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +.phpunit.result.cache diff --git a/extra/intl-extra/LICENSE b/extra/intl-extra/LICENSE new file mode 100644 index 000000000..1a1869751 --- /dev/null +++ b/extra/intl-extra/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extra/intl-extra/README.md b/extra/intl-extra/README.md new file mode 100644 index 000000000..c8a453cde --- /dev/null +++ b/extra/intl-extra/README.md @@ -0,0 +1,29 @@ +Twig Intl Extension +=================== + +This package is a Twig extension that provides the following: + + * [`country_name`][1] filter: returns the country name given its ISO-3166 two-letter code; + * [`currency_name`][2] filter: returns the currency name given its three-letter code; + * [`currency_symbol`][3] filter: returns the currency symbol given its three-letter code; + * [`language_name`][4] filter: returns the language name given its two-letter code; + * [`locale_name`][5] filter: returns the language name given its two-letter code; + * [`timezone_name`][6] filter: returns the timezone name given its identifier; + * [`country_timezones`][7] filter: returns the names of the countries associated with a given timezone; + * [`format_currency`][8] filter: formats a number as a currency; + * [`format_number`][9] filter: formats a number; + * [`format_datetime`][9] filter: formats a date time; + * [`format_date`][10] filter: formats a date; + * [`format_time`][11] filter: formats a time. + +[1]: https://twig.symfony.com/country_name +[2]: https://twig.symfony.com/currency_name +[3]: https://twig.symfony.com/currency_symbol +[4]: https://twig.symfony.com/language_name +[5]: https://twig.symfony.com/locale_name +[6]: https://twig.symfony.com/timezone_name +[7]: https://twig.symfony.com/country_timezones +[8]: https://twig.symfony.com/format_currency +[9]: https://twig.symfony.com/format_number +[10]: https://twig.symfony.com/format_datetime +[11]: https://twig.symfony.com/format_time diff --git a/extra/intl-extra/composer.json b/extra/intl-extra/composer.json new file mode 100644 index 000000000..73da47c76 --- /dev/null +++ b/extra/intl-extra/composer.json @@ -0,0 +1,39 @@ +{ + "name": "twig/intl-extra", + "type": "library", + "description": "A Twig extension for Intl", + "keywords": ["twig", "intl"], + "homepage": "https://twig.symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "require": { + "php": "^7.1.3", + "twig/twig": "^2.4|^3.0", + "symfony/intl": "^4.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4@dev" + }, + "autoload": { + "psr-4" : { + "Twig\\Extra\\Intl\\" : "src/" + } + }, + "autoload-dev": { + "psr-4" : { + "Twig\\Extra\\Intl\\Tests\\" : "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.12-dev" + } + } +} diff --git a/extra/intl-extra/phpunit.xml.dist b/extra/intl-extra/phpunit.xml.dist new file mode 100644 index 000000000..7794b5467 --- /dev/null +++ b/extra/intl-extra/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./tests/ + + + + + + ./ + + ./tests + ./vendor + + + + diff --git a/extra/intl-extra/src/IntlExtension.php b/extra/intl-extra/src/IntlExtension.php new file mode 100644 index 000000000..1dc2bfd2d --- /dev/null +++ b/extra/intl-extra/src/IntlExtension.php @@ -0,0 +1,354 @@ + \IntlDateFormatter::NONE, + 'short' => \IntlDateFormatter::SHORT, + 'medium' => \IntlDateFormatter::MEDIUM, + 'long' => \IntlDateFormatter::LONG, + 'full' => \IntlDateFormatter::FULL, + ]; + private const NUMBER_TYPES = [ + 'default' => \NumberFormatter::TYPE_DEFAULT, + 'int32' => \NumberFormatter::TYPE_INT32, + 'int64' => \NumberFormatter::TYPE_INT64, + 'double' => \NumberFormatter::TYPE_DOUBLE, + 'currency' => \NumberFormatter::TYPE_CURRENCY, + ]; + private const NUMBER_STYLES = [ + 'decimal' => \NumberFormatter::DECIMAL, + 'currency' => \NumberFormatter::CURRENCY, + 'percent' => \NumberFormatter::PERCENT, + 'scientific' => \NumberFormatter::SCIENTIFIC, + 'spellout' => \NumberFormatter::SPELLOUT, + 'ordinal' => \NumberFormatter::ORDINAL, + 'duration' => \NumberFormatter::DURATION, + ]; + private const NUMBER_ATTRIBUTES = [ + 'grouping_used' => \NumberFormatter::GROUPING_USED, + 'decimal_always_shown' => \NumberFormatter::DECIMAL_ALWAYS_SHOWN, + 'max_integer_digit' => \NumberFormatter::MAX_INTEGER_DIGITS, + 'min_integer_digit' => \NumberFormatter::MIN_INTEGER_DIGITS, + 'integer_digit' => \NumberFormatter::INTEGER_DIGITS, + 'max_fraction_digit' => \NumberFormatter::MAX_FRACTION_DIGITS, + 'min_fraction_digit' => \NumberFormatter::MIN_FRACTION_DIGITS, + 'fraction_digit' => \NumberFormatter::FRACTION_DIGITS, + 'multiplier' => \NumberFormatter::MULTIPLIER, + 'grouping_size' => \NumberFormatter::GROUPING_SIZE, + 'rounding_mode' => \NumberFormatter::ROUNDING_MODE, + 'rounding_increment' => \NumberFormatter::ROUNDING_INCREMENT, + 'format_width' => \NumberFormatter::FORMAT_WIDTH, + 'padding_position' => \NumberFormatter::PADDING_POSITION, + 'secondary_grouping_size' => \NumberFormatter::SECONDARY_GROUPING_SIZE, + 'significant_digits_used' => \NumberFormatter::SIGNIFICANT_DIGITS_USED, + 'min_significant_digits_used' => \NumberFormatter::MIN_SIGNIFICANT_DIGITS, + 'max_significant_digits_used' => \NumberFormatter::MAX_SIGNIFICANT_DIGITS, + 'lenient_parse' => \NumberFormatter::LENIENT_PARSE, + ]; + private const NUMBER_ROUNDING_ATTRIBUTES = [ + 'ceiling' => \NumberFormatter::ROUND_CEILING, + 'floor' => \NumberFormatter::ROUND_FLOOR, + 'down' => \NumberFormatter::ROUND_DOWN, + 'up' => \NumberFormatter::ROUND_UP, + 'halfeven' => \NumberFormatter::ROUND_HALFEVEN, + 'halfdown' => \NumberFormatter::ROUND_HALFDOWN, + 'halfup' => \NumberFormatter::ROUND_HALFUP, + ]; + private const NUMBER_PADDONG_ATTRIBUTES = [ + 'before_prefix' => \NumberFormatter::PAD_BEFORE_PREFIX, + 'after_prefix' => \NumberFormatter::PAD_AFTER_PREFIX, + 'before_suffix' => \NumberFormatter::PAD_BEFORE_SUFFIX, + 'after_suffix' => \NumberFormatter::PAD_AFTER_SUFFIX, + ]; + private const NUMBER_TEXT_ATTRIBUTES = [ + 'positive_prefix' => \NumberFormatter::POSITIVE_PREFIX, + 'positive_suffix' => \NumberFormatter::POSITIVE_SUFFIX, + 'negative_prefix' => \NumberFormatter::NEGATIVE_PREFIX, + 'negative_suffix' => \NumberFormatter::NEGATIVE_SUFFIX, + 'padding_character' => \NumberFormatter::PADDING_CHARACTER, + 'currency_mode' => \NumberFormatter::CURRENCY_CODE, + 'default_ruleset' => \NumberFormatter::DEFAULT_RULESET, + 'public_rulesets' => \NumberFormatter::PUBLIC_RULESETS, + ]; + private const NUMBER_SYMBOLS = [ + 'decimal_separator' => \NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, + 'grouping_separator' => \NumberFormatter::GROUPING_SEPARATOR_SYMBOL, + 'pattern_separator' => \NumberFormatter::PATTERN_SEPARATOR_SYMBOL, + 'percent' => \NumberFormatter::PERCENT_SYMBOL, + 'zero_digit' => \NumberFormatter::ZERO_DIGIT_SYMBOL, + 'digit' => \NumberFormatter::DIGIT_SYMBOL, + 'minus_sign' => \NumberFormatter::MINUS_SIGN_SYMBOL, + 'plus_sign' => \NumberFormatter::PLUS_SIGN_SYMBOL, + 'currency' => \NumberFormatter::CURRENCY_SYMBOL, + 'intl_currency' => \NumberFormatter::INTL_CURRENCY_SYMBOL, + 'monetary_separator' => \NumberFormatter::MONETARY_SEPARATOR_SYMBOL, + 'exponential' => \NumberFormatter::EXPONENTIAL_SYMBOL, + 'permill' => \NumberFormatter::PERMILL_SYMBOL, + 'pad_escape' => \NumberFormatter::PAD_ESCAPE_SYMBOL, + 'infinity' => \NumberFormatter::INFINITY_SYMBOL, + 'nan' => \NumberFormatter::NAN_SYMBOL, + 'significant_digit' => \NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL, + 'monetary_grouping_separator' => \NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, + ]; + + private $dateFormatters = []; + private $numberFormatters = []; + private $dateFormatterPrototype; + private $numberFormatterPrototype; + + public function __construct(\IntlDateFormatter $dateFormatterPrototype = null, \NumberFormatter $numberFormatterPrototype = null) + { + $this->dateFormatterPrototype = $dateFormatterPrototype; + $this->numberFormatterPrototype = $numberFormatterPrototype; + } + + public function getFilters() + { + return [ + // internationalized names + new TwigFilter('country_name', [$this, 'getCountryName']), + new TwigFilter('currency_name', [$this, 'getCurrencyName']), + new TwigFilter('currency_symbol', [$this, 'getCurrencySymbol']), + new TwigFilter('language_name', [$this, 'getLanguageName']), + new TwigFilter('locale_name', [$this, 'getLocaleName']), + new TwigFilter('timezone_name', [$this, 'getTimezoneName']), + new TwigFilter('country_timezones', [$this, 'getCountryTimezones']), + + // localized formatters + new TwigFilter('format_currency', [$this, 'formatCurrency']), + new TwigFilter('format_number', [$this, 'formatNumber']), + new TwigFilter('format_*_number', [$this, 'formatNumberStyle']), + new TwigFilter('format_datetime', [$this, 'formatDateTime'], ['needs_environment' => true]), + new TwigFilter('format_date', [$this, 'formatDate'], ['needs_environment' => true]), + new TwigFilter('format_time', [$this, 'formatTime'], ['needs_environment' => true]), + ]; + } + + public function getCountryName(string $country, string $locale = null): string + { + return Countries::getName($country, $locale); + } + + public function getCurrencyName(string $currency, string $locale = null): string + { + return Currencies::getName($currency, $locale); + } + + public function getCurrencySymbol(string $currency, string $locale = null): string + { + return Currencies::getSymbol($currency, $locale); + } + + public function getLanguageName(string $language, string $locale = null): string + { + return Languages::getName($language, $locale); + } + + public function getLocaleName(string $data, string $locale = null): string + { + return Locales::getName($data, $locale); + } + + public function getTimezoneName(string $timezone, string $locale = null): string + { + return Timezones::getName($timezone, $locale); + } + + public function getCountryTimezones(string $country): array + { + return Timezones::forCountryCode($country); + } + + public function formatCurrency($amount, string $currency, array $attrs = [], string $locale = null): string + { + $formatter = $this->createNumberFormatter($locale, 'currency', $attrs); + + if (false === $ret = $formatter->formatCurrency($amount, $currency)) { + throw new RuntimeError('Unable to format the given number as a currency.'); + } + + return $ret; + } + + public function formatNumber($number, array $attrs = [], string $style = 'decimal', string $type = 'default', string $locale = null): string + { + if (!isset(self::NUMBER_TYPES[$type])) { + throw new RuntimeError(sprintf('The type "%s" does not exist, known types are: "%s".', $type, implode('", "', array_keys(self::NUMBER_TYPES)))); + } + + $formatter = $this->createNumberFormatter($locale, $style, $attrs); + + if (false === $ret = $formatter->format($number, self::NUMBER_TYPES[$type])) { + throw new RuntimeError('Unable to format the given number.'); + } + + return $ret; + } + + public function formatNumberStyle(string $style, $number, array $attrs = [], string $type = 'default', string $locale = null): string + { + return $this->formatNumber($number, $attrs, $style, $type, $locale); + } + + /** + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + */ + public function formatDateTime(Environment $env, $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): string + { + $date = \twig_date_converter($env, $date, $timezone); + $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $date->getTimezone(), $calendar); + + if (false === $ret = $formatter->format($date)) { + throw new RuntimeError('Unable to format the given date.'); + } + + return $ret; + } + + /** + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + */ + public function formatDate(Environment $env, $date, ?string $dateFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): string + { + return $this->formatDateTime($env, $date, $dateFormat, 'none', $pattern, $timezone, $calendar, $locale); + } + + /** + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + */ + public function formatTime(Environment $env, $date, ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): string + { + return $this->formatDateTime($env, $date, 'none', $timeFormat, $pattern, $timezone, $calendar, $locale); + } + + private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern = '', \DateTimeZone $timezone, string $calendar): \IntlDateFormatter + { + if (null !== $dateFormat && !isset(self::DATE_FORMATS[$dateFormat])) { + throw new RuntimeError(sprintf('The date format "%s" does not exist, known formats are: "%s".', $dateFormat, implode('", "', array_keys(self::DATE_FORMATS)))); + } + + if (null !== $timeFormat && !isset(self::DATE_FORMATS[$timeFormat])) { + throw new RuntimeError(sprintf('The time format "%s" does not exist, known formats are: "%s".', $timeFormat, implode('", "', array_keys(self::DATE_FORMATS)))); + } + + if (null === $locale) { + $locale = \Locale::getDefault(); + } + + $calendar = 'gregorian' === $calendar ? \IntlDateFormatter::GREGORIAN : \IntlDateFormatter::TRADITIONAL; + + $dateFormatValue = self::DATE_FORMATS[$dateFormat] ?? null; + $timeFormatValue = self::DATE_FORMATS[$timeFormat] ?? null; + + if ($this->dateFormatterPrototype) { + $dateFormatValue = $dateFormatValue ?: $this->dateFormatterPrototype->getDateType(); + $timeFormatValue = $timeFormatValue ?: $this->dateFormatterPrototype->getTimeType(); + $timezone = $timezone ?: $this->dateFormatterPrototype->getTimeType(); + $calendar = $calendar ?: $this->dateFormatterPrototype->getCalendar(); + $pattern = $pattern ?: $this->dateFormatterPrototype->getPattern(); + } + + $hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezone->getName().'|'.$calendar.'|'.$pattern; + + if (!isset($this->dateFormatters[$hash])) { + $this->dateFormatters[$hash] = new \IntlDateFormatter($locale, $dateFormatValue, $timeFormatValue, $timezone, $calendar, $pattern); + } + + return $this->dateFormatters[$hash]; + } + + private function createNumberFormatter(?string $locale, string $style, array $attrs = []): \NumberFormatter + { + if (!isset(self::NUMBER_STYLES[$style])) { + throw new RuntimeError(sprintf('The style "%s" does not exist, known styles are: "%s".', $style, implode('", "', array_keys(self::NUMBER_STYLES)))); + } + + if (null === $locale) { + $locale = \Locale::getDefault(); + } + + // textAttrs and symbols can only be set on the prototype as there is probably no + // use case for setting it on each call. + $textAttrs = []; + $symbols = []; + if ($this->numberFormatterPrototype) { + foreach (self::NUMBER_ATTRIBUTES as $name) { + if (!isset($attrs[$name])) { + $attrs[$name] = $this->numberFormatterPrototype->getAttribute($name); + } + } + + foreach (self::NUMBER_TEXT_ATTRIBUTES as $name) { + $textAttrs[$name] = $this->numberFormatterPrototype->getTextAttribute($name); + } + + foreach (self::NUMBER_SYMBOLS as $name) { + $symbols[$name] = $this->numberFormatterPrototype->getSymbol($name); + } + } + + ksort($attrs); + $hash = $locale.'|'.$style.'|'.json_encode($attrs).'|'.json_encode($textAttrs).'|'.json_encode($symbols); + + if (!isset($this->numberFormatters[$hash])) { + $this->numberFormatters[$hash] = new \NumberFormatter($locale, self::NUMBER_STYLES[$style]); + } + + foreach ($attrs as $name => $value) { + if (!isset(self::NUMBER_ATTRIBUTES[$name])) { + throw new RuntimeError(sprintf('The number formatter attribute "%s" does not exist, known attributes are: "%s".', $name, implode('", "', array_keys(self::NUMBER_ATTRIBUTES)))); + } + + if ('rounding_mode' === $name) { + if (!isset(self::NUMBER_ROUNDING_ATTRIBUTES[$value])) { + throw new RuntimeError(sprintf('The number formatter rounding mode "%s" does not exist, known modes are: "%s".', $value, implode('", "', array_keys(self::NUMBER_ROUNDING_ATTRIBUTES)))); + } + + $value = self::NUMBER_ROUNDING_ATTRIBUTES[$value]; + } elseif ('padding_position' === $name) { + if (!isset(self::NUMBER_PADDONG_ATTRIBUTES[$value])) { + throw new RuntimeError(sprintf('The number formatter padding position "%s" does not exist, known positions are: "%s".', $value, implode('", "', array_keys(self::NUMBER_PADDONG_ATTRIBUTES)))); + } + + $value = self::NUMBER_PADDONG_ATTRIBUTES[$value]; + } + + $this->numberFormatters[$hash]->setAttribute(self::NUMBER_ATTRIBUTES[$name], $value); + } + + foreach ($textAttrs as $name => $value) { + $this->numberFormatters[$hash]->setTextAttribute($name, $value); + } + + foreach ($symbols as $name => $value) { + $this->numberFormatters[$hash]->setSymbol($name, $value); + } + + return $this->numberFormatters[$hash]; + } +} diff --git a/extra/intl-extra/tests/Fixtures/country_name.test b/extra/intl-extra/tests/Fixtures/country_name.test new file mode 100644 index 000000000..d1b729dd1 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/country_name.test @@ -0,0 +1,14 @@ +--TEST-- +"country_name" filter +--TEMPLATE-- +{{ 'FR'|country_name }} +{{ 'US'|country_name }} +{{ 'US'|country_name('fr') }} +{{ 'CH'|country_name('fr_CA') }} +--DATA-- +return []; +--EXPECT-- +France +United States +États-Unis +Suisse diff --git a/extra/intl-extra/tests/Fixtures/country_timezones.test b/extra/intl-extra/tests/Fixtures/country_timezones.test new file mode 100644 index 000000000..727b19100 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/country_timezones.test @@ -0,0 +1,10 @@ +--TEST-- +"country_timezones" filter +--TEMPLATE-- +{{ 'FR'|country_timezones|join(', ') }} +{{ 'US'|country_timezones|join(', ') }} +--DATA-- +return []; +--EXPECT-- +Europe/Paris +America/Adak, America/Anchorage, America/Boise, America/Chicago, America/Denver, America/Detroit, America/Indiana/Knox, America/Indiana/Marengo, America/Indiana/Petersburg, America/Indiana/Tell_City, America/Indiana/Vevay, America/Indiana/Vincennes, America/Indiana/Winamac, America/Indianapolis, America/Juneau, America/Kentucky/Monticello, America/Los_Angeles, America/Louisville, America/Menominee, America/Metlakatla, America/New_York, America/Nome, America/North_Dakota/Beulah, America/North_Dakota/Center, America/North_Dakota/New_Salem, America/Phoenix, America/Sitka, America/Yakutat, Pacific/Honolulu diff --git a/extra/intl-extra/tests/Fixtures/currency_name.test b/extra/intl-extra/tests/Fixtures/currency_name.test new file mode 100644 index 000000000..d0e1cf4aa --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/currency_name.test @@ -0,0 +1,14 @@ +--TEST-- +"currency_name" filter +--TEMPLATE-- +{{ 'EUR'|currency_name }} +{{ 'JPY'|currency_name }} +{{ 'EUR'|currency_name('fr') }} +{{ 'JPY'|currency_name('fr_FR') }} +--DATA-- +return []; +--EXPECT-- +Euro +Japanese Yen +euro +yen japonais diff --git a/extra/intl-extra/tests/Fixtures/currency_symbol.test b/extra/intl-extra/tests/Fixtures/currency_symbol.test new file mode 100644 index 000000000..9212d4316 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/currency_symbol.test @@ -0,0 +1,10 @@ +--TEST-- +"currency_symbol" filter +--TEMPLATE-- +{{ 'EUR'|currency_symbol }} +{{ 'JPY'|currency_symbol }} +--DATA-- +return []; +--EXPECT-- +€ +¥ diff --git a/extra/intl-extra/tests/Fixtures/format_currency.test b/extra/intl-extra/tests/Fixtures/format_currency.test new file mode 100644 index 000000000..097815071 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/format_currency.test @@ -0,0 +1,16 @@ +--TEST-- +"format_currency" filter +--TEMPLATE-- +{{ '1000000'|format_currency('EUR') }} +{{ '1000000'|format_currency('EUR', locale='de') }} +{{ '1000000'|format_currency('EUR', {fraction_digit: 2}) }} +{{ '12.345'|format_currency('EUR', {rounding_mode: 'floor'}) }} +{{ '125000'|format_currency('YEN') }} +--DATA-- +return []; +--EXPECT-- +€1,000,000.00 +1.000.000,00 € +€1,000,000.00 +€12.34 +YEN125,000.00 diff --git a/extra/intl-extra/tests/Fixtures/format_date.test b/extra/intl-extra/tests/Fixtures/format_date.test new file mode 100644 index 000000000..a1fe8c484 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/format_date.test @@ -0,0 +1,26 @@ +--TEST-- +"format_date" filter +--TEMPLATE-- +{{ '2019-08-07 23:39:12'|format_datetime() }} +{{ '2019-08-07 23:39:12'|format_datetime(locale='fr') }} +{{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale='fr') }} +{{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale='fr') }} +{{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale='fr') }} +{{ '2019-08-07 23:39:12'|format_datetime(pattern="hh 'oclock' a, zzzz") }} + +{{ '2019-08-07 23:39:12'|format_date }} +{{ '2019-08-07 23:39:12'|format_date(locale='fr') }} +{{ '2019-08-07 23:39:12'|format_time }} +--DATA-- +return []; +--EXPECT-- +Aug 7, 2019, 11:39:12 PM +7 août 2019 23:39:12 +23:39 +07/08/2019 +mercredi 7 août 2019 23:39:12 UTC +11 oclock PM, GMT + +Aug 7, 2019 +7 août 2019 +11:39:12 PM diff --git a/extra/intl-extra/tests/Fixtures/format_number.test b/extra/intl-extra/tests/Fixtures/format_number.test new file mode 100644 index 000000000..d52e132de --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/format_number.test @@ -0,0 +1,28 @@ +--TEST-- +"format_number" filter +--TEMPLATE-- +{{ '12.345'|format_number }} +{{ '12.345'|format_number(locale='fr') }} +{{ '12.345'|format_number(style='percent') }} +{{ '12.345'|format_number(style='spellout') }} +{{ '12.345'|format_percent_number }} +{{ '12.345'|format_spellout_number }} +{{ '80.345'|format_spellout_number(locale='fr_FR') }} +{{ '80.345'|format_spellout_number(locale='fr_CH') }} +{{ '12'|format_duration_number }} +{{ '0.12'|format_percent_number({fraction_digit: 1}) }} +{{ '0.12345'|format_percent_number({rounding_mode: 'ceiling'}) }} +--DATA-- +return []; +--EXPECT-- +12.345 +12,345 +1,234% +twelve point three four five +1,234% +twelve point three four five +quatre-vingts virgule trois quatre cinq +huitante virgule trois quatre cinq +12 sec. +12.0% +13% diff --git a/extra/intl-extra/tests/Fixtures/language_name.test b/extra/intl-extra/tests/Fixtures/language_name.test new file mode 100644 index 000000000..8339ae90b --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/language_name.test @@ -0,0 +1,16 @@ +--TEST-- +"language_name" filter +--TEMPLATE-- +{{ 'de'|language_name }} +{{ 'fr'|language_name }} +{{ 'de'|language_name('fr') }} +{{ 'fr'|language_name('fr_FR') }} +{{ 'fr_CA'|language_name('fr_FR') }} +--DATA-- +return []; +--EXPECT-- +German +French +allemand +français +français canadien diff --git a/extra/intl-extra/tests/Fixtures/locale_name.test b/extra/intl-extra/tests/Fixtures/locale_name.test new file mode 100644 index 000000000..e3dc5694c --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/locale_name.test @@ -0,0 +1,16 @@ +--TEST-- +"locale_name" filter +--TEMPLATE-- +{{ 'de'|locale_name }} +{{ 'fr'|locale_name }} +{{ 'de'|locale_name('fr') }} +{{ 'fr'|locale_name('fr_FR') }} +{{ 'fr_CA'|locale_name('fr_FR') }} +--DATA-- +return []; +--EXPECT-- +German +French +allemand +français +français (Canada) diff --git a/extra/intl-extra/tests/Fixtures/timezone_name.test b/extra/intl-extra/tests/Fixtures/timezone_name.test new file mode 100644 index 000000000..501a40ad2 --- /dev/null +++ b/extra/intl-extra/tests/Fixtures/timezone_name.test @@ -0,0 +1,12 @@ +--TEST-- +"timezone_name" filter +--TEMPLATE-- +{{ 'Europe/Paris'|timezone_name }} +{{ 'America/Los_Angeles'|timezone_name }} +{{ 'America/Los_Angeles'|timezone_name('fr') }} +--DATA-- +return []; +--EXPECT-- +Central European Time (Paris) +Pacific Time (Los Angeles) +heure du Pacifique nord-américain (Los Angeles) diff --git a/extra/intl-extra/tests/IntegrationTest.php b/extra/intl-extra/tests/IntegrationTest.php new file mode 100644 index 000000000..7b191bacd --- /dev/null +++ b/extra/intl-extra/tests/IntegrationTest.php @@ -0,0 +1,30 @@ +end() ; + $rootNode + ->children() + ->arrayNode('intl') + ->{class_exists(IntlExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->end() + ->end() + ; + return $treeBuilder; } } diff --git a/extra/twig-extra-bundle/src/DependencyInjection/TwigExtraExtension.php b/extra/twig-extra-bundle/src/DependencyInjection/TwigExtraExtension.php index b8cdbb565..7a05f8710 100644 --- a/extra/twig-extra-bundle/src/DependencyInjection/TwigExtraExtension.php +++ b/extra/twig-extra-bundle/src/DependencyInjection/TwigExtraExtension.php @@ -38,5 +38,9 @@ class TwigExtraExtension extends Extension if ($this->isConfigEnabled($container, $config['markdown'])) { $loader->load('markdown.xml'); } + + if ($this->isConfigEnabled($container, $config['intl'])) { + $loader->load('intl.xml'); + } } } diff --git a/extra/twig-extra-bundle/src/MissingExtensionSuggestor.php b/extra/twig-extra-bundle/src/MissingExtensionSuggestor.php index 0c974a805..fbc12df62 100644 --- a/extra/twig-extra-bundle/src/MissingExtensionSuggestor.php +++ b/extra/twig-extra-bundle/src/MissingExtensionSuggestor.php @@ -16,9 +16,31 @@ use Twig\Error\SyntaxError; final class MissingExtensionSuggestor { private const FILTERS = [ + // HTML 'data_uri' => ['HtmlExtension', 'twig/html-extra'], - 'markdown_to_html' => ['MarkdownExtension', 'twig/markdown-extra'], + + // Markdown 'html_to_markdown' => ['MarkdownExtension', 'twig/markdown-extra'], + 'markdown_to_html' => ['MarkdownExtension', 'twig/markdown-extra'], + + // Intl + 'country_name' => ['IntlExtension', 'twig/intl-extra'], + 'currency_name' => ['IntlExtension', 'twig/intl-extra'], + 'currency_symbol' => ['IntlExtension', 'twig/intl-extra'], + 'language_name' => ['IntlExtension', 'twig/intl-extra'], + 'country_timezones' => ['IntlExtension', 'twig/intl-extra'], + 'format_currency' => ['IntlExtension', 'twig/intl-extra'], + 'format_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_decimal_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_currency_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_percent_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_scientific_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_spellout_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_ordinal_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_duration_number' => ['IntlExtension', 'twig/intl-extra'], + 'format_date' => ['IntlExtension', 'twig/intl-extra'], + 'format_datetime' => ['IntlExtension', 'twig/intl-extra'], + 'format_time' => ['IntlExtension', 'twig/intl-extra'], ]; private const FUNCTIONS = [ diff --git a/extra/twig-extra-bundle/src/Resources/config/intl.xml b/extra/twig-extra-bundle/src/Resources/config/intl.xml new file mode 100644 index 000000000..36e5fff45 --- /dev/null +++ b/extra/twig-extra-bundle/src/Resources/config/intl.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + +