From 8fe577786b153d9f613edc8bf7fed40b848b0fad Mon Sep 17 00:00:00 2001 From: TITUS KIPTANUI <36922214+titustum@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:04:52 +0300 Subject: [PATCH] Add Swahili Language Support (#181) * Added Swahili language * Updated README to fit swahili * Improved kiswahili grammar * Added Swahili tests * Added Swahili --- .gitignore | 1 + README.md | 1 + composer.json | 2 +- src/Concerns/ManagesCurrencyTransformers.php | 1 + src/Concerns/ManagesNumberTransformers.php | 1 + .../SwahiliCurrencyTransformer.php | 16 ++ src/Legacy/Numbers/Words/Locale/Sw.php | 248 ++++++++++++++++++ .../SwahiliNumberTransformer.php | 15 ++ .../SwahiliCurrencyTransformerTest.php | 37 +++ .../SwahiliNumberTransformerTest.php | 123 +++++++++ 10 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/CurrencyTransformer/SwahiliCurrencyTransformer.php create mode 100644 src/Legacy/Numbers/Words/Locale/Sw.php create mode 100644 src/NumberTransformer/SwahiliNumberTransformer.php create mode 100644 tests/CurrencyTransformer/SwahiliCurrencyTransformerTest.php create mode 100644 tests/NumberTransformer/SwahiliNumberTransformerTest.php diff --git a/.gitignore b/.gitignore index 73c562c6..39ed1ee8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /build/ /composer.lock *.cache +test.php diff --git a/README.md b/README.md index 991352e8..f2ff4fdd 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Note: The Currency Transformer within this library processes integers; ensure yo | Ukrainian | ua | + | + | | Uzbek | uz | + | + | | Yoruba | yo | + | + | +| Swahili | sw | + | + | ## Contributors diff --git a/composer.json b/composer.json index d0fa5890..3ab62578 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^9.6.7", + "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3.7.2" }, "autoload": { diff --git a/src/Concerns/ManagesCurrencyTransformers.php b/src/Concerns/ManagesCurrencyTransformers.php index 2908bb78..97c429ac 100644 --- a/src/Concerns/ManagesCurrencyTransformers.php +++ b/src/Concerns/ManagesCurrencyTransformers.php @@ -33,6 +33,7 @@ trait ManagesCurrencyTransformers 'ua' => Transformer\UkrainianCurrencyTransformer::class, 'uz' => Transformer\UzbekCurrencyTransformer::class, 'yo' => Transformer\YorubaCurrencyTransformer::class, + 'sw' => Transformer\SwahiliCurrencyTransformer::class, ]; /** diff --git a/src/Concerns/ManagesNumberTransformers.php b/src/Concerns/ManagesNumberTransformers.php index d641aeed..7a66b728 100644 --- a/src/Concerns/ManagesNumberTransformers.php +++ b/src/Concerns/ManagesNumberTransformers.php @@ -44,6 +44,7 @@ trait ManagesNumberTransformers 'ua' => Transformer\UkrainianNumberTransformer::class, 'uz' => Transformer\UzbekNumberTransformer::class, 'yo' => Transformer\YorubaNumberTransformer::class, + 'sw' => Transformer\SwahiliNumberTransformer::class, ]; /** diff --git a/src/CurrencyTransformer/SwahiliCurrencyTransformer.php b/src/CurrencyTransformer/SwahiliCurrencyTransformer.php new file mode 100644 index 00000000..eac0b284 --- /dev/null +++ b/src/CurrencyTransformer/SwahiliCurrencyTransformer.php @@ -0,0 +1,16 @@ +transformToCurrency($amount, 'sw', $currency); + } +} diff --git a/src/Legacy/Numbers/Words/Locale/Sw.php b/src/Legacy/Numbers/Words/Locale/Sw.php new file mode 100644 index 00000000..34b52890 --- /dev/null +++ b/src/Legacy/Numbers/Words/Locale/Sw.php @@ -0,0 +1,248 @@ + 'kumi', + 20 => 'ishirini', + 30 => 'thelathini', + 40 => 'arobaini', + 50 => 'hamsini', + 60 => 'sitini', + 70 => 'sabini', + 80 => 'themanini', + 90 => 'tisini', + 100 => 'mia' + ]; + + private static $digits = [ + 1 => 'moja', + 2 => 'mbili', + 3 => 'tatu', + 4 => 'nne', + 5 => 'tano', + 6 => 'sita', + 7 => 'saba', + 8 => 'nane', + 9 => 'tisa' + ]; + + private static $exponent = [ + 0 => '', + 3 => 'elfu', + 5 => 'laki', + 6 => 'milioni', + 9 => 'bilioni', + 12 => 'trilioni', + ]; + + private $zero = 'sifuri'; + private $and = 'na'; + private $wordSeparator = ' '; + private $minus = 'kasoro'; + + // Currency names + private static $currencyNames = [ + 'KES' => [['Shilingi ya Kenya', 'Shilingi za Kenya'], ['senti', 'senti']], + 'USD' => [['Dola ya Marekani', 'Dola za Marekani'], ['senti', 'senti']], + 'EUR' => [['Yuro', 'Yuro'], ['senti', 'senti']], + 'GBP' => [['Pauni ya Uingereza', 'Pauni za Uingereza'], ['senti', 'senti']], + 'TZS' => [['Shilingi ya Tanzania', 'Shilingi za Tanzania'], ['senti', 'senti']], + 'UGX' => [['Shilingi ya Uganda', 'Shilingi za Uganda'], ['senti', 'senti']], + 'ZAR' => [['Randi ya Afrika Kusini', 'Randi za Afrika Kusini'], ['senti', 'senti']], + 'NGN' => [['Naira ya Nigeria', 'Naira za Nigeria'], ['kobo', 'kobo']], + 'GHS' => [['Cedi ya Ghana', 'Cedi za Ghana'], ['Pesewa', 'Pesewa']], + 'RWF' => [['Faranga ya Rwanda', 'Faranga za Rwanda'], ['senti', 'senti']], + 'BWP' => [['Pula ya Botswana', 'Pula za Botswana'], ['thebe', 'thebe']], + 'INR' => [['Rupia ya India', 'Rupia za India'], ['paise', 'paise']], + 'JPY' => [['Yen ya Japani', 'Yen za Japani'], ['seni', 'seni']], + 'CNY' => [['Yuan ya China', 'Yuan za China'], ['feni', 'feni']], + 'CAD' => [['Dola ya Kanada', 'Dola za Kanada'], ['senti', 'senti']], + 'AUD' => [['Dola ya Australia', 'Dola za Australia'], ['senti', 'senti']], + 'CHF' => [['Faranga ya Uswisi', 'Faranga za Uswisi'], ['senti', 'senti']], + 'BRL' => [['Reali ya Brazil', 'Reali za Brazil'], ['sentavo', 'sentavo']], + 'MXN' => [['Peso ya Meksiko', 'Peso za Meksiko'], ['sentavo', 'sentavo']], + 'SAR' => [['Riyal ya Saudi Arabia', 'Riyal za Saudi Arabia'], ['halala', 'halala']], + 'AED' => [['Dirham ya Umoja wa Falme za Kiarabu', 'Dirham za Umoja wa Falme za Kiarabu'], ['fils', 'fils']], + 'EGP' => [['Pauni ya Misri', 'Pauni za Misri'], ['piastiri', 'piastiri']], + ]; + + + + protected function toWords($number) + { + if ($number == 0) { + return $this->zero; + } + + $isNegative = $number < 0; + $number = abs($number); + + // Handle decimal numbers + $integerPart = floor($number); + $decimalPart = $number - $integerPart; + + $result = $this->convertIntegerPart($integerPart); + + if ($decimalPart > 0) { + $result .= ' nukta ' . $this->convertDecimalPart($decimalPart); + } + + if ($isNegative) { + $result = $this->minus . $this->wordSeparator . $result; + } + + return trim($result); + } + + private function convertIntegerPart($number) + { + $result = ''; + + if ($number >= 1000000000000) { + $trillions = floor($number / 1000000000000); + $result .= self::$exponent[12] . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($trillions) . ', '; + $number %= 1000000000000; + } + + if ($number >= 1000000000) { + $billions = floor($number / 1000000000); + $result .= self::$exponent[9] . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($billions) . ', '; + $number %= 1000000000; + } + + if ($number >= 1000000) { + $millions = floor($number / 1000000); + $result .= self::$exponent[6] . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($millions) . ', '; + $number %= 1000000; + } + + if ($number >= 100000) { + $lakhs = floor($number / 100000); + $result .= self::$exponent[5] . $this->wordSeparator . $this->numberToSwahiliLessThanHundredThousand($lakhs) . ', '; + $number %= 100000; + } + + if ($number >= 1000) { + $thousands = floor($number / 1000); + $result .= self::$exponent[3] . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($thousands) . ', '; + $number %= 1000; + } + + if ($number > 0) { + $result .= $this->numberToSwahiliLessThanThousand($number); + } + + return rtrim(rtrim($result), ','); + } + + private function convertDecimalPart($decimal) + { + $decimalStr = substr(strval($decimal), 2); // Remove "0." + $result = ''; + foreach (str_split($decimalStr) as $digit) { + $result .= $this->numberToSwahiliLessThanTen((int)$digit) . $this->wordSeparator; + } + return trim($result); + } + + private function numberToSwahiliLessThanTen($number) + { + return self::$digits[$number]; + } + + private function numberToSwahiliLessThanHundred($number) + { + if ($number < 10) { + return $this->numberToSwahiliLessThanTen($number); + } + + $tens = array_values(self::$miscNumbers); + $ten = floor($number / 10); + $one = $number % 10; + + $result = $tens[$ten - 1]; + if ($one > 0) { + $result .= $this->wordSeparator . $this->and . $this->wordSeparator . $this->numberToSwahiliLessThanTen($one); + } + + return $result; + } + + private function numberToSwahiliLessThanThousand($number) + { + if ($number < 100) { + return $this->numberToSwahiliLessThanHundred($number); + } + + $hundred = floor($number / 100); + $remainder = $number % 100; + + $result = ($hundred == 1) ? self::$miscNumbers[100] : self::$miscNumbers[100] . $this->wordSeparator . $this->numberToSwahiliLessThanTen($hundred); + if ($remainder > 0) { + $result .= $this->wordSeparator . $this->and . $this->wordSeparator . $this->numberToSwahiliLessThanHundred($remainder); + } + + return $result; + } + + private function numberToSwahiliLessThanHundredThousand($number) + { + if ($number < 1000) { + return $this->numberToSwahiliLessThanThousand($number); + } + + $thousand = floor($number / 1000); + $remainder = $number % 1000; + + $result = ($thousand == 1) ? self::$exponent[3] : self::$exponent[3] . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($thousand); + if ($remainder > 0) { + $result .= $this->wordSeparator . $this->and . $this->wordSeparator . $this->numberToSwahiliLessThanThousand($remainder); + } + + return $result; + } + + public function toCurrencyWords($currency, $decimal, $fraction = null) + { + $currency = strtoupper($currency); + + if (!array_key_exists($currency, static::$currencyNames)) { + throw new NumberToWordsException( + sprintf('Currency "%s" is not available for Swahili language', $currency) + ); + } + + $currencyNames = static::$currencyNames[$currency]; + $return = trim($this->toWords($decimal)); + + // Check if singular and plural forms are available + $level = ($decimal === 1) ? 0 : 1; + $currencySingularOrPlural = isset($currencyNames[0][$level]) ? $currencyNames[0][$level] : ''; + + $return = $currencySingularOrPlural. $this->wordSeparator.$return; + + if (null !== $fraction) { + + $level = $fraction === 1 ? 0 : 1; + + // Check if singular and plural forms for fraction are available + $fractionSingularOrPlural = isset($currencyNames[1][$level]) ? $currencyNames[1][$level] : ''; + + $return .= sprintf( + '%1$s%2$s%1$s%3$s', + $this->wordSeparator, + $this->and.' '.$fractionSingularOrPlural, + trim($this->toWords($fraction)) + ); + } + + return trim(ucfirst($return)); + } + +} diff --git a/src/NumberTransformer/SwahiliNumberTransformer.php b/src/NumberTransformer/SwahiliNumberTransformer.php new file mode 100644 index 00000000..d1aa9dbc --- /dev/null +++ b/src/NumberTransformer/SwahiliNumberTransformer.php @@ -0,0 +1,15 @@ +transformToWords($number, 'sw'); + } +} diff --git a/tests/CurrencyTransformer/SwahiliCurrencyTransformerTest.php b/tests/CurrencyTransformer/SwahiliCurrencyTransformerTest.php new file mode 100644 index 00000000..2011fd19 --- /dev/null +++ b/tests/CurrencyTransformer/SwahiliCurrencyTransformerTest.php @@ -0,0 +1,37 @@ +currencyTransformer = new SwahiliCurrencyTransformer(); + } + + public function providerItConvertsMoneyAmountToWords(): array + { + return [ + [6474, 'USD', 'Dola za Marekani sitini na nne na senti sabini na nne'], + [6574, 'USD', 'Dola za Marekani sitini na tano na senti sabini na nne'], + [8174, 'USD', 'Dola za Marekani themanini na moja na senti sabini na nne'], + [8255, 'KES', 'Shilingi za Kenya themanini na mbili na senti hamsini na tano'], + [72900, 'USD', 'Dola za Marekani mia saba na ishirini na tisa'], + [89400, 'USD', 'Dola za Marekani mia nane na tisini na nne'], + [99900, 'TZS', 'Shilingi za Tanzania mia tisa na tisini na tisa'], + [100000, 'USD', 'Dola za Marekani elfu moja'], + [100100, 'USD', 'Dola za Marekani elfu moja, moja'], + [109725, 'GBP', 'Pauni za Uingereza elfu moja, tisini na saba na senti ishirini na tano'], + [110400, 'USD', 'Dola za Marekani elfu moja, mia na nne'], + [124380, 'EUR', 'Yuro elfu moja, mia mbili na arobaini na tatu na senti themanini'], + [238500, 'USD', 'Dola za Marekani elfu mbili, mia tatu na themanini na tano'], + [376600, 'USD', 'Dola za Marekani elfu tatu, mia saba na sitini na sita'], + [584600, 'USD', 'Dola za Marekani elfu tano, mia nane na arobaini na sita'], + [645900, 'USD', 'Dola za Marekani elfu sita, mia nne na hamsini na tisa'], + [723200, 'USD', 'Dola za Marekani elfu saba, mia mbili na thelathini na mbili'], + [-72925, 'UGX', 'Shilingi za Uganda kasoro mia saba na ishirini na tisa na senti ishirini na tano'], + [-89425, 'USD', 'Dola za Marekani kasoro mia nane na tisini na nne na senti ishirini na tano'], + [-99925, 'USD', 'Dola za Marekani kasoro mia tisa na tisini na tisa na senti ishirini na tano'], + ]; + } +} diff --git a/tests/NumberTransformer/SwahiliNumberTransformerTest.php b/tests/NumberTransformer/SwahiliNumberTransformerTest.php new file mode 100644 index 00000000..125d8a2a --- /dev/null +++ b/tests/NumberTransformer/SwahiliNumberTransformerTest.php @@ -0,0 +1,123 @@ +numberTransformer = new SwahiliNumberTransformer(); + } + + public function providerItConvertsNumbersToWords(): array + { + return [ + [-3, 'kasoro tatu'], + [-9539, 'kasoro elfu tisa, mia tano na thelathini na tisa'], + [0, 'sifuri'], + [1, 'moja'], + [3, 'tatu'], + [8, 'nane'], + [9, 'tisa'], + [10, 'kumi'], + [11, 'kumi na moja'], + [12, 'kumi na mbili'], + [16, 'kumi na sita'], + [19, 'kumi na tisa'], + [20, 'ishirini'], + [21, 'ishirini na moja'], + [25, 'ishirini na tano'], + [26, 'ishirini na sita'], + [30, 'thelathini'], + [31, 'thelathini na moja'], + [40, 'arobaini'], + [43, 'arobaini na tatu'], + [50, 'hamsini'], + [55, 'hamsini na tano'], + [58, 'hamsini na nane'], + [60, 'sitini'], + [67, 'sitini na saba'], + [70, 'sabini'], + [79, 'sabini na tisa'], + [80, 'themanini'], + [90, 'tisini'], + [99, 'tisini na tisa'], + [100, 'mia'], + [101, 'mia na moja'], + [102, 'mia na mbili'], + [111, 'mia na kumi na moja'], + [113, 'mia na kumi na tatu'], + [120, 'mia na ishirini'], + [121, 'mia na ishirini na moja'], + [199, 'mia na tisini na tisa'], + [203, 'mia mbili na tatu'], + [229, 'mia mbili na ishirini na tisa'], + [287, 'mia mbili na themanini na saba'], + [300, 'mia tatu'], + [356, 'mia tatu na hamsini na sita'], + [410, 'mia nne na kumi'], + [434, 'mia nne na thelathini na nne'], + [500, 'mia tano'], + [578, 'mia tano na sabini na nane'], + [660, 'mia sita na sitini'], + [666, 'mia sita na sitini na sita'], + [689, 'mia sita na themanini na tisa'], + [729, 'mia saba na ishirini na tisa'], + [894, 'mia nane na tisini na nne'], + [900, 'mia tisa'], + [909, 'mia tisa na tisa'], + [919, 'mia tisa na kumi na tisa'], + [990, 'mia tisa na tisini'], + [999, 'mia tisa na tisini na tisa'], + [1000, 'elfu moja'], + [1001, 'elfu moja, moja'], + [1010, 'elfu moja, kumi'], + [1015, 'elfu moja, kumi na tano'], + [1097, 'elfu moja, tisini na saba'], + [1100, 'elfu moja, mia'], + [1104, 'elfu moja, mia na nne'], + [1111, 'elfu moja, mia na kumi na moja'], + [1243, 'elfu moja, mia mbili na arobaini na tatu'], + [2000, 'elfu mbili'], + [2385, 'elfu mbili, mia tatu na themanini na tano'], + [3766, 'elfu tatu, mia saba na sitini na sita'], + [4000, 'elfu nne'], + [4196, 'elfu nne, mia na tisini na sita'], + [4538, 'elfu nne, mia tano na thelathini na nane'], + [5000, 'elfu tano'], + [5020, 'elfu tano, ishirini'], + [5846, 'elfu tano, mia nane na arobaini na sita'], + [6459, 'elfu sita, mia nne na hamsini na tisa'], + [7232, 'elfu saba, mia mbili na thelathini na mbili'], + [8569, 'elfu nane, mia tano na sitini na tisa'], + [9539, 'elfu tisa, mia tano na thelathini na tisa'], + [11000, 'elfu kumi na moja'], + [11001, 'elfu kumi na moja, moja'], + [21000, 'elfu ishirini na moja'], + [21512, 'elfu ishirini na moja, mia tano na kumi na mbili'], + [90000, 'elfu tisini'], + [92100, 'elfu tisini na mbili, mia'], + [212112, 'laki mbili, elfu kumi na mbili, mia na kumi na mbili'], + [720018, 'laki saba, elfu ishirini, kumi na nane'], + [999000, 'laki tisa, elfu tisini na tisa'], + [999999, 'laki tisa, elfu tisini na tisa, mia tisa na tisini na tisa'], + [1000000, 'milioni moja'], + [1001001, 'milioni moja, elfu moja, moja'], + [2000000, 'milioni mbili'], + [3248518, 'milioni tatu, laki mbili, elfu arobaini na nane, mia tano na kumi na nane'], + [4000000, 'milioni nne'], + [5000000, 'milioni tano'], + [999000000, 'milioni mia tisa na tisini na tisa'], + [999000999, 'milioni mia tisa na tisini na tisa, mia tisa na tisini na tisa'], + [999999000, 'milioni mia tisa na tisini na tisa, laki tisa, elfu tisini na tisa'], + [999999999, 'milioni mia tisa na tisini na tisa, laki tisa, elfu tisini na tisa, mia tisa na tisini na tisa'], + [1174315110, 'bilioni moja, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi'], + [1174315119, 'bilioni moja, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi na tisa'], + [1800000006, 'bilioni moja, milioni mia nane, sita'], + [15174315119, 'bilioni kumi na tano, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi na tisa'], + [35174315119, 'bilioni thelathini na tano, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi na tisa'], + [935174315119, 'bilioni mia tisa na thelathini na tano, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi na tisa'], + [2935174315119, 'trilioni mbili, bilioni mia tisa na thelathini na tano, milioni mia na sabini na nne, laki tatu, elfu kumi na tano, mia na kumi na tisa'] + ]; + } +}