Skip to content

Commit

Permalink
Generate Currency from the CLDR
Browse files Browse the repository at this point in the history
Drop Repository::$currencies
Drop CurrencyCollection
Drop CurrencyData
  • Loading branch information
olvlvl committed Aug 9, 2024
1 parent 7848e85 commit d803673
Show file tree
Hide file tree
Showing 21 changed files with 1,053 additions and 452 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

### New features

- `LocaleId` is a representation of an available locale.
- `LocaleId` is generated from the CLDR. It represents an available locale ID.
- `LocaleNotAvailable` is thrown when a requested locale ID is not available.
- `Currency` is generated from the CLDR. It represents a currency, including fraction information.
- Added the `Warmable` interface to features that can warm the CLDR cache.

### Backward Incompatible Changes
Expand All @@ -20,6 +21,10 @@
- `LocalizedListFormatter:TYPE_*` constants are replaced by the `ListType` enum.
- `DateTimeFormatter::WIDTH_*` constants are replaced by the `DateTimeFormatLength` enum.
- `$clrd->locales['fr']` is replaced by `$clrd->locale_for('fr')`.
- `$cldr->currencies['USD']` is replaced by `Currency::of('USD')`.
- Removed `Repository::$currencies`, use `Currency::CODES` instead.
- Removed `Supplemental::$currency_data`, use `Currency` instead.
- Removed `CurrencyData`, use `Currency` instead.

### Deprecated Features

Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ GENERATE=./generator/generate

.PHONY=generate
generate: \
lib/Currency.php \
lib/LocaleId.php \
lib/Locale/HasContextTransforms.php \
lib/Units/SequenceCompanion.php \
lib/Units/UnitsCompanion.php

lib/Currency.php: generator/src/Command/GenerateCurrency.php
$(GENERATE) $@

lib/LocaleId.php: generator/src/Command/GenerateLocaleId.php
$(GENERATE) $@

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ as formatters for numbers, currencies, dates and times, units, sequences, lists
```php
<?php

use ICanBoogie\CLDR\Currency;

/* @var ICanBoogie\CLDR\Repository $repository */

# You get a locale from the repository, here the locale for French.
Expand Down Expand Up @@ -88,8 +90,8 @@ $repository->plurals->rule_for(2, 'fr'); // other
$repository->plurals->rule_for(2, 'ar'); // two

# You can access currencies and their localized data
$euro = $repository->currencies['EUR'];
$fr_euro = $euro->localize('fr');
$euro = Currency::of('EUR');
$fr_euro = $euro->localize($fr);
echo $fr_euro->name;
echo $fr_euro->name_for(1); // euro
echo $fr_euro->name_for(10); // euros
Expand Down
19 changes: 10 additions & 9 deletions docs/Numbers.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ in the given locale.
<?php

/**
* @var ICanBoogie\CLDR\Locale $de
* @var ICanBoogie\CLDR\Locale $de
*/

echo $de->numbers->default_numbering_system;
Expand Down Expand Up @@ -86,7 +86,7 @@ You can format a number with a given pattern using the `format_number()` functio
<?php

/**
* @var ICanBoogie\CLDR\Repository $repository
* @var ICanBoogie\CLDR\Repository $repository
*/

echo $repository->format_number(4123.37, "#,#00.#0");
Expand Down Expand Up @@ -164,23 +164,24 @@ echo $localized_formatter->format(123456.78);
```php
<?php

use ICanBoogie\CLDR\Currency;

/**
* @var ICanBoogie\CLDR\Repository $cldr
* @var ICanBoogie\CLDR\Territory $territory
* @var ICanBoogie\CLDR\Locale $locale
* @var string $locale_id
*/

$currency_code = 'EUR';

# You can obtain a currency from CLDR using its code
$currency = $cldr->currencies[$currency_code];
# You can get a currency with its code
$currency = Currency::of($currency_code);

# You can obtain the main currency from a territory
# You can get the main currency from a territory
$currency = $territory->currency;

# You can localize a currency, to get its local name, symbol, or format a number
$localized_currency = $currency->localize($locale_id);
# You can localize a currency to get its local name, symbol, or format a number
$localized_currency = $currency->localize($locale);

echo $localized_currency->name;
// euro
Expand Down Expand Up @@ -209,7 +210,7 @@ other. ICanBoogie's CLDR makes it easy to find the plural rules for any numeric
<?php

/**
* @var ICanBoogie\CLDR\Repository $cldr
* @var ICanBoogie\CLDR\Repository $cldr
*/

$cldr->plurals->rules_for('fr'); // [ 'one', 'other' ]
Expand Down
202 changes: 202 additions & 0 deletions generator/src/Command/GenerateCurrency.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<?php

namespace ICanBoogie\CLDR\Generator\Command;

use ICanBoogie\CLDR\Currency;
use ICanBoogie\CLDR\LocalizedCurrency;
use ICanBoogie\CLDR\Repository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarExporter\VarExporter;
use function ICanBoogie\CLDR\Generator\indent;

#[AsCommand('lib/Currency.php')]
final class GenerateCurrency extends Command
{
private const GENERATED_FILE = 'lib/Currency.php';

public function __construct(
private readonly Repository $repository
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
/**
* @var string[] $codes
*
* @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-full/main/en-001/currencies.json
* @phpstan-ignore-next-line
*/
$codes = array_keys($this->repository->locale_for('en-001')['currencies']);

/**
* @var array<string, array{
* _rounding: string,
* _digits: string,
* _cashRounding?: string,
* _cashDigits?: string
* }> $fractions
*
* @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json
* @phpstan-ignore-next-line
*/
$fractions = $this->repository->supplemental['currencyData']['fractions'];

$contents = $this->render(
codes: indent(VarExporter::export($codes), 1),
fractions: indent(VarExporter::export($fractions), 1),
);

file_put_contents(self::GENERATED_FILE, $contents);

return self::SUCCESS;
}

private function render(
string $codes,
string $fractions,
): string {
$class = __CLASS__;

return <<<PHP
<?php
/**
* CODE GENERATED; DO NOT EDIT.
*
* {@see \\$class}
*/
namespace ICanBoogie\CLDR;
use ICanBoogie\CLDR\Supplemental\Fraction;
/**
* Representation of a currency.
*
* @link https://www.unicode.org/reports/tr35/tr35-72/tr35-numbers.html#Currencies
*/
final class Currency
{
/**
* @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-numbers-modern/main/en-001/currencies.json
*/
public const CODES =
$codes;
/**
* @link https://github.com/unicode-org/cldr-json/blob/45.0.0/cldr-json/cldr-core/supplemental/currencyData.json
*/
private const FRACTIONS =
$fractions;
private const FRACTIONS_FALLBACK = 'DEFAULT';
/**
* Whether a currency code is defined.
*
* @param string \$code
* A currency code; for example, EUR.
*/
public static function is_defined(string \$code): bool
{
return in_array(\$code, self::CODES);
}
/**
* @param string \$code
* A currency code; for example, EUR.
*
* @throws CurrencyNotDefined
*/
public static function assert_is_defined(string \$code): void
{
self::is_defined(\$code)
or throw new CurrencyNotDefined(\$code);
}
/**
* Returns a {@see CurrencyCode} of the specified code.
*
* @param string \$code
* A currency code; for example, EUR.
*
* @throws CurrencyNotDefined
*/
public static function of(string \$code): self
{
static \$instances;
self::assert_is_defined(\$code);
return \$instances[\$code] ??= new self(\$code, self::fraction_for(\$code));
}
/**
* Returns the {@see Fraction} for the specified currency code.
*
* @param string \$code
* * A currency code; for example, EUR.
*/
private static function fraction_for(string \$code): Fraction
{
static \$default_fraction;
\$data = self::FRACTIONS[\$code] ?? null;
if (!\$data)
{
return \$default_fraction ??= self::fraction_for(self::FRACTIONS_FALLBACK);
}
return Fraction::from(\$data);
}
/**
* @param string \$code
* A currency code; for example, EUR.
*/
private function __construct(
public readonly string \$code,
public readonly Fraction \$fraction,
) {
}
/**
* Returns the {@see \$code} of the currency.
*/
public function __toString() : string
{
return \$this->code;
}
public function __serialize(): array
{
return [ 'code' => \$this->code ];
}
/**
* @param array{ code: string } \$data
*/
public function __unserialize(array \$data): void
{
\$this->code = \$data['code'];
\$this->fraction = self::fraction_for(\$this->code);
}
/**
* Returns a localized currency.
*/
public function localize(Locale \$locale): LocalizedCurrency
{
return new LocalizedCurrency(\$this, \$locale);
}
}
PHP;
}
}
20 changes: 11 additions & 9 deletions generator/src/Command/GenerateLocaleId.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ final class LocaleId
{
/**
* Whether a locale ID is available.
*
* @param string \$value
* A locale identifier; for example, fr-BE
*/
public static function is_available(string \$value): bool
{
Expand All @@ -73,6 +76,9 @@ public static function is_available(string \$value): bool
}
/**
* @param string \$value
* A locale identifier; for example, fr-BE
*
* @throws LocaleNotAvailable
*/
public static function assert_is_available(string \$value): void
Expand All @@ -81,31 +87,27 @@ public static function assert_is_available(string \$value): void
or throw new LocaleNotAvailable(\$value);
}
/**
* @var array<string, self>
* Where _key_ is a locale identifier.
*/
private static array \$instances = [];
/**
* Returns a {@see LocaleId} of a value.
*
* Note: If the locale has a parent locale, that locale is used instead.
*
* @param string \$value
* A locale identifier.
* A locale identifier; for example, fr-BE
*
* @throws InvalidArgumentException if the locale is not available.
* @throws LocaleNotAvailable
*/
public static function of(string \$value): self
{
static \$instances;
self::assert_is_available(\$value);
if (isset(self::PARENT_LOCALES[\$value])) {
\$value = self::PARENT_LOCALES[\$value];
}
return self::\$instances[\$value] ??= new self(\$value);
return \$instances[\$value] ??= new self(\$value);
}
private function __construct(
Expand Down
2 changes: 2 additions & 0 deletions generator/src/ContainerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ICanBoogie\CLDR\Cache\CacheCollection;
use ICanBoogie\CLDR\Cache\FileCache;
use ICanBoogie\CLDR\Cache\RuntimeCache;
use ICanBoogie\CLDR\Generator\Command\GenerateCurrency;
use ICanBoogie\CLDR\Generator\Command\GenerateHasContextTransforms;
use ICanBoogie\CLDR\Generator\Command\GenerateLocaleId;
use ICanBoogie\CLDR\Generator\Command\GenerateSequenceCompanion;
Expand All @@ -20,6 +21,7 @@ final class ContainerProvider
private const COMMANDS = [
GenerateLocaleId::class,
GenerateHasContextTransforms::class,
GenerateCurrency::class,
GenerateSequenceCompanion::class,
GenerateUnitsCompanion::class,
];
Expand Down
Loading

0 comments on commit d803673

Please sign in to comment.