diff --git a/README.md b/README.md index 9ccc2c5..77fc949 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,23 @@ A .NET library that converts currency values into words in Bulgarian for accounting purposes. -Example: Input: `32048.27` Outpud: `тридесет и две хиляди и четиридесет и осем лева и 27 ст.` +Example BGN: Input: `32048.27` Output: `тридесет и две хиляди и четиридесет и осем лева и 27 ст.` +Example EUR: Input: `32048.27` Output: `тридесет и две хиляди и четиридесет и осем евро и 27 ц.` ## Functionality - It takes into consideration the [grammatical gender](https://en.wikipedia.org/wiki/Grammatical_gender). +- It supports negative values. - It writes decimal fractions in the short form: `X лева и ст.` when the value is above zero, and the full word when it is under the value of `1`: `девет стотинки`. -- The current maximum value is `999999.99` and the minimum is `0.`. +- The current maximum value is `999999.99` and the minimum is `-999999.99`. + +## Supported currencies + +The library supports the following currencies through predefined descriptors: + +| Currency | Code | Major Unit | Minor Unit | Usage | +|----------|------|------------|------------|-------| +| Bulgarian Lev | BGN | лев/лева | стотинка/стотинки | `CurrencyDescriptor.Bgn` | +| Euro | EUR | евро | евроцент/евроцента | `CurrencyDescriptor.Euro` | ## AI Story This project is my first attempt to build something with GitHub Copilot, with as little intervention as possible. @@ -31,8 +42,28 @@ Or via the .NET Core command line interface: dotnet add package OneBitSoftware.Slovom ``` +## Usage + +To use the library, call the `NumbersToWords.Convert` method, passing the amount and the desired currency descriptor. + +```csharp +using OneBitSoftware.Slovom; +using OneBitSoftware.Slovom.Currencies; + +// Convert BGN +decimal amountBgn = 1234.56m; +string resultBgn = NumbersToWords.Convert(amountBgn, CurrencyDescriptor.Bgn); +// Result: "хиляда двеста тридесет и четири лева и 56 ст." + +// Convert EUR +decimal amountEur = 1234.56m; +string resultEur = NumbersToWords.Convert(amountEur, CurrencyDescriptor.Euro); +// Result: "хиляда двеста тридесет и четири евро и 56 ц." +``` + ## Examples +## BGN examples |Input|Output| |--------|-------| |0|нула лева| @@ -46,6 +77,21 @@ dotnet add package OneBitSoftware.Slovom |2014.78|две хиляди и четиринадесет лева и 78 ст.| |32478.27|тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.| + +## EURO examples +|Input| Output | +|--------|-------------------------------------------------------------------| +|0| нула евро | +|1| едно евро | +|2| две евро | +|19| деветнадесет евро | +|0.1| десет евроцента | +|1.20| едно евро и 20 ц. | +|1019.78| хиляда и деветнадесет евро и 78 ц. | +|1119.78| хиляда сто и деветнадесет евро и 78 ц. | +|2014.78| две хиляди и четиринадесет евро и 78 ц. | +|32478.27| тридесет и две хиляди четиристотин седемдесет и осем евро и 27 ц. | + ## Contributing Feel free to raise a PR to improve the code quality or add new features. diff --git a/src/OneBitSoftware.Slovom/Currencies/CurrencyDescriptor.cs b/src/OneBitSoftware.Slovom/Currencies/CurrencyDescriptor.cs new file mode 100644 index 0000000..e8547bb --- /dev/null +++ b/src/OneBitSoftware.Slovom/Currencies/CurrencyDescriptor.cs @@ -0,0 +1,82 @@ +namespace OneBitSoftware.Slovom.Currencies; + +/// +/// Represents a descriptor for a currency, providing information about major and minor currency units +/// in both singular and plural forms, as well as an abbreviated symbol for minor currency units. +/// +public sealed record CurrencyDescriptor +{ + private CurrencyDescriptor() { } + + /// + /// Gets the singular form of the major currency unit. + /// + public required string MajorCurrencyUnitSingular { get; init; } + + /// + /// Gets the plural form of the major currency unit. + /// + public required string MajorCurrencyUnitPlural { get; init; } + + /// + /// Gets the plural form of the minor currency unit. + /// + public required string MinorCurrencyUnitPlural { get; init; } + + /// + /// Gets the singular form of the minor currency unit. + /// + public required string MinorCurrencyUnitSingular { get; init; } + + /// + /// Gets the abbreviated form of the minor currency unit. + /// + public required string MinorCurrencyUnitAbbreviated { get; init; } + + /// + /// Gets the vocabulary containing word representations for numbers and units in a specific language or currency context. + /// + public required NumberWordsVocabulary Vocabulary { get; init; } + + /// + /// Gets the currency descriptor for Bulgarian Lev (BGN). + /// + public static CurrencyDescriptor Bgn => new() + { + MajorCurrencyUnitSingular = " лев", + MajorCurrencyUnitPlural = " лева", + MinorCurrencyUnitPlural = " стотинки", + MinorCurrencyUnitSingular = " стотинка", + MinorCurrencyUnitAbbreviated = "ст.", + Vocabulary = new NumberWordsVocabulary + { + MinorCurrencyUnitSingular = "една", + NumbersZeroToNineteen = ["нула", "един", "два", "три", "четири", "пет", "шест", "седем", "осем", "девет", "десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"], + SingleDigitsNeutral = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет"], + NumbersTenToNineteen = ["десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"], + TensMultiples = ["", "десет", "двадесет", "тридесет", "четиридесет", "петдесет", "шестдесет", "седемдесет", "осемдесет", "деветдесет"], + HundredsMultiples = ["", "сто", "двеста", "триста", "четиристотин", "петстотин", "шестстотин", "седемстотин", "осемстотин", "деветстотин"] + } + }; + + /// + /// Gets the currency descriptor for Euro (EUR). + /// + public static CurrencyDescriptor Euro => new() + { + MajorCurrencyUnitSingular = " евро", + MajorCurrencyUnitPlural = " евро", + MinorCurrencyUnitPlural = " евроцента", + MinorCurrencyUnitSingular = " евроцент", + MinorCurrencyUnitAbbreviated = "ц.", + Vocabulary = new NumberWordsVocabulary + { + MinorCurrencyUnitSingular = "един", + NumbersZeroToNineteen = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет", "десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"], + SingleDigitsNeutral = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет"], + NumbersTenToNineteen = ["десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"], + TensMultiples = ["", "десет", "двадесет", "тридесет", "четиридесет", "петдесет", "шестдесет", "седемдесет", "осемдесет", "деветдесет"], + HundredsMultiples = ["", "сто", "двеста", "триста", "четиристотин", "петстотин", "шестстотин", "седемстотин", "осемстотин", "деветстотин"] + } + }; +} \ No newline at end of file diff --git a/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs new file mode 100644 index 0000000..af0c7c4 --- /dev/null +++ b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs @@ -0,0 +1,44 @@ +namespace OneBitSoftware.Slovom.Currencies; + +/// +/// Represents a vocabulary collection for converting numbers and currency into words. +/// +/// +/// This class defines the language-specific words used for numbers, single digits, tens, +/// hundreds, and numbers between zero and nineteen. It is intended to support currency and +/// numeric transformation into word representations. +/// +public sealed record NumberWordsVocabulary +{ + internal NumberWordsVocabulary() { } + + /// + /// Gets or inits the word representation for the singular form of the minor currency unit. + /// + public required string MinorCurrencyUnitSingular { get; init; } + + /// + /// Gets or inits the words for numbers from zero to nineteen. + /// + public required string[] NumbersZeroToNineteen { get; init; } + + /// + /// Gets or inits the words for single digits in neutral form. + /// + public required string[] SingleDigitsNeutral { get; init; } + + /// + /// Gets or inits the words for numbers from ten to nineteen. + /// + public required string[] NumbersTenToNineteen { get; init; } + + /// + /// Gets or inits the words for multiples of ten. + /// + public required string[] TensMultiples { get; init; } + + /// + /// Gets or inits the words for multiples of a hundred. + /// + public required string[] HundredsMultiples { get; init; } +} \ No newline at end of file diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 80aa3d6..1742a01 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -1,200 +1,173 @@ -namespace OneBitSoftware.Slovom +namespace OneBitSoftware.Slovom; + +using OneBitSoftware.Slovom.Currencies; + +public static class NumbersToWords { - public static class NumbersToWords - { - private static readonly string[] under20 = ["нула", "един", "два", "три", "четири", "пет", "шест", "седем", "осем", "девет", "десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; - private static readonly string[] units = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет"]; - private static readonly string[] teens = ["десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; - private static readonly string[] tens = ["", "десет", "двадесет", "тридесет", "четиридесет", "петдесет", "шестдесет", "седемдесет", "осемдесет", "деветдесет"]; - private static readonly string[] hundreds = ["", "сто", "двеста", "триста", "четиристотин", "петстотин", "шестстотин", "седемстотин", "осемстотин", "деветстотин"]; - - private static readonly string AppendLvMale = " лев"; - private static readonly string AppendLvFemale = " лева"; - private static readonly string AppendStotinki = " стотинки"; - private static readonly string AppendStotinka = " стотинка"; - private static readonly string AppendStotinkaShort = "ст."; - - static string ConvertWholeNumber(int n) + private static string ConvertWholeNumber(int number, NumberWordsVocabulary numberWordsVocabulary) => + number switch { - if (n < 20) - return under20[n]; - else if (n < 100) - return Tens(n); - else if (n < 1000) - return Hundreds(n); - else if (n < 10000) - return Thousands(n); - else if (n < 100000) - return TensOfThousands(n); - else - return "Числото е твърде голямо"; - } + < 20 => numberWordsVocabulary.NumbersZeroToNineteen[number], + < 100 => Tens(number, numberWordsVocabulary), + < 1000 => Hundreds(number, numberWordsVocabulary), + < 10000 => Thousands(number, numberWordsVocabulary), + < 100000 => TensOfThousands(number, numberWordsVocabulary), + _ => "Числото е твърде голямо" + }; + + private static string Tens(int n, NumberWordsVocabulary numberWordsVocabulary) + { + if (n < 20) return numberWordsVocabulary.NumbersZeroToNineteen[n]; - private static string Tens(int n) - { - if (n < 20) - return under20[n]; + var i = n / 10; + var d = n % 10; - var i = n / 10; - var d = n % 10; + return numberWordsVocabulary.TensMultiples[i] + (d == 0 ? "" : " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]); + } - return tens[i] + (d == 0 ? "" : " и " + under20[d]); - } + private static string Hundreds(int n, NumberWordsVocabulary numberWordsVocabulary) + { + var i = n / 100; + var d = n % 100; - static string Hundreds(int n) + if (n < 120) { - var i = n / 100; - var d = n % 100; + if (d == 0) return numberWordsVocabulary.HundredsMultiples[i]; + + return numberWordsVocabulary.HundredsMultiples[i] + " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]; + } - if (n < 120) - { - return hundreds[i] + " и " + under20[d]; - } - else if (n >= 120 && n < 200) - { - return hundreds[i] + " " + ConvertWholeNumber(d); - } - else - { - if (d < 20) - return hundreds[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d)); + if (n < 200) return numberWordsVocabulary.HundredsMultiples[i] + " " + ConvertWholeNumber(d, numberWordsVocabulary); - return hundreds[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d)); - } - } + if (d < 20) return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d, numberWordsVocabulary)); + + return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d, numberWordsVocabulary)); + } - private static string Thousands(int n) - { - var i = n / 1000; - var d = n % 1000; + private static string Thousands(int n, NumberWordsVocabulary numberWordsVocabulary) + { + var i = n / 1000; + var d = n % 1000; - if (n == 1000) return "хиляда"; + if (n == 1000) return "хиляда"; - if (n > 1000 && n < 1099) return "хиляда и " + Tens(d); + if (n is > 1000 and < 1099) return "хиляда и " + Tens(d, numberWordsVocabulary); - if (n > 1099 && n < 2000) return "хиляда " + Hundreds(d); + if (n is > 1099 and < 2000) return "хиляда " + Hundreds(d, numberWordsVocabulary); - if (d == 0) return units[i] + " хиляди"; // 2000,3000,4000, etc + if (d == 0) return numberWordsVocabulary.SingleDigitsNeutral[i] + " хиляди"; // 2000,3000,4000, etc - if (d < 100) return units[i] + " хиляди и " + Tens(d); + if (d < 100) return numberWordsVocabulary.SingleDigitsNeutral[i] + " хиляди и " + Tens(d, numberWordsVocabulary); - return units[i] + " хиляди " + Hundreds(d); - } + return numberWordsVocabulary.SingleDigitsNeutral[i] + " хиляди " + Hundreds(d, numberWordsVocabulary); + } - private static string TensOfThousands(int number) + private static string TensOfThousands(int number, NumberWordsVocabulary numberWordsVocabulary) + { + var o = number / 10000; + var n = number % 10000; + var e = n % 1000; + var b = n / 1000; + var i = number / 1000; + var t = i % 10; + var soft = e % 100; + var ware = soft % 10; + + if (number is > 10000 and < 10099) return numberWordsVocabulary.TensMultiples[o] + " хиляди и " + Tens(n, numberWordsVocabulary); + + if (number is >= 10099 and < 11000) { - var o = number / 10000; - var n = number % 10000; - var e = n % 1000; - var b = n / 1000; - var i = number / 1000; - var t = i % 10; - var soft = e % 100; - var ware = soft % 10; - - if (number > 10000 && number < 10099) + if (soft == 0) // 10100, 10900, 10800 , etc { - return tens[o] + " хиляди и " + Tens(n); + return numberWordsVocabulary.TensMultiples[o] + " хиляди и " + Hundreds(n, numberWordsVocabulary); } - if (number >= 10099 && number < 11000) - { - if (soft == 0) // 10100, 10900, 10800 , etc - { - return tens[o] + " хиляди и " + Hundreds(n); - } + return BuildThousandsWithoutAnd(numberWordsVocabulary.TensMultiples[o], Hundreds(n, numberWordsVocabulary)); + } - return BuildThousandsWithoutAnd(tens[o], Hundreds(n)); - } + if (number is >= 11000 and < 20000) + { + if (soft != 0) return BuildThousandsWithoutAnd(numberWordsVocabulary.NumbersTenToNineteen[t], Hundreds(e, numberWordsVocabulary)); // 11100, 11900, 11800 , etc + if (e == 0) return numberWordsVocabulary.NumbersTenToNineteen[t] + " хиляди"; + + return BuildThousandsWithAnd(numberWordsVocabulary.NumbersTenToNineteen[t], Hundreds(e, numberWordsVocabulary)); + } - if (number >= 11000 && number < 20000) + if (number is > 20000 and < 99999) + { + if (b == 1) { - if (soft == 0) // 11100, 11900, 11800 , etc - { - if (e == 0) return teens[t] + " хиляди"; + if (e < 100) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди и " + Tens(e, numberWordsVocabulary); - return BuildThousandsWithAnd(teens[t], Hundreds(e)); - } + if (ware == 0) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди и " + Hundreds(e, numberWordsVocabulary); - return BuildThousandsWithoutAnd(teens[t], Hundreds(e)); + if (ware > 0) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди " + Hundreds(e, numberWordsVocabulary); } - if (number > 20000 && number < 99999) + if (b == 2) { - if (b == 1) - { - if (e < 100) return tens[o] + " и една хиляди и " + Tens(e); + if (e < 100) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди и " + Tens(e, numberWordsVocabulary); - if (ware == 0) return tens[o] + " и една хиляди и " + Hundreds(e); + if (ware == 0) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди и " + Hundreds(e, numberWordsVocabulary); - if (ware > 0) return tens[o] + " и една хиляди " + Hundreds(e); - } - - if (b == 2) - { - if (e < 100) return tens[o] + " и две хиляди и " + Tens(e); - - if (ware == 0) return tens[o] + " и две хиляди и " + Hundreds(e); + if (ware > 0) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди " + Hundreds(e, numberWordsVocabulary); + } - if (ware > 0) return tens[o] + " и две хиляди " + Hundreds(e); - } + return BuildThousandsWithAnd(Tens(i, numberWordsVocabulary), Hundreds(e, numberWordsVocabulary)); + } - return BuildThousandsWithAnd(Tens(i), Hundreds(e)); - } + if (n == 0) return numberWordsVocabulary.TensMultiples[o] + " хиляди"; // 10000,20000,30000, etc - if (n == 0) return tens[o] + " хиляди"; // 10000,20000,30000, etc + if (n < 100) return BuildThousandsWithAnd(numberWordsVocabulary.TensMultiples[o], Tens(soft, numberWordsVocabulary)); - if (n < 100) return BuildThousandsWithAnd(tens[o], Tens(soft)); + return BuildThousandsWithoutAnd(Tens(i, numberWordsVocabulary), Hundreds(e, numberWordsVocabulary)); + } - return BuildThousandsWithoutAnd(Tens(i), Hundreds(e)); - } + private static string BuildThousandsWithoutAnd(string thousands, string afterThousands) => thousands + " хиляди " + afterThousands; - static string BuildThousandsWithoutAnd(string thousands, string afterThousands) - { - return thousands + " хиляди " + afterThousands; - } + private static string BuildThousandsWithAnd(string thousands, string afterThousands) => thousands + " хиляди и " + afterThousands; - static string BuildThousandsWithAnd(string thousands, string afterThousands) - { - return thousands + " хиляди и " + afterThousands; - } + private static string AppendNegativePrefix(string numberAsWords, bool isNegativeNumber) + { + if (!isNegativeNumber) return numberAsWords; - public static string Convert(decimal number) - { - if (number == 0 || number == 0.0m) return under20[0] + AppendLvFemale; // нула лева + return "Минус " + numberAsWords; + } - number = Math.Abs(number); // Convert negative number to positive + public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) + { + if (number is < -99999.99m or > 99999.99m) throw new ArgumentOutOfRangeException(nameof(number), "Входното число трябва да бъде в интервала [-99999.99; 99999.99]"); + ArgumentNullException.ThrowIfNull(currencyDescriptor); + + if (number is 0 or 0.0m) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[0] + currencyDescriptor.MajorCurrencyUnitPlural; // нула лева, нула евро - int leva = (int)number; - int stotinki = (int)((number % 1.0m) * 100); + var isNegativeNumber = number < 0; + number = Math.Abs(number); // Convert negative number to positive - if (number == 1 && stotinki == 0) return under20[leva] + AppendLvMale; // един лев + var majorUnit = (int)number; + var minorUnit = (int)((number % 1.0m) * 100); - string levaWords = leva != 1 ? ConvertWholeNumber(leva) + AppendLvFemale : "един" + AppendLvMale; + if (number == 1 && minorUnit == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[majorUnit] + currencyDescriptor.MajorCurrencyUnitSingular, isNegativeNumber); // един лев, едно евро - string stotinkiWords; + var majorUnitWords = majorUnit != 1 ? ConvertWholeNumber(majorUnit, currencyDescriptor.Vocabulary) + currencyDescriptor.MajorCurrencyUnitPlural : currencyDescriptor.Vocabulary.NumbersZeroToNineteen[majorUnit] + currencyDescriptor.MajorCurrencyUnitSingular; - if (leva == 0) - { - if (stotinki == 0) return under20[leva] + AppendLvFemale; - if (stotinki == 1) return "една" + AppendStotinka; - if (stotinki == 2) return "две" + AppendStotinki; - if (stotinki == 10) return under20[stotinki] + AppendStotinki; - if (stotinki < 20) return under20[stotinki] + AppendStotinki; + string minorUnitWords; - stotinkiWords = stotinki.ToString() + " " + AppendStotinkaShort; - } - else - { - stotinkiWords = stotinki.ToString() + " " + AppendStotinkaShort; - } + if (majorUnit == 0) + { + if (minorUnit == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[majorUnit] + currencyDescriptor.MajorCurrencyUnitPlural, isNegativeNumber); + if (minorUnit == 1) return AppendNegativePrefix(currencyDescriptor.Vocabulary.MinorCurrencyUnitSingular + currencyDescriptor.MinorCurrencyUnitSingular, isNegativeNumber); + if (minorUnit == 2) return AppendNegativePrefix("две" + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); + if (minorUnit == 10) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[minorUnit] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); + if (minorUnit < 20) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[minorUnit] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); - if (leva == 0) - return stotinkiWords; - else if (stotinki == 0) - return levaWords; - else - return levaWords + " и " + stotinkiWords; + minorUnitWords = minorUnit + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; } + else minorUnitWords = minorUnit + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; + + if (majorUnit == 0) return AppendNegativePrefix(minorUnitWords, isNegativeNumber); + if (minorUnit == 0) return AppendNegativePrefix(majorUnitWords, isNegativeNumber); + + return AppendNegativePrefix(majorUnitWords + " и " + minorUnitWords, isNegativeNumber); } -} +} \ No newline at end of file diff --git a/src/OneBitSoftware.Slovom/OneBitSoftware.Slovom.csproj b/src/OneBitSoftware.Slovom/OneBitSoftware.Slovom.csproj index c6adef5..8948158 100644 --- a/src/OneBitSoftware.Slovom/OneBitSoftware.Slovom.csproj +++ b/src/OneBitSoftware.Slovom/OneBitSoftware.Slovom.csproj @@ -6,7 +6,7 @@ enable true OneBitSoftware.Slovom - 1.1.0 + 1.2.0 Radi Atanassov OneBit Software A .NET library for converting currency values into Bulgarian words, tailored for accounting needs. diff --git a/src/Tests/ConvertTests.cs b/src/Tests/ConvertTests.cs index a5e2543..8c8085f 100644 --- a/src/Tests/ConvertTests.cs +++ b/src/Tests/ConvertTests.cs @@ -1,82 +1,197 @@ -namespace Tests +namespace Tests; + +using Xunit; +using OneBitSoftware.Slovom; +using OneBitSoftware.Slovom.Currencies; + +public class ConvertTests { - using Xunit; - using OneBitSoftware.Slovom; + [Theory] + [InlineData(0, "нула лева")] + [InlineData(1, "един лев")] + [InlineData(2, "два лева")] + [InlineData(2.0, "два лева")] + [InlineData(9, "девет лева")] + [InlineData(10, "десет лева")] + [InlineData(11, "единадесет лева")] + [InlineData(12, "дванадесет лева")] + [InlineData(19, "деветнадесет лева")] + [InlineData(100, "сто лева")] + [InlineData(0.0, "нула лева")] + [InlineData(0.1, "десет стотинки")] + [InlineData(1.20, "един лев и 20 ст.")] + [InlineData(1.3, "един лев и 30 ст.")] + [InlineData(1.02, "един лев и 2 ст.")] + [InlineData(118, "сто и осемнадесет лева")] + [InlineData(123, "сто двадесет и три лева")] + [InlineData(1000, "хиляда лева")] + [InlineData(2000, "две хиляди лева")] + [InlineData(3000, "три хиляди лева")] + [InlineData(9000, "девет хиляди лева")] + [InlineData(123.00, "сто двадесет и три лева")] + [InlineData(1234, "хиляда двеста тридесет и четири лева")] + [InlineData(1234.78, "хиляда двеста тридесет и четири лева и 78 ст.")] + [InlineData(1019.78, "хиляда и деветнадесет лева и 78 ст.")] + [InlineData(1109.78, "хиляда сто и девет лева и 78 ст.")] + [InlineData(1119.78, "хиляда сто и деветнадесет лева и 78 ст.")] + [InlineData(2014.78, "две хиляди и четиринадесет лева и 78 ст.")] + [InlineData(4314.18, "четири хиляди триста и четиринадесет лева и 18 ст.")] + [InlineData(123.45, "сто двадесет и три лева и 45 ст.")] + [InlineData(0.01, "една стотинка")] + [InlineData(1.01, "един лев и 1 ст.")] + [InlineData(1.10, "един лев и 10 ст.")] + [InlineData(999.99, "деветстотин деветдесет и девет лева и 99 ст.")] + [InlineData(9999.99, "девет хиляди деветстотин деветдесет и девет лева и 99 ст.")] + [InlineData(10000, "десет хиляди лева")] + [InlineData(10012, "десет хиляди и дванадесет лева")] + [InlineData(10112, "десет хиляди сто и дванадесет лева")] + [InlineData(10912, "десет хиляди деветстотин и дванадесет лева")] + [InlineData(10900, "десет хиляди и деветстотин лева")] + [InlineData(11900, "единадесет хиляди и деветстотин лева")] + [InlineData(18900, "осемнадесет хиляди и деветстотин лева")] + [InlineData(20900, "двадесет хиляди и деветстотин лева")] + [InlineData(21900, "двадесет и една хиляди и деветстотин лева")] + [InlineData(22900, "двадесет и две хиляди и деветстотин лева")] + [InlineData(23901, "двадесет и три хиляди и деветстотин и един лева")] + [InlineData(12000, "дванадесет хиляди лева")] + [InlineData(11112, "единадесет хиляди сто и дванадесет лева")] + [InlineData(32478.27, "тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.")] + [InlineData(32048.27, "тридесет и две хиляди и четиридесет и осем лева и 27 ст.")] + [InlineData(32008.27, "тридесет и две хиляди и осем лева и 27 ст.")] + [InlineData(99999.99, "деветдесет и девет хиляди деветстотин деветдесет и девет лева и 99 ст.")] + public void NumberToWordsBG_ShouldReturnCorrectWords(decimal number, string expected) + { + // Act + var result = NumbersToWords.Convert(number, CurrencyDescriptor.Bgn); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(-0.01, "Минус една стотинка")] + [InlineData(-0.02, "Минус две стотинки")] + [InlineData(-0.10, "Минус десет стотинки")] + [InlineData(-0.19, "Минус деветнадесет стотинки")] + [InlineData(-0.20, "Минус 20 ст.")] + [InlineData(-1, "Минус един лев")] + [InlineData(-1.01, "Минус един лев и 1 ст.")] + [InlineData(-1.10, "Минус един лев и 10 ст.")] + [InlineData(-2, "Минус два лева")] + [InlineData(-11, "Минус единадесет лева")] + [InlineData(-20, "Минус двадесет лева")] + [InlineData(-21, "Минус двадесет и един лева")] + [InlineData(-100, "Минус сто лева")] + [InlineData(-101, "Минус сто и един лева")] + [InlineData(-111, "Минус сто и единадесет лева")] + [InlineData(-1000, "Минус хиляда лева")] + [InlineData(-1001, "Минус хиляда и един лева")] + [InlineData(-2000, "Минус две хиляди лева")] + [InlineData(-10000, "Минус десет хиляди лева")] + [InlineData(-21000, "Минус двадесет и една хиляди и нула лева")] + [InlineData(-32478.27, "Минус тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.")] + [InlineData(-0.00, "нула лева")] + public void NumberToWordsBG_ShouldReturnCorrectWordsForNegativeValues(decimal number, string expected) + { + // Act + var result = NumbersToWords.Convert(number, CurrencyDescriptor.Bgn); - public class ConvertTests + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(0, "нула евро")] + [InlineData(1, "едно евро")] + [InlineData(2, "две евро")] + [InlineData(9, "девет евро")] + [InlineData(10, "десет евро")] + [InlineData(11, "единадесет евро")] + [InlineData(12, "дванадесет евро")] + [InlineData(19, "деветнадесет евро")] + [InlineData(0.0, "нула евро")] + [InlineData(0.1, "десет евроцента")] + [InlineData(1.20, "едно евро и 20 ц.")] + [InlineData(1.3, "едно евро и 30 ц.")] + [InlineData(1.02, "едно евро и 2 ц.")] + [InlineData(100, "сто евро")] + [InlineData(118, "сто и осемнадесет евро")] + [InlineData(123, "сто двадесет и три евро")] + [InlineData(1000, "хиляда евро")] + [InlineData(2000, "две хиляди евро")] + [InlineData(3000, "три хиляди евро")] + [InlineData(9000, "девет хиляди евро")] + [InlineData(123.00, "сто двадесет и три евро")] + [InlineData(1234, "хиляда двеста тридесет и четири евро")] + [InlineData(1234.78, "хиляда двеста тридесет и четири евро и 78 ц.")] + [InlineData(1019.78, "хиляда и деветнадесет евро и 78 ц.")] + [InlineData(1109.78, "хиляда сто и девет евро и 78 ц.")] + [InlineData(1119.78, "хиляда сто и деветнадесет евро и 78 ц.")] + [InlineData(2014.78, "две хиляди и четиринадесет евро и 78 ц.")] + [InlineData(4314.18, "четири хиляди триста и четиринадесет евро и 18 ц.")] + [InlineData(123.45, "сто двадесет и три евро и 45 ц.")] + [InlineData(0.01, "един евроцент")] + [InlineData(0.02, "две евроцента")] + [InlineData(1.01, "едно евро и 1 ц.")] + [InlineData(1.10, "едно евро и 10 ц.")] + [InlineData(999.99, "деветстотин деветдесет и девет евро и 99 ц.")] + [InlineData(9999.99, "девет хиляди деветстотин деветдесет и девет евро и 99 ц.")] + [InlineData(10000, "десет хиляди евро")] + [InlineData(10012, "десет хиляди и дванадесет евро")] + [InlineData(10112, "десет хиляди сто и дванадесет евро")] + [InlineData(10912, "десет хиляди деветстотин и дванадесет евро")] + [InlineData(10900, "десет хиляди и деветстотин евро")] + [InlineData(11900, "единадесет хиляди и деветстотин евро")] + [InlineData(18900, "осемнадесет хиляди и деветстотин евро")] + [InlineData(20900, "двадесет хиляди и деветстотин евро")] + [InlineData(21900, "двадесет и една хиляди и деветстотин евро")] + [InlineData(22900, "двадесет и две хиляди и деветстотин евро")] + [InlineData(23901, "двадесет и три хиляди и деветстотин и едно евро")] + [InlineData(12000, "дванадесет хиляди евро")] + [InlineData(11112, "единадесет хиляди сто и дванадесет евро")] + [InlineData(32478.27, "тридесет и две хиляди четиристотин седемдесет и осем евро и 27 ц.")] + [InlineData(32048.27, "тридесет и две хиляди и четиридесет и осем евро и 27 ц.")] + [InlineData(32008.27, "тридесет и две хиляди и осем евро и 27 ц.")] + [InlineData(99999.99, "деветдесет и девет хиляди деветстотин деветдесет и девет евро и 99 ц.")] + public void NumberToWordsEuro_ShouldReturnCorrectWords(decimal number, string expected) { - [Theory] - [InlineData(0, "нула лева")] - [InlineData(1, "един лев")] - [InlineData(2, "два лева")] - [InlineData(2.0, "два лева")] - [InlineData(9, "девет лева")] - [InlineData(10, "десет лева")] - [InlineData(11, "единадесет лева")] - [InlineData(12, "дванадесет лева")] - [InlineData(19, "деветнадесет лева")] - [InlineData(0.0, "нула лева")] - [InlineData(0.1, "десет стотинки")] - [InlineData(1.20, "един лев и 20 ст.")] - [InlineData(1.3, "един лев и 30 ст.")] - [InlineData(1.02, "един лев и 2 ст.")] - [InlineData(118, "сто и осемнадесет лева")] - [InlineData(123, "сто двадесет и три лева")] - [InlineData(1000, "хиляда лева")] - [InlineData(2000, "две хиляди лева")] - [InlineData(3000, "три хиляди лева")] - [InlineData(9000, "девет хиляди лева")] - [InlineData(123.00, "сто двадесет и три лева")] - [InlineData(1234, "хиляда двеста тридесет и четири лева")] - [InlineData(1234.78, "хиляда двеста тридесет и четири лева и 78 ст.")] - [InlineData(1019.78, "хиляда и деветнадесет лева и 78 ст.")] - [InlineData(1109.78, "хиляда сто и девет лева и 78 ст.")] - [InlineData(1119.78, "хиляда сто и деветнадесет лева и 78 ст.")] - [InlineData(2014.78, "две хиляди и четиринадесет лева и 78 ст.")] - [InlineData(4314.18, "четири хиляди триста и четиринадесет лева и 18 ст.")] - [InlineData(123.45, "сто двадесет и три лева и 45 ст.")] - [InlineData(0.01, "една стотинка")] - [InlineData(1.01, "един лев и 1 ст.")] - [InlineData(1.10, "един лев и 10 ст.")] - [InlineData(999.99, "деветстотин деветдесет и девет лева и 99 ст.")] - [InlineData(9999.99, "девет хиляди деветстотин деветдесет и девет лева и 99 ст.")] - [InlineData(10000, "десет хиляди лева")] - [InlineData(10012, "десет хиляди и дванадесет лева")] - [InlineData(10112, "десет хиляди сто и дванадесет лева")] - [InlineData(10912, "десет хиляди деветстотин и дванадесет лева")] - [InlineData(10900, "десет хиляди и деветстотин лева")] - [InlineData(11900, "единадесет хиляди и деветстотин лева")] - [InlineData(18900, "осемнадесет хиляди и деветстотин лева")] - [InlineData(20900, "двадесет хиляди и деветстотин лева")] - [InlineData(21900, "двадесет и една хиляди и деветстотин лева")] - [InlineData(22900, "двадесет и две хиляди и деветстотин лева")] - [InlineData(23901, "двадесет и три хиляди и деветстотин и един лева")] - [InlineData(12000, "дванадесет хиляди лева")] - [InlineData(11112, "единадесет хиляди сто и дванадесет лева")] - [InlineData(32478.27, "тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.")] - [InlineData(32048.27, "тридесет и две хиляди и четиридесет и осем лева и 27 ст.")] - [InlineData(32008.27, "тридесет и две хиляди и осем лева и 27 ст.")] - [InlineData(99999.99, "деветдесет и девет хиляди деветстотин деветдесет и девет лева и 99 ст.")] - public void NumberToWordsBG_ShouldReturnCorrectWords(decimal number, string expected) - { - // Act - string result = NumbersToWords.Convert(number); + // Act + var result = NumbersToWords.Convert(number, CurrencyDescriptor.Euro); - // Assert - Assert.Equal(expected, result); - } + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(-0.01, "Минус един евроцент")] + [InlineData(-0.02, "Минус две евроцента")] + [InlineData(-0.10, "Минус десет евроцента")] + [InlineData(-0.19, "Минус деветнадесет евроцента")] + [InlineData(-0.20, "Минус 20 ц.")] + [InlineData(-1, "Минус едно евро")] + [InlineData(-1.01, "Минус едно евро и 1 ц.")] + [InlineData(-2, "Минус две евро")] + [InlineData(-10.20, "Минус десет евро и 20 ц.")] + [InlineData(-11, "Минус единадесет евро")] + [InlineData(-21, "Минус двадесет и едно евро")] + [InlineData(-100, "Минус сто евро")] + [InlineData(-1000, "Минус хиляда евро")] + [InlineData(-32478.27, "Минус тридесет и две хиляди четиристотин седемдесет и осем евро и 27 ц.")] + public void NumberToWordsEuro_ShouldReturnCorrectWordsForNegativeValues(decimal number, string expected) + { + // Act + var result = NumbersToWords.Convert(number, CurrencyDescriptor.Euro); - [Theory] - [InlineData(-1, "един лев")] // Assuming negative values are treated as positive - [InlineData(-0.01, "една стотинка")] // Assuming negative values are treated as positive - [InlineData(-32478.27, "тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.")] - public void NumberToWordsBG_ShouldReturnCorrectWordsForNegativeValues(decimal number, string expected) - { - // Act - string result = NumbersToWords.Convert(number); + // Assert + Assert.Equal(expected, result); + } - // Assert - Assert.Equal(expected, result); - } + [Theory] + [InlineData(-100000)] + [InlineData(100000)] + public void Convert_WithNumberOutOfTheSupportedLimits_ShouldThrowArgumentOutOfRangeException(decimal number) + { + Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Bgn)); + Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Euro)); } -} +} \ No newline at end of file