From bf8e9d95f1eab3d84694f1c93ee7cf54af3c03b9 Mon Sep 17 00:00:00 2001 From: John Wick <113771065+gkangelov@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:36:08 +0200 Subject: [PATCH] Added Bulgarian currency transformer. (#169) * Added Bulgarian currency transformer. * Added Bulgarian currency. * Fixed PSR12 compatibility. Added unit tests for Bulgarian currency transformer. * Updated tests. * Removed not needed plural form in tests. * Fixed currency code in tests. * Test updates. Simplified singular and plural form. Removed unused units. * Updates to PSR12 compliance. * Final touches. * Fixed problem with teens cents in Bulgarian. * Removed spaces. * Updated README.md --------- Co-authored-by: Georgi Angelov --- README.md | 2 +- src/Concerns/ManagesCurrencyTransformers.php | 1 + .../BulgarianCurrencyTransformer.php | 86 +++++++++ .../Bulgarian/BulgarianDictionary.php | 166 ++++++++++++++++++ .../Bulgarian/BulgarianExponentInflector.php | 50 ++++++ .../BulgarianFemaleTripletTransformer.php | 40 +++++ .../BulgarianNounGenderInflector.php | 15 ++ .../Bulgarian/BulgarianTripletTransformer.php | 56 ++++++ .../BulgarianCurrencyTransformerTest.php | 29 +++ .../BulgarianNounGenderInflectorTest.php | 55 ++++++ 10 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 src/CurrencyTransformer/BulgarianCurrencyTransformer.php create mode 100644 src/Language/Bulgarian/BulgarianDictionary.php create mode 100644 src/Language/Bulgarian/BulgarianExponentInflector.php create mode 100644 src/Language/Bulgarian/BulgarianFemaleTripletTransformer.php create mode 100644 src/Language/Bulgarian/BulgarianNounGenderInflector.php create mode 100644 src/Language/Bulgarian/BulgarianTripletTransformer.php create mode 100644 tests/CurrencyTransformer/BulgarianCurrencyTransformerTest.php create mode 100644 tests/Language/Bulgarian/BulgarianNounGenderInflectorTest.php diff --git a/README.md b/README.md index 3bd90aa3..991352e8 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Note: The Currency Transformer within this library processes integers; ensure yo | Azerbaijani | az | + | + | | Belgian French | fr_BE | + | - | | Brazilian Portuguese | pt_BR | + | + | -| Bulgarian | bg | + | - | +| Bulgarian | bg | + | + | | Czech | cs | + | - | | Danish | dk | + | + | | Dutch | nl | + | - | diff --git a/src/Concerns/ManagesCurrencyTransformers.php b/src/Concerns/ManagesCurrencyTransformers.php index e950eede..2908bb78 100644 --- a/src/Concerns/ManagesCurrencyTransformers.php +++ b/src/Concerns/ManagesCurrencyTransformers.php @@ -12,6 +12,7 @@ trait ManagesCurrencyTransformers 'ar' => Transformer\ArabicCurrencyTransformer::class, 'al' => Transformer\AlbanianCurrencyTransformer::class, 'az' => Transformer\AzerbaijaniCurrencyTransformer::class, + 'bg' => Transformer\BulgarianCurrencyTransformer::class, 'de' => Transformer\GermanCurrencyTransformer::class, 'dk' => Transformer\DanishCurrencyTransformer::class, 'en' => Transformer\EnglishCurrencyTransformer::class, diff --git a/src/CurrencyTransformer/BulgarianCurrencyTransformer.php b/src/CurrencyTransformer/BulgarianCurrencyTransformer.php new file mode 100644 index 00000000..d7c54a24 --- /dev/null +++ b/src/CurrencyTransformer/BulgarianCurrencyTransformer.php @@ -0,0 +1,86 @@ +withDictionary($dictionary) + ->withWordsSeparatedBy($dictionary->getSeparator()) + ->transformNumbersBySplittingIntoPowerAwareTriplets($numberToTripletsConverter, $tripletTransformer) + ->inflectExponentByNumbers($exponentInflector) + ->build(); + + $decimal = (int) ($amount / 100); + $fraction = abs($amount % 100); + + if ($fraction === 0) { + $fraction = null; + } + + $currency = strtoupper($currency); + + if (!array_key_exists($currency, BulgarianDictionary::$currencyNames)) { + throw new NumberToWordsException( + sprintf('Currency "%s" is not available for "%s" language', $currency, get_class($this)) + ); + } + + $currencyNames = BulgarianDictionary::$currencyNames[$currency]; + + $words = []; + + $words[] = $numberTransformer->toWords($decimal); + $words[] = $nounGenderInflector->inflectNounByNumber( + $decimal, + $currencyNames[0][0], + $currencyNames[0][1], + $currencyNames[0][1], + ); + + $words[] = BulgarianDictionary::$and; + if (null !== $fraction) { + $centTransformer = (new NumberTransformerBuilder()) + ->withDictionary($dictionary) + ->withWordsSeparatedBy($dictionary->getSeparator()) + ->transformNumbersBySplittingIntoPowerAwareTriplets( + $numberToTripletsConverter, + $femaleTripletTransformer + ) + ->inflectExponentByNumbers($exponentInflector) + ->build(); + + $words[] = $centTransformer->toWords($fraction); + $words[] = $nounGenderInflector->inflectNounByNumber( + $fraction, + $currencyNames[1][0], + $currencyNames[1][1], + $currencyNames[1][1], + ); + } else { + $words[] = $dictionary->getZero(); + $words[] = $currencyNames[1][1]; + } + + return implode(' ', $words); + } +} diff --git a/src/Language/Bulgarian/BulgarianDictionary.php b/src/Language/Bulgarian/BulgarianDictionary.php new file mode 100644 index 00000000..878fb589 --- /dev/null +++ b/src/Language/Bulgarian/BulgarianDictionary.php @@ -0,0 +1,166 @@ + '', + 1 => 'сто', + 2 => 'двеста', + 3 => 'триста', + 4 => 'четиристотин', + 5 => 'петстотин', + 6 => 'шестстотин', + 7 => 'седемстотин', + 8 => 'осемстотин', + 9 => 'деветстотин' + ]; + + public static string $and = 'и'; + public static array $currencyNames = [ + 'ALL' => [['lek'], ['qindarka']], + 'AED' => [['Dirham'], ['Fils']], + 'AUD' => [['Australian dollar'], ['cent']], + 'BAM' => [['convertible marka'], ['fenig']], + 'BGN' => [['лев', 'лева'], ['стотинка', 'стотинки']], + 'BRL' => [['real'], ['centavos']], + 'BYR' => [['Belarussian rouble'], ['kopiejka']], + 'CAD' => [['Canadian dollar'], ['cent']], + 'CHF' => [['Swiss franc'], ['rapp']], + 'CYP' => [['Cypriot pound'], ['cent']], + 'CZK' => [['Czech koruna'], ['halerz']], + 'DKK' => [['Danish krone'], ['ore']], + 'DZD' => [['dinar'], ['cent']], + 'EEK' => [['kroon'], ['senti']], + 'EGP' => [['Egyptian Pound'], ['piastre']], + 'EUR' => [['euro'], ['euro-cent']], + 'GBP' => [['pound', 'pounds'], ['pence', 'pence']], + 'HKD' => [['Hong Kong dollar'], ['cent']], + 'HRK' => [['Croatian kuna'], ['lipa']], + 'HUF' => [['forint'], ['filler']], + 'ILS' => [['new sheqel', 'new sheqels'], ['agora', 'agorot']], + 'ISK' => [['Icelandic króna'], ['aurar']], + 'JPY' => [['yen'], ['sen']], + 'LTL' => [['litas'], ['cent']], + 'LVL' => [['lat'], ['sentim']], + 'LYD' => [['dinar'], ['cent']], + 'MAD' => [['dirham'], ['cent']], + 'MKD' => [['Macedonian dinar'], ['deni']], + 'MRO' => [['ouguiya'], ['khoums']], + 'MTL' => [['Maltese lira'], ['centym']], + 'NGN' => [['Naira'], ['kobo']], + 'NOK' => [['Norwegian krone'], ['oere']], + 'PHP' => [['peso'], ['centavo']], + 'PLN' => [['zloty', 'zlotys'], ['grosz']], + 'ROL' => [['Romanian leu'], ['bani']], + 'RUB' => [['Russian Federation rouble'], ['kopiejka']], + 'SAR' => [['Riyal'], ['Halalah']], + 'SEK' => [['Swedish krona'], ['oere']], + 'SIT' => [['Tolar'], ['stotinia']], + 'SKK' => [['Slovak koruna'], []], + 'TMT' => [['manat'], ['tenge']], + 'TND' => [['dinar'], ['millime']], + 'TRL' => [['lira'], ['kuruş']], + 'TRY' => [['lira'], ['kuruş']], + 'UAH' => [['hryvna'], ['cent']], + 'USD' => [['dollar'], ['cent']], + 'XAF' => [['CFA franc'], ['cent']], + 'XOF' => [['CFA franc'], ['cent']], + 'XPF' => [['CFP franc'], ['centime']], + 'YUM' => [['dinar'], ['para']], + 'ZAR' => [['rand'], ['cent']], + 'UZS' => [['sum'], ['tiyin']], + ]; + + public function getSeparator(): string + { + return $this->separator; + } + + public function getZero(): string + { + return $this->zero; + } + + public function getMinus(): string + { + return $this->minus; + } + + public function getCorrespondingUnit(int $unit): string + { + return self::$units[$unit]; + } + + public function getCorrespondingUnitFemale(int $unit): string + { + return self::$unitsFemale[$unit]; + } + + public function getCorrespondingTen(int $ten): string + { + return self::$tens[$ten]; + } + + public function getCorrespondingTeen(int $teen): string + { + return self::$teens[$teen]; + } + + public function getCorrespondingHundred(int $hundred): string + { + return self::$hundreds[$hundred]; + } +} diff --git a/src/Language/Bulgarian/BulgarianExponentInflector.php b/src/Language/Bulgarian/BulgarianExponentInflector.php new file mode 100644 index 00000000..a47113ca --- /dev/null +++ b/src/Language/Bulgarian/BulgarianExponentInflector.php @@ -0,0 +1,50 @@ +inflector = $inflector; + } + + public function inflectExponent(int $number, int $power): string + { + return $this->inflector->inflectNounByNumber( + $number, + self::$exponent[$power][0], + self::$exponent[$power][1], + self::$exponent[$power][1], + ); + } +} diff --git a/src/Language/Bulgarian/BulgarianFemaleTripletTransformer.php b/src/Language/Bulgarian/BulgarianFemaleTripletTransformer.php new file mode 100644 index 00000000..7bb91f39 --- /dev/null +++ b/src/Language/Bulgarian/BulgarianFemaleTripletTransformer.php @@ -0,0 +1,40 @@ + 0) { + $words[] = $this->dictionary->getCorrespondingHundred($hundreds); + } + + if ($tens === 1) { + $words[] = $this->dictionary->getCorrespondingTeen($units); + } + + if ($tens > 1) { + $words[] = $this->dictionary->getCorrespondingTen($tens); + } + + if ($units > 0 && ($hundreds > 0 || $tens > 1)) { + $words[] = BulgarianDictionary::$and; + } + + if ($tens != 1) { + $words[] = $this->dictionary->getCorrespondingUnitFemale($units); + } + + return implode($this->dictionary->getSeparator(), $words); + } +} diff --git a/src/Language/Bulgarian/BulgarianNounGenderInflector.php b/src/Language/Bulgarian/BulgarianNounGenderInflector.php new file mode 100644 index 00000000..200b1a7e --- /dev/null +++ b/src/Language/Bulgarian/BulgarianNounGenderInflector.php @@ -0,0 +1,15 @@ +dictionary = $dictionary; + } + + public function transformToWords(int $number, int $power): string + { + $units = $number % 10; + $tens = (int) ($number / 10) % 10; + $hundreds = (int) ($number / 100) % 10; + $words = []; + + if ($hundreds > 0) { + $words[] = $this->dictionary->getCorrespondingHundred($hundreds); + } + + if ($hundreds > 0 && $tens > 0 && $units == 0) { + $words[] = BulgarianDictionary::$and; + } + + if ($tens === 1) { + $words[] = $this->dictionary->getCorrespondingTeen($units); + } + + if ($tens > 1) { + $words[] = $this->dictionary->getCorrespondingTen($tens); + } + + if ($units > 0 && $tens !== 1) { + // Skip "one" in one thousand because in Bulgarian it's not used + if ($power == 1 && $units == 1) { + return implode($this->dictionary->getSeparator(), $words); + } else { + if ($units > 0 && ($hundreds > 0 || $tens > 0)) { + $words[] = BulgarianDictionary::$and; + } + if ($units == 2 && $power == 1) { + $words[] = $this->dictionary->getCorrespondingUnitFemale($units); + } else { + $words[] = $this->dictionary->getCorrespondingUnit($units); + } + } + } + return implode($this->dictionary->getSeparator(), $words); + } +} diff --git a/tests/CurrencyTransformer/BulgarianCurrencyTransformerTest.php b/tests/CurrencyTransformer/BulgarianCurrencyTransformerTest.php new file mode 100644 index 00000000..7732fe96 --- /dev/null +++ b/tests/CurrencyTransformer/BulgarianCurrencyTransformerTest.php @@ -0,0 +1,29 @@ +currencyTransformer = new BulgarianCurrencyTransformer(); + } + + public function providerItConvertsMoneyAmountToWords(): array + { + return [ + [100, 'BGN', 'един лев и нула стотинки'], + [200, 'BGN', 'два лева и нула стотинки'], + [500, 'BGN', 'пет лева и нула стотинки'], + [54000, 'BGN', 'петстотин и четиридесет лева и нула стотинки'], + [54100, 'BGN', 'петстотин четиридесет и един лева и нула стотинки'], + [54200, 'BGN', 'петстотин четиридесет и два лева и нула стотинки'], + [54400, 'BGN', 'петстотин четиридесет и четири лева и нула стотинки'], + [54500, 'BGN', 'петстотин четиридесет и пет лева и нула стотинки'], + [54501, 'BGN', 'петстотин четиридесет и пет лева и една стотинка'], + [54552, 'BGN', 'петстотин четиридесет и пет лева и петдесет и две стотинки'], + [54599, 'BGN', 'петстотин четиридесет и пет лева и деветдесет и девет стотинки'], + [304501, 'BGN', 'три хиляди четиридесет и пет лева и една стотинка'], + ]; + } +} diff --git a/tests/Language/Bulgarian/BulgarianNounGenderInflectorTest.php b/tests/Language/Bulgarian/BulgarianNounGenderInflectorTest.php new file mode 100644 index 00000000..d5c487fb --- /dev/null +++ b/tests/Language/Bulgarian/BulgarianNounGenderInflectorTest.php @@ -0,0 +1,55 @@ +inflectNounByNumber($number, self::$nouns[0], self::$nouns[1], self::$nouns[1]); + + self::assertEquals($expectedNoun, $inflected, "Incorrect value: '$number $inflected'!"); + } + + /** + * @dataProvider providerItInflectsThousandsByNumbers + */ + + public function providerItInflectsNounsByNumbers(): array + { + return [ + [1, 'лев'], + [2, 'лева'], + [3, 'лева'], + [4, 'лева'], + [5, 'лева'], + [10, 'лева'], + [11, 'лева'], + [19, 'лева'], + [20, 'лева'], + [21, 'лева'], + [22, 'лева'], + [29, 'лева'], + [31, 'лева'], + [101, 'лева'], + [102, 'лева'], + [1000, 'лева'], + [1001, 'лева'], + [2555, 'лева'], + [2561, 'лева'], + [10001, 'лева'], + [100001, 'лева'], + [1000001, 'лева'], + [1000000001, 'лева'], + ]; + } +}