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