Merge branch '2.x' into 3.x

* 2.x:
  added the Intl extension
This commit is contained in:
Fabien Potencier
2019-08-10 16:03:35 +02:00
36 changed files with 1349 additions and 3 deletions
+2
View File
@@ -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:
+9 -2
View File
@@ -19,8 +19,15 @@
* 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
+41
View File
@@ -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
+29
View File
@@ -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());
+44
View File
@@ -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
+44
View File
@@ -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
+73
View File
@@ -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
+31
View File
@@ -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
+67
View File
@@ -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
+117
View File
@@ -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
+31
View File
@@ -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
+12
View File
@@ -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
+44
View File
@@ -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
+44
View File
@@ -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
+44
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
vendor/
composer.lock
phpunit.xml
.phpunit.result.cache
+19
View File
@@ -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.
+29
View File
@@ -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
+39
View File
@@ -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"
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Twig Intl Extension Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
+354
View File
@@ -0,0 +1,354 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Intl;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Intl\Currencies;
use Symfony\Component\Intl\Languages;
use Symfony\Component\Intl\Locales;
use Symfony\Component\Intl\Timezones;
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
final class IntlExtension extends AbstractExtension
{
private const DATE_FORMATS = [
'none' => \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];
}
}
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,10 @@
--TEST--
"currency_symbol" filter
--TEMPLATE--
{{ 'EUR'|currency_symbol }}
{{ 'JPY'|currency_symbol }}
--DATA--
return [];
--EXPECT--
¥
@@ -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
@@ -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
@@ -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%
@@ -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
@@ -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)
@@ -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)
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Intl\Tests;
use Twig\Extra\Intl\IntlExtension;
use Twig\Test\IntegrationTestCase;
class IntegrationTest extends IntegrationTestCase
{
public function getExtensions()
{
return [
new IntlExtension(),
];
}
public function getFixturesDir()
{
return __DIR__.'/Fixtures/';
}
}
@@ -14,6 +14,7 @@ namespace Twig\Extra\TwigExtraBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Twig\Extra\Html\HtmlExtension;
use Twig\Extra\Intl\IntlExtension;
use Twig\Extra\Markdown\MarkdownExtension;
class Configuration implements ConfigurationInterface
@@ -39,6 +40,14 @@ class Configuration implements ConfigurationInterface
->end()
;
$rootNode
->children()
->arrayNode('intl')
->{class_exists(IntlExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->end()
->end()
;
return $treeBuilder;
}
}
@@ -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');
}
}
}
@@ -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 = [
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults public="false" />
<service id="twig.extension.intl" class="Twig\Extra\Html\IntlExtension">
<tag name="twig.extension" />
</service>
</services>
</container>