From 543c22b5d6acaa2ea09999937e3a167a1a522a4f Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 16:00:57 +0200 Subject: [PATCH 01/12] Code refactoring --- src/OneBitSoftware.Slovom/NumbersToWords.cs | 284 +++++++++----------- 1 file changed, 132 insertions(+), 152 deletions(-) diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 80aa3d6..785be23 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -1,200 +1,180 @@ -namespace OneBitSoftware.Slovom +namespace OneBitSoftware.Slovom; + +public static class NumbersToWords { - public static class NumbersToWords + private const string AppendLvMale = " лев"; + private const string AppendLvFemale = " лева"; + private const string AppendStotinki = " стотинки"; + private const string AppendStotinka = " стотинка"; + private const string AppendStotinkaShort = "ст."; + + private static readonly string[] NumbersZeroToNineteen = ["нула", "един", "два", "три", "четири", "пет", "шест", "седем", "осем", "девет", "десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; + private static readonly string[] SingleDigitsNeutral = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет"]; + private static readonly string[] NumbersTenToNineteen = ["десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; + private static readonly string[] TensMultiples = ["", "десет", "двадесет", "тридесет", "четиридесет", "петдесет", "шестдесет", "седемдесет", "осемдесет", "деветдесет"]; + private static readonly string[] HundredsMultiples = ["", "сто", "двеста", "триста", "четиристотин", "петстотин", "шестстотин", "седемстотин", "осемстотин", "деветстотин"]; + + private static string ConvertWholeNumber(int number) { - 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) - { - 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 "Числото е твърде голямо"; - } - - private static string Tens(int n) + return number switch { - if (n < 20) - return under20[n]; + < 20 => NumbersZeroToNineteen[number], + < 100 => Tens(number), + < 1000 => Hundreds(number), + < 10000 => Thousands(number), + < 100000 => TensOfThousands(number), + _ => "Числото е твърде голямо" + }; + } - var i = n / 10; - var d = n % 10; + private static string Tens(int n) + { + if (n < 20) return NumbersZeroToNineteen[n]; - return tens[i] + (d == 0 ? "" : " и " + under20[d]); - } + var i = n / 10; + var d = n % 10; - static string Hundreds(int n) - { - var i = n / 100; - var d = n % 100; + return TensMultiples[i] + (d == 0 ? "" : " и " + 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)); + private static string Hundreds(int n) + { + var i = n / 100; + var d = n % 100; - return hundreds[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d)); - } + switch (n) + { + case < 120: return HundredsMultiples[i] + " и " + NumbersZeroToNineteen[d]; + case < 200: return HundredsMultiples[i] + " " + ConvertWholeNumber(d); } - private static string Thousands(int n) - { - var i = n / 1000; - var d = n % 1000; + if (d < 20) return HundredsMultiples[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d)); + + return HundredsMultiples[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d)); + } - if (n == 1000) return "хиляда"; + private static string Thousands(int n) + { + var i = n / 1000; + var d = n % 1000; - if (n > 1000 && n < 1099) return "хиляда и " + Tens(d); + if (n == 1000) return "хиляда"; - if (n > 1099 && n < 2000) return "хиляда " + Hundreds(d); + if (n is > 1000 and < 1099) return "хиляда и " + Tens(d); - if (d == 0) return units[i] + " хиляди"; // 2000,3000,4000, etc + if (n is > 1099 and < 2000) return "хиляда " + Hundreds(d); - if (d < 100) return units[i] + " хиляди и " + Tens(d); + if (d == 0) return SingleDigitsNeutral[i] + " хиляди"; // 2000,3000,4000, etc - return units[i] + " хиляди " + Hundreds(d); - } + if (d < 100) return SingleDigitsNeutral[i] + " хиляди и " + Tens(d); - private static string TensOfThousands(int number) + return SingleDigitsNeutral[i] + " хиляди " + Hundreds(d); + } + + private static string TensOfThousands(int number) + { + 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 TensMultiples[o] + " хиляди и " + Tens(n); + + 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 TensMultiples[o] + " хиляди и " + Hundreds(n); } - if (number >= 10099 && number < 11000) - { - if (soft == 0) // 10100, 10900, 10800 , etc - { - return tens[o] + " хиляди и " + Hundreds(n); - } + return BuildThousandsWithoutAnd(TensMultiples[o], Hundreds(n)); + } - return BuildThousandsWithoutAnd(tens[o], Hundreds(n)); - } + if (number is >= 11000 and < 20000) + { + if (soft != 0) return BuildThousandsWithoutAnd(NumbersTenToNineteen[t], Hundreds(e)); // 11100, 11900, 11800 , etc + if (e == 0) return NumbersTenToNineteen[t] + " хиляди"; - if (number >= 11000 && number < 20000) + return BuildThousandsWithAnd(NumbersTenToNineteen[t], Hundreds(e)); + } + + 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 TensMultiples[o] + " и една хиляди и " + Tens(e); - return BuildThousandsWithAnd(teens[t], Hundreds(e)); - } + if (ware == 0) return TensMultiples[o] + " и една хиляди и " + Hundreds(e); - return BuildThousandsWithoutAnd(teens[t], Hundreds(e)); + if (ware > 0) return TensMultiples[o] + " и една хиляди " + Hundreds(e); } - if (number > 20000 && number < 99999) + if (b == 2) { - if (b == 1) - { - if (e < 100) return tens[o] + " и една хиляди и " + Tens(e); - - if (ware == 0) return tens[o] + " и една хиляди и " + Hundreds(e); - - 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 (e < 100) return TensMultiples[o] + " и две хиляди и " + Tens(e); - if (ware > 0) return tens[o] + " и две хиляди " + Hundreds(e); - } + if (ware == 0) return TensMultiples[o] + " и две хиляди и " + Hundreds(e); - return BuildThousandsWithAnd(Tens(i), Hundreds(e)); + if (ware > 0) return TensMultiples[o] + " и две хиляди " + Hundreds(e); } - if (n == 0) return tens[o] + " хиляди"; // 10000,20000,30000, etc + return BuildThousandsWithAnd(Tens(i), Hundreds(e)); + } - if (n < 100) return BuildThousandsWithAnd(tens[o], Tens(soft)); + if (n == 0) return TensMultiples[o] + " хиляди"; // 10000,20000,30000, etc - return BuildThousandsWithoutAnd(Tens(i), Hundreds(e)); - } + if (n < 100) return BuildThousandsWithAnd(TensMultiples[o], Tens(soft)); - static string BuildThousandsWithoutAnd(string thousands, string afterThousands) - { - return thousands + " хиляди " + afterThousands; - } + return BuildThousandsWithoutAnd(Tens(i), Hundreds(e)); + } - static string BuildThousandsWithAnd(string thousands, string afterThousands) - { - return thousands + " хиляди и " + afterThousands; - } + private static string BuildThousandsWithoutAnd(string thousands, string afterThousands) + { + return thousands + " хиляди " + afterThousands; + } - public static string Convert(decimal number) - { - if (number == 0 || number == 0.0m) return under20[0] + AppendLvFemale; // нула лева + private static string BuildThousandsWithAnd(string thousands, string afterThousands) + { + return thousands + " хиляди и " + afterThousands; + } - number = Math.Abs(number); // Convert negative number to positive + public static string Convert(decimal number) + { + if (number is 0 or 0.0m) return NumbersZeroToNineteen[0] + AppendLvFemale; // нула лева - int leva = (int)number; - int stotinki = (int)((number % 1.0m) * 100); + number = Math.Abs(number); // Convert negative number to positive - if (number == 1 && stotinki == 0) return under20[leva] + AppendLvMale; // един лев + var leva = (int)number; + var stotinki = (int)((number % 1.0m) * 100); - string levaWords = leva != 1 ? ConvertWholeNumber(leva) + AppendLvFemale : "един" + AppendLvMale; + if (number == 1 && stotinki == 0) return NumbersZeroToNineteen[leva] + AppendLvMale; // един лев - string stotinkiWords; + var levaWords = leva != 1 ? ConvertWholeNumber(leva) + AppendLvFemale : "един" + AppendLvMale; - 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 stotinkiWords; - stotinkiWords = stotinki.ToString() + " " + AppendStotinkaShort; - } - else - { - stotinkiWords = stotinki.ToString() + " " + AppendStotinkaShort; - } + if (leva == 0) + { + if (stotinki == 0) return NumbersZeroToNineteen[leva] + AppendLvFemale; + if (stotinki == 1) return "една" + AppendStotinka; + if (stotinki == 2) return "две" + AppendStotinki; + if (stotinki == 10) return NumbersZeroToNineteen[stotinki] + AppendStotinki; + if (stotinki < 20) return NumbersZeroToNineteen[stotinki] + AppendStotinki; - if (leva == 0) - return stotinkiWords; - else if (stotinki == 0) - return levaWords; - else - return levaWords + " и " + stotinkiWords; + stotinkiWords = stotinki + " " + AppendStotinkaShort; } + else + { + stotinkiWords = stotinki + " " + AppendStotinkaShort; + } + + if (leva == 0) return stotinkiWords; + if (stotinki == 0) return levaWords; + + return levaWords + " и " + stotinkiWords; } -} +} \ No newline at end of file From 8bd836e631ab429d919c800dc85d1dbac35498f9 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 19:11:46 +0200 Subject: [PATCH 02/12] Created currency descriptors for BGN and EUR --- .../Currencies/CurrencyDescriptor.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/OneBitSoftware.Slovom/Currencies/CurrencyDescriptor.cs 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 From fc06e32b4afcd16bd0c59186098181040f70f88c Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 19:12:04 +0200 Subject: [PATCH 03/12] Created vocabulary in bulgarian for BGN and EUR --- .../Currencies/NumberWordsVocabulary.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs diff --git a/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs new file mode 100644 index 0000000..7019c83 --- /dev/null +++ b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs @@ -0,0 +1,42 @@ +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 +{ + /// + /// 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 From 6a62f1a9ad8eb556d91ee28ab4f3a7337948015d Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 19:12:17 +0200 Subject: [PATCH 04/12] Using the new components for currency description --- src/OneBitSoftware.Slovom/NumbersToWords.cs | 112 +++++++++----------- 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 785be23..f36f04c 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -1,77 +1,67 @@ namespace OneBitSoftware.Slovom; +using OneBitSoftware.Slovom.Currencies; + public static class NumbersToWords { - private const string AppendLvMale = " лев"; - private const string AppendLvFemale = " лева"; - private const string AppendStotinki = " стотинки"; - private const string AppendStotinka = " стотинка"; - private const string AppendStotinkaShort = "ст."; - - private static readonly string[] NumbersZeroToNineteen = ["нула", "един", "два", "три", "четири", "пет", "шест", "седем", "осем", "девет", "десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; - private static readonly string[] SingleDigitsNeutral = ["нула", "едно", "две", "три", "четири", "пет", "шест", "седем", "осем", "девет"]; - private static readonly string[] NumbersTenToNineteen = ["десет", "единадесет", "дванадесет", "тринадесет", "четиринадесет", "петнадесет", "шестнадесет", "седемнадесет", "осемнадесет", "деветнадесет"]; - private static readonly string[] TensMultiples = ["", "десет", "двадесет", "тридесет", "четиридесет", "петдесет", "шестдесет", "седемдесет", "осемдесет", "деветдесет"]; - private static readonly string[] HundredsMultiples = ["", "сто", "двеста", "триста", "четиристотин", "петстотин", "шестстотин", "седемстотин", "осемстотин", "деветстотин"]; - - private static string ConvertWholeNumber(int number) + private static string ConvertWholeNumber(int number, NumberWordsVocabulary numberWordsVocabulary) { return number switch { - < 20 => NumbersZeroToNineteen[number], - < 100 => Tens(number), - < 1000 => Hundreds(number), - < 10000 => Thousands(number), - < 100000 => TensOfThousands(number), + < 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) + private static string Tens(int n, NumberWordsVocabulary numberWordsVocabulary) { - if (n < 20) return NumbersZeroToNineteen[n]; + if (n < 20) return numberWordsVocabulary.NumbersZeroToNineteen[n]; var i = n / 10; var d = n % 10; - return TensMultiples[i] + (d == 0 ? "" : " и " + NumbersZeroToNineteen[d]); + return numberWordsVocabulary.TensMultiples[i] + (d == 0 ? "" : " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]); } - private static string Hundreds(int n) + private static string Hundreds(int n, NumberWordsVocabulary numberWordsVocabulary) { var i = n / 100; var d = n % 100; switch (n) { - case < 120: return HundredsMultiples[i] + " и " + NumbersZeroToNineteen[d]; - case < 200: return HundredsMultiples[i] + " " + ConvertWholeNumber(d); + case < 120: return numberWordsVocabulary.HundredsMultiples[i] + " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]; + case < 200: return numberWordsVocabulary.HundredsMultiples[i] + " " + ConvertWholeNumber(d, numberWordsVocabulary); } - if (d < 20) return HundredsMultiples[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d)); + if (d < 20) return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d, numberWordsVocabulary)); - return HundredsMultiples[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d)); + return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d, numberWordsVocabulary)); } - private static string Thousands(int n) + private static string Thousands(int n, NumberWordsVocabulary numberWordsVocabulary) { var i = n / 1000; var d = n % 1000; if (n == 1000) return "хиляда"; - if (n is > 1000 and < 1099) return "хиляда и " + Tens(d); + if (n is > 1000 and < 1099) return "хиляда и " + Tens(d, numberWordsVocabulary); - if (n is > 1099 and < 2000) return "хиляда " + Hundreds(d); + if (n is > 1099 and < 2000) return "хиляда " + Hundreds(d, numberWordsVocabulary); - if (d == 0) return SingleDigitsNeutral[i] + " хиляди"; // 2000,3000,4000, etc + if (d == 0) return numberWordsVocabulary.SingleDigitsNeutral[i] + " хиляди"; // 2000,3000,4000, etc - if (d < 100) return SingleDigitsNeutral[i] + " хиляди и " + Tens(d); + if (d < 100) return numberWordsVocabulary.SingleDigitsNeutral[i] + " хиляди и " + Tens(d, numberWordsVocabulary); - return SingleDigitsNeutral[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; @@ -82,54 +72,54 @@ private static string TensOfThousands(int number) var soft = e % 100; var ware = soft % 10; - if (number is > 10000 and < 10099) return TensMultiples[o] + " хиляди и " + Tens(n); + if (number is > 10000 and < 10099) return numberWordsVocabulary.TensMultiples[o] + " хиляди и " + Tens(n, numberWordsVocabulary); if (number is >= 10099 and < 11000) { if (soft == 0) // 10100, 10900, 10800 , etc { - return TensMultiples[o] + " хиляди и " + Hundreds(n); + return numberWordsVocabulary.TensMultiples[o] + " хиляди и " + Hundreds(n, numberWordsVocabulary); } - return BuildThousandsWithoutAnd(TensMultiples[o], Hundreds(n)); + return BuildThousandsWithoutAnd(numberWordsVocabulary.TensMultiples[o], Hundreds(n, numberWordsVocabulary)); } if (number is >= 11000 and < 20000) { - if (soft != 0) return BuildThousandsWithoutAnd(NumbersTenToNineteen[t], Hundreds(e)); // 11100, 11900, 11800 , etc - if (e == 0) return NumbersTenToNineteen[t] + " хиляди"; + if (soft != 0) return BuildThousandsWithoutAnd(numberWordsVocabulary.NumbersTenToNineteen[t], Hundreds(e, numberWordsVocabulary)); // 11100, 11900, 11800 , etc + if (e == 0) return numberWordsVocabulary.NumbersTenToNineteen[t] + " хиляди"; - return BuildThousandsWithAnd(NumbersTenToNineteen[t], Hundreds(e)); + return BuildThousandsWithAnd(numberWordsVocabulary.NumbersTenToNineteen[t], Hundreds(e, numberWordsVocabulary)); } if (number is > 20000 and < 99999) { if (b == 1) { - if (e < 100) return TensMultiples[o] + " и една хиляди и " + Tens(e); + if (e < 100) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди и " + Tens(e, numberWordsVocabulary); - if (ware == 0) return TensMultiples[o] + " и една хиляди и " + Hundreds(e); + if (ware == 0) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди и " + Hundreds(e, numberWordsVocabulary); - if (ware > 0) return TensMultiples[o] + " и една хиляди " + Hundreds(e); + if (ware > 0) return numberWordsVocabulary.TensMultiples[o] + " и една хиляди " + Hundreds(e, numberWordsVocabulary); } if (b == 2) { - if (e < 100) return TensMultiples[o] + " и две хиляди и " + Tens(e); + if (e < 100) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди и " + Tens(e, numberWordsVocabulary); - if (ware == 0) return TensMultiples[o] + " и две хиляди и " + Hundreds(e); + if (ware == 0) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди и " + Hundreds(e, numberWordsVocabulary); - if (ware > 0) return TensMultiples[o] + " и две хиляди " + Hundreds(e); + if (ware > 0) return numberWordsVocabulary.TensMultiples[o] + " и две хиляди " + Hundreds(e, numberWordsVocabulary); } - return BuildThousandsWithAnd(Tens(i), Hundreds(e)); + return BuildThousandsWithAnd(Tens(i, numberWordsVocabulary), Hundreds(e, numberWordsVocabulary)); } - if (n == 0) return TensMultiples[o] + " хиляди"; // 10000,20000,30000, etc + if (n == 0) return numberWordsVocabulary.TensMultiples[o] + " хиляди"; // 10000,20000,30000, etc - if (n < 100) return BuildThousandsWithAnd(TensMultiples[o], Tens(soft)); + if (n < 100) return BuildThousandsWithAnd(numberWordsVocabulary.TensMultiples[o], Tens(soft, numberWordsVocabulary)); - return BuildThousandsWithoutAnd(Tens(i), Hundreds(e)); + return BuildThousandsWithoutAnd(Tens(i, numberWordsVocabulary), Hundreds(e, numberWordsVocabulary)); } private static string BuildThousandsWithoutAnd(string thousands, string afterThousands) @@ -142,34 +132,36 @@ private static string BuildThousandsWithAnd(string thousands, string afterThousa return thousands + " хиляди и " + afterThousands; } - public static string Convert(decimal number) + public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) { - if (number is 0 or 0.0m) return NumbersZeroToNineteen[0] + AppendLvFemale; // нула лева + ArgumentNullException.ThrowIfNull(currencyDescriptor); + + if (number is 0 or 0.0m) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[0] + currencyDescriptor.MajorCurrencyUnitPlural; // нула лева, нула евро number = Math.Abs(number); // Convert negative number to positive var leva = (int)number; var stotinki = (int)((number % 1.0m) * 100); - if (number == 1 && stotinki == 0) return NumbersZeroToNineteen[leva] + AppendLvMale; // един лев + if (number == 1 && stotinki == 0) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular; // един лев, едно евро - var levaWords = leva != 1 ? ConvertWholeNumber(leva) + AppendLvFemale : "един" + AppendLvMale; + var levaWords = leva != 1 ? ConvertWholeNumber(leva, currencyDescriptor.Vocabulary) + currencyDescriptor.MajorCurrencyUnitPlural : currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular; string stotinkiWords; if (leva == 0) { - if (stotinki == 0) return NumbersZeroToNineteen[leva] + AppendLvFemale; - if (stotinki == 1) return "една" + AppendStotinka; - if (stotinki == 2) return "две" + AppendStotinki; - if (stotinki == 10) return NumbersZeroToNineteen[stotinki] + AppendStotinki; - if (stotinki < 20) return NumbersZeroToNineteen[stotinki] + AppendStotinki; + if (stotinki == 0) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitPlural; + if (stotinki == 1) return currencyDescriptor.Vocabulary.MinorCurrencyUnitSingular + currencyDescriptor.MinorCurrencyUnitSingular; + if (stotinki == 2) return "две" + currencyDescriptor.MinorCurrencyUnitPlural; + if (stotinki == 10) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural; + if (stotinki < 20) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural; - stotinkiWords = stotinki + " " + AppendStotinkaShort; + stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; } else { - stotinkiWords = stotinki + " " + AppendStotinkaShort; + stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; } if (leva == 0) return stotinkiWords; From 254e3d0203626309e3fa41d4562ab20829273791 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 19:12:28 +0200 Subject: [PATCH 05/12] Written tests for EUR --- src/Tests/ConvertTests.cs | 211 ++++++++++++++++++++++++-------------- 1 file changed, 136 insertions(+), 75 deletions(-) diff --git a/src/Tests/ConvertTests.cs b/src/Tests/ConvertTests.cs index a5e2543..85cd0a0 100644 --- a/src/Tests/ConvertTests.cs +++ b/src/Tests/ConvertTests.cs @@ -1,82 +1,143 @@ -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(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); - public class ConvertTests + // Assert + Assert.Equal(expected, result); + } + + [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) { - [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.Bgn); - // Assert - Assert.Equal(expected, result); - } + // Assert + Assert.Equal(expected, result); + } - [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); + [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(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) + { + // Act + var result = NumbersToWords.Convert(number, CurrencyDescriptor.Euro); - // Assert - Assert.Equal(expected, result); - } + // Assert + Assert.Equal(expected, result); } -} +} \ No newline at end of file From f65ffbfc9f1a34c20da3e9c0f1d6cdafdb96748f Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Fri, 16 Jan 2026 19:20:37 +0200 Subject: [PATCH 06/12] Code refactoring --- .../Currencies/NumberWordsVocabulary.cs | 2 ++ src/OneBitSoftware.Slovom/NumbersToWords.cs | 16 ++++------------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs index 7019c83..af0c7c4 100644 --- a/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs +++ b/src/OneBitSoftware.Slovom/Currencies/NumberWordsVocabulary.cs @@ -10,6 +10,8 @@ /// public sealed record NumberWordsVocabulary { + internal NumberWordsVocabulary() { } + /// /// Gets or inits the word representation for the singular form of the minor currency unit. /// diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index f36f04c..74acd44 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -4,9 +4,8 @@ public static class NumbersToWords { - private static string ConvertWholeNumber(int number, NumberWordsVocabulary numberWordsVocabulary) - { - return number switch + private static string ConvertWholeNumber(int number, NumberWordsVocabulary numberWordsVocabulary) => + number switch { < 20 => numberWordsVocabulary.NumbersZeroToNineteen[number], < 100 => Tens(number, numberWordsVocabulary), @@ -15,7 +14,6 @@ private static string ConvertWholeNumber(int number, NumberWordsVocabulary numbe < 100000 => TensOfThousands(number, numberWordsVocabulary), _ => "Числото е твърде голямо" }; - } private static string Tens(int n, NumberWordsVocabulary numberWordsVocabulary) { @@ -122,15 +120,9 @@ private static string TensOfThousands(int number, NumberWordsVocabulary numberWo return BuildThousandsWithoutAnd(Tens(i, numberWordsVocabulary), Hundreds(e, numberWordsVocabulary)); } - private static string BuildThousandsWithoutAnd(string thousands, string afterThousands) - { - return thousands + " хиляди " + afterThousands; - } + private static string BuildThousandsWithoutAnd(string thousands, string afterThousands) => thousands + " хиляди " + afterThousands; - private static string BuildThousandsWithAnd(string thousands, string afterThousands) - { - return thousands + " хиляди и " + afterThousands; - } + private static string BuildThousandsWithAnd(string thousands, string afterThousands) => thousands + " хиляди и " + afterThousands; public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) { From c6c8e88f6e1335ed648baef4b19a0d8a0fe09552 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 10:36:33 +0200 Subject: [PATCH 07/12] Increased the package version --- src/OneBitSoftware.Slovom/OneBitSoftware.Slovom.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 7423699590335d6dcb2b1722bbb5c64ee6341999 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 11:46:58 +0200 Subject: [PATCH 08/12] Added support for negative numbers --- src/OneBitSoftware.Slovom/NumbersToWords.cs | 40 ++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 74acd44..21af3b5 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -30,12 +30,15 @@ private static string Hundreds(int n, NumberWordsVocabulary numberWordsVocabular var i = n / 100; var d = n % 100; - switch (n) + if (n < 120) { - case < 120: return numberWordsVocabulary.HundredsMultiples[i] + " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]; - case < 200: return numberWordsVocabulary.HundredsMultiples[i] + " " + ConvertWholeNumber(d, numberWordsVocabulary); + if (d == 0) return numberWordsVocabulary.HundredsMultiples[i]; + + return numberWordsVocabulary.HundredsMultiples[i] + " и " + numberWordsVocabulary.NumbersZeroToNineteen[d]; } + if (n < 200) return numberWordsVocabulary.HundredsMultiples[i] + " " + ConvertWholeNumber(d, numberWordsVocabulary); + if (d < 20) return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " и " + ConvertWholeNumber(d, numberWordsVocabulary)); return numberWordsVocabulary.HundredsMultiples[i] + (d == 0 ? "" : " " + ConvertWholeNumber(d, numberWordsVocabulary)); @@ -124,18 +127,26 @@ private static string TensOfThousands(int number, NumberWordsVocabulary numberWo private static string BuildThousandsWithAnd(string thousands, string afterThousands) => thousands + " хиляди и " + afterThousands; + private static string AppendNegativePrefix(string numberAsWords, bool isNegativeNumber) + { + if (!isNegativeNumber) return numberAsWords; + + return "Минус " + numberAsWords; + } + public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) { ArgumentNullException.ThrowIfNull(currencyDescriptor); if (number is 0 or 0.0m) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[0] + currencyDescriptor.MajorCurrencyUnitPlural; // нула лева, нула евро + var isNegativeNumber = number < 0; number = Math.Abs(number); // Convert negative number to positive var leva = (int)number; var stotinki = (int)((number % 1.0m) * 100); - if (number == 1 && stotinki == 0) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular; // един лев, едно евро + if (number == 1 && stotinki == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular, isNegativeNumber); // един лев, едно евро var levaWords = leva != 1 ? ConvertWholeNumber(leva, currencyDescriptor.Vocabulary) + currencyDescriptor.MajorCurrencyUnitPlural : currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular; @@ -143,22 +154,19 @@ public static string Convert(decimal number, CurrencyDescriptor currencyDescript if (leva == 0) { - if (stotinki == 0) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitPlural; - if (stotinki == 1) return currencyDescriptor.Vocabulary.MinorCurrencyUnitSingular + currencyDescriptor.MinorCurrencyUnitSingular; - if (stotinki == 2) return "две" + currencyDescriptor.MinorCurrencyUnitPlural; - if (stotinki == 10) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural; - if (stotinki < 20) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural; + if (stotinki == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitPlural, isNegativeNumber); + if (stotinki == 1) return AppendNegativePrefix(currencyDescriptor.Vocabulary.MinorCurrencyUnitSingular + currencyDescriptor.MinorCurrencyUnitSingular, isNegativeNumber); + if (stotinki == 2) return AppendNegativePrefix("две" + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); + if (stotinki == 10) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); + if (stotinki < 20) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; } - else - { - stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; - } + else stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; - if (leva == 0) return stotinkiWords; - if (stotinki == 0) return levaWords; + if (leva == 0) return AppendNegativePrefix(stotinkiWords, isNegativeNumber); + if (stotinki == 0) return AppendNegativePrefix(levaWords, isNegativeNumber); - return levaWords + " и " + stotinkiWords; + return AppendNegativePrefix(levaWords + " и " + stotinkiWords, isNegativeNumber); } } \ No newline at end of file From 88278a00c72d7ee4ba1535bfd90c6a815d1ac0dc Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 11:47:10 +0200 Subject: [PATCH 09/12] Added tests for negative values --- src/Tests/ConvertTests.cs | 51 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Tests/ConvertTests.cs b/src/Tests/ConvertTests.cs index 85cd0a0..101dc31 100644 --- a/src/Tests/ConvertTests.cs +++ b/src/Tests/ConvertTests.cs @@ -16,6 +16,7 @@ public class ConvertTests [InlineData(11, "единадесет лева")] [InlineData(12, "дванадесет лева")] [InlineData(19, "деветнадесет лева")] + [InlineData(100, "сто лева")] [InlineData(0.0, "нула лева")] [InlineData(0.1, "десет стотинки")] [InlineData(1.20, "един лев и 20 ст.")] @@ -68,9 +69,28 @@ public void NumberToWordsBG_ShouldReturnCorrectWords(decimal number, string expe } [Theory] - [InlineData(-1, "един лев")] // Assuming negative values are treated as positive - [InlineData(-0.01, "една стотинка")] // Assuming negative values are treated as positive - [InlineData(-32478.27, "тридесет и две хиляди четиристотин седемдесет и осем лева и 27 ст.")] + [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 @@ -94,6 +114,7 @@ public void NumberToWordsBG_ShouldReturnCorrectWordsForNegativeValues(decimal nu [InlineData(1.20, "едно евро и 20 ц.")] [InlineData(1.3, "едно евро и 30 ц.")] [InlineData(1.02, "едно евро и 2 ц.")] + [InlineData(100, "сто евро")] [InlineData(118, "сто и осемнадесет евро")] [InlineData(123, "сто двадесет и три евро")] [InlineData(1000, "хиляда евро")] @@ -140,4 +161,28 @@ public void NumberToWordsEuro_ShouldReturnCorrectWords(decimal number, string ex // 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); + + // Assert + Assert.Equal(expected, result); + } } \ No newline at end of file From 9535227429c1e57a5629cd6abf81af711a595850 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 12:07:00 +0200 Subject: [PATCH 10/12] Do not allow numbers less than -99999.99 --- src/OneBitSoftware.Slovom/NumbersToWords.cs | 1 + src/Tests/ConvertTests.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 21af3b5..9446e93 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -136,6 +136,7 @@ private static string AppendNegativePrefix(string numberAsWords, bool isNegative public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) { + if (number < -99999.99m) throw new ArgumentOutOfRangeException(nameof(number), "Входното число не може да бъде по-малко от -99999.99"); ArgumentNullException.ThrowIfNull(currencyDescriptor); if (number is 0 or 0.0m) return currencyDescriptor.Vocabulary.NumbersZeroToNineteen[0] + currencyDescriptor.MajorCurrencyUnitPlural; // нула лева, нула евро diff --git a/src/Tests/ConvertTests.cs b/src/Tests/ConvertTests.cs index 101dc31..bab8c9e 100644 --- a/src/Tests/ConvertTests.cs +++ b/src/Tests/ConvertTests.cs @@ -185,4 +185,12 @@ public void NumberToWordsEuro_ShouldReturnCorrectWordsForNegativeValues(decimal // Assert Assert.Equal(expected, result); } + + [Theory] + [InlineData(-100000)] + public void Convert_WithNumberLessThanNegativeLimit_ShouldThrowArgumentOutOfRangeException(decimal number) + { + Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Bgn)); + Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Euro)); + } } \ No newline at end of file From 8027be81343f1483894e1f5bc9854fcb18a46467 Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 12:07:10 +0200 Subject: [PATCH 11/12] Enhanced the readme --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9ccc2c5..4374f44 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. @@ -33,6 +44,7 @@ dotnet add package OneBitSoftware.Slovom ## Examples +## BGN examples |Input|Output| |--------|-------| |0|нула лева| @@ -46,6 +58,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. From cc6734b25b5c8a961f75c40fc89bea4f8ee774af Mon Sep 17 00:00:00 2001 From: Yulian Ashikov Date: Mon, 19 Jan 2026 13:52:06 +0200 Subject: [PATCH 12/12] PR review comments --- README.md | 19 ++++++++++++ src/OneBitSoftware.Slovom/NumbersToWords.cs | 34 ++++++++++----------- src/Tests/ConvertTests.cs | 3 +- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4374f44..77fc949 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,25 @@ 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 diff --git a/src/OneBitSoftware.Slovom/NumbersToWords.cs b/src/OneBitSoftware.Slovom/NumbersToWords.cs index 9446e93..1742a01 100644 --- a/src/OneBitSoftware.Slovom/NumbersToWords.cs +++ b/src/OneBitSoftware.Slovom/NumbersToWords.cs @@ -136,7 +136,7 @@ private static string AppendNegativePrefix(string numberAsWords, bool isNegative public static string Convert(decimal number, CurrencyDescriptor currencyDescriptor) { - if (number < -99999.99m) throw new ArgumentOutOfRangeException(nameof(number), "Входното число не може да бъде по-малко от -99999.99"); + 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; // нула лева, нула евро @@ -144,30 +144,30 @@ public static string Convert(decimal number, CurrencyDescriptor currencyDescript var isNegativeNumber = number < 0; number = Math.Abs(number); // Convert negative number to positive - var leva = (int)number; - var stotinki = (int)((number % 1.0m) * 100); + var majorUnit = (int)number; + var minorUnit = (int)((number % 1.0m) * 100); - if (number == 1 && stotinki == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular, isNegativeNumber); // един лев, едно евро + if (number == 1 && minorUnit == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[majorUnit] + currencyDescriptor.MajorCurrencyUnitSingular, isNegativeNumber); // един лев, едно евро - var levaWords = leva != 1 ? ConvertWholeNumber(leva, currencyDescriptor.Vocabulary) + currencyDescriptor.MajorCurrencyUnitPlural : currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitSingular; + var majorUnitWords = majorUnit != 1 ? ConvertWholeNumber(majorUnit, currencyDescriptor.Vocabulary) + currencyDescriptor.MajorCurrencyUnitPlural : currencyDescriptor.Vocabulary.NumbersZeroToNineteen[majorUnit] + currencyDescriptor.MajorCurrencyUnitSingular; - string stotinkiWords; + string minorUnitWords; - if (leva == 0) + if (majorUnit == 0) { - if (stotinki == 0) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[leva] + currencyDescriptor.MajorCurrencyUnitPlural, isNegativeNumber); - if (stotinki == 1) return AppendNegativePrefix(currencyDescriptor.Vocabulary.MinorCurrencyUnitSingular + currencyDescriptor.MinorCurrencyUnitSingular, isNegativeNumber); - if (stotinki == 2) return AppendNegativePrefix("две" + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); - if (stotinki == 10) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); - if (stotinki < 20) return AppendNegativePrefix(currencyDescriptor.Vocabulary.NumbersZeroToNineteen[stotinki] + currencyDescriptor.MinorCurrencyUnitPlural, isNegativeNumber); + 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); - stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; + minorUnitWords = minorUnit + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; } - else stotinkiWords = stotinki + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; + else minorUnitWords = minorUnit + " " + currencyDescriptor.MinorCurrencyUnitAbbreviated; - if (leva == 0) return AppendNegativePrefix(stotinkiWords, isNegativeNumber); - if (stotinki == 0) return AppendNegativePrefix(levaWords, isNegativeNumber); + if (majorUnit == 0) return AppendNegativePrefix(minorUnitWords, isNegativeNumber); + if (minorUnit == 0) return AppendNegativePrefix(majorUnitWords, isNegativeNumber); - return AppendNegativePrefix(levaWords + " и " + stotinkiWords, isNegativeNumber); + return AppendNegativePrefix(majorUnitWords + " и " + minorUnitWords, isNegativeNumber); } } \ No newline at end of file diff --git a/src/Tests/ConvertTests.cs b/src/Tests/ConvertTests.cs index bab8c9e..8c8085f 100644 --- a/src/Tests/ConvertTests.cs +++ b/src/Tests/ConvertTests.cs @@ -188,7 +188,8 @@ public void NumberToWordsEuro_ShouldReturnCorrectWordsForNegativeValues(decimal [Theory] [InlineData(-100000)] - public void Convert_WithNumberLessThanNegativeLimit_ShouldThrowArgumentOutOfRangeException(decimal number) + [InlineData(100000)] + public void Convert_WithNumberOutOfTheSupportedLimits_ShouldThrowArgumentOutOfRangeException(decimal number) { Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Bgn)); Assert.Throws(() => NumbersToWords.Convert(number, CurrencyDescriptor.Euro));