Skip to content

Commit

Permalink
Added hungarian translations, and hungarian number formatters (#1501)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkelemen18 authored May 9, 2024
1 parent 50811da commit f0e379d
Show file tree
Hide file tree
Showing 7 changed files with 704 additions and 3 deletions.
87 changes: 87 additions & 0 deletions src/Humanizer.Tests/Localisation/hu/NumberToWordsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace hu;

[UseCulture("hu-HU")]
public class NumberToWordsTests
{
private readonly HungarianNumberToWordsConverter converter = new();

[Theory]
[InlineData(0, "nulla")]
[InlineData(1, "egy")]
[InlineData(2, "kettő")]
[InlineData(10, "tíz")]
[InlineData(11, "tizenegy")]
[InlineData(20, "húsz")]
[InlineData(21, "huszonegy")]
[InlineData(100, "száz")]
[InlineData(101, "százegy")]
[InlineData(111, "száztizenegy")]
[InlineData(200, "kétszáz")]
[InlineData(999, "kilencszázkilencvenkilenc")]
[InlineData(1000, "ezer")]
[InlineData(1001, "ezeregy")]
[InlineData(1002, "ezerkettő")]
[InlineData(1111, "ezerszáztizenegy")]
[InlineData(2000, "kétezer")]
[InlineData(2001, "kétezer-egy")]
[InlineData(2002, "kétezer-kettő")]
[InlineData(2111, "kétezer-száztizenegy")]
[InlineData(3000, "háromezer")]
[InlineData(3001, "háromezer-egy")]
[InlineData(3002, "háromezer-kettő")]
[InlineData(3122, "háromezer-százhuszonkettő")]
[InlineData(12345, "tizenkétezer-háromszáznegyvenöt")]
[InlineData(123456, "százhuszonháromezer-négyszázötvenhat")]
[InlineData(1234567, "egymillió-kétszázharmincnégyezer-ötszázhatvanhét")]
[InlineData(12345678, "tizenkétmillió-háromszáznegyvenötezer-hatszázhetvennyolc")]
[InlineData(123456789, "százhuszonhárommillió-négyszázötvenhatezer-hétszáznyolcvankilenc")]
[InlineData(1234567890, "egymilliárd-kétszázharmincnégymillió-ötszázhatvanhétezer-nyolcszázkilencven")]
[InlineData(1000000, "egymillió")]
[InlineData(2000000, "kétmillió")]
[InlineData(12000000, "tizenkétmillió")]
[InlineData(-1, "mínusz egy")]
[InlineData(-18, "mínusz tizennyolc")]
public void TestNumberConversion(long number, string expected) =>
Assert.Equal(expected, converter.Convert(number));


[Theory]
[InlineData(0, "nulladik")]
[InlineData(1, "első")]
[InlineData(2, "második")]
[InlineData(10, "tizedik")]
[InlineData(11, "tizenegyedik")]
[InlineData(20, "huszadik")]
[InlineData(21, "huszonegyedik")]
[InlineData(100, "századik")]
[InlineData(101, "százegyedik")]
[InlineData(111, "száztizenegyedik")]
[InlineData(200, "kétszázadik")]
[InlineData(999, "kilencszázkilencvenkilencedik")]
[InlineData(1000, "ezredik")]
[InlineData(1001, "ezeregyedik")]
[InlineData(1002, "ezerkettedik")]
[InlineData(1111, "ezerszáztizenegyedik")]
[InlineData(2000, "kétezredik")]
[InlineData(2001, "kétezer-egyedik")]
[InlineData(2002, "kétezer-kettedik")]
[InlineData(2111, "kétezer-száztizenegyedik")]
[InlineData(3000, "háromezredik")]
[InlineData(3001, "háromezer-egyedik")]
[InlineData(3002, "háromezer-kettedik")]
[InlineData(3122, "háromezer-százhuszonkettedik")]
[InlineData(12345, "tizenkétezer-háromszáznegyvenötödik")]
[InlineData(123456, "százhuszonháromezer-négyszázötvenhatodik")]
[InlineData(1234567, "egymillió-kétszázharmincnégyezer-ötszázhatvanhetedik")]
[InlineData(12345678, "tizenkétmillió-háromszáznegyvenötezer-hatszázhetvennyolcadik")]
[InlineData(123456789, "százhuszonhárommillió-négyszázötvenhatezer-hétszáznyolcvankilencedik")]
[InlineData(1234567890, "egymilliárd-kétszázharmincnégymillió-ötszázhatvanhétezer-nyolcszázkilencvenedik")]
[InlineData(1000000, "egymilliomodik")]
[InlineData(2000000, "kétmilliomodik")]
[InlineData(12000000, "tizenkétmilliomodik")]
[InlineData(-1, "mínusz első")]
[InlineData(-18, "mínusz tizennyolcadik")]
public void TestOrdinalConversion(int number, string expected) =>
Assert.Equal(expected, converter.ConvertToOrdinal(number));

}
52 changes: 50 additions & 2 deletions src/Humanizer.Tests/Localisation/hu/TimeSpanHumanizeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class TimeSpanHumanizeTests
{
[Theory]
[Trait("Translation", "Google")]
[InlineData(366, "egy év")]
[InlineData(366, "1 év")]
[InlineData(731, "2 év")]
[InlineData(1096, "3 év")]
[InlineData(4018, "11 év")]
Expand All @@ -14,7 +14,7 @@ public void Years(int days, string expected) =>

[Theory]
[Trait("Translation", "Google")]
[InlineData(31, "egy hónap")]
[InlineData(31, "1 hónap")]
[InlineData(61, "2 hónap")]
[InlineData(92, "3 hónap")]
[InlineData(335, "11 hónap")]
Expand Down Expand Up @@ -51,6 +51,54 @@ public void Minutes(int minutes, string expected) =>
public void Seconds(int seconds, string expected) =>
Assert.Equal(expected, TimeSpan.FromSeconds(seconds).Humanize());

[Theory]
[Trait("Translation", "Google")]
[InlineData(366, "egy év")]
[InlineData(731, "kettő év")]
[InlineData(1096, "három év")]
[InlineData(4018, "tizenegy év")]
public void YearsWithWords(int days, string expected) =>
Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(maxUnit: TimeUnit.Year, toWords:true));

[Theory]
[Trait("Translation", "Google")]
[InlineData(31, "egy hónap")]
[InlineData(61, "kettő hónap")]
[InlineData(92, "három hónap")]
[InlineData(335, "tizenegy hónap")]
public void MonthsWithWords(int days, string expected) =>
Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(maxUnit: TimeUnit.Year, toWords:true));

[Theory]
[InlineData(14, "kettő hét")]
[InlineData(7, "egy hét")]
public void WeeksWithWords(int days, string expected) =>
Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(toWords:true));

[Theory]
[InlineData(2, "kettő nap")]
[InlineData(1, "egy nap")]
public void DaysWithWords(int days, string expected) =>
Assert.Equal(expected, TimeSpan.FromDays(days).Humanize(toWords:true));

[Theory]
[InlineData(2, "kettő óra")]
[InlineData(1, "egy óra")]
public void HoursWithWords(int hours, string expected) =>
Assert.Equal(expected, TimeSpan.FromHours(hours).Humanize(toWords:true));

[Theory]
[InlineData(2, "kettő perc")]
[InlineData(1, "egy perc")]
public void MinutesWithWords(int minutes, string expected) =>
Assert.Equal(expected, TimeSpan.FromMinutes(minutes).Humanize(toWords:true));

[Theory]
[InlineData(2, "kettő másodperc")]
[InlineData(1, "egy másodperc")]
public void SecondsWithWords(int seconds, string expected) =>
Assert.Equal(expected, TimeSpan.FromSeconds(seconds).Humanize(toWords:true));

[Fact]
public void NoTime()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ public NumberToWordsConverterRegistry()
Register("en-IN", new IndianNumberToWordsConverter());
Register("lt", new LithuanianNumberToWordsConverter());
Register("lb", new LuxembourgishNumberToWordsConverter());
Register("hu", new HungarianNumberToWordsConverter());
}
}
1 change: 1 addition & 0 deletions src/Humanizer/Configuration/OrdinalizerRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ public OrdinalizerRegistry()
Register("hy", new ArmenianOrdinalizer());
Register("az", new AzerbaijaniOrdinalizer());
Register("lb", new LuxembourgishOrdinalizer());
Register("hu", new HungarianOrdinalizer());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
namespace Humanizer;

class HungarianNumberToWordsConverter : GenderlessNumberToWordsConverter
{
// Units
private static readonly string[] UnitsMap = ["", "egy", "kettő", "három", "négy", "öt", "hat", "hét", "nyolc", "kilenc"];
private static readonly string[] OrdinalUnitsMap = ["", "első", "második", "harmadik", "negyedik", "ötödik", "hatodik", "hetedik", "nyolcadik", "kilencedik"];

// Tens
private static readonly string[] TensMap = ["", "tizen", "huszon", "harminc", "negyven", "ötven", "hatvan", "hetven", "nyolcvan", "kilencven"];
private static readonly string[] OrdinalTensMap = ["", "tizedik", "huszadik", "harmincadik", "negyvenedik", "ötvenedik", "hatvanadik", "hetvenedik", "nyolcvanadik", "kilencvenedik"];

// Hundreds
private static readonly string[] HundredsMap = ["", "száz", "kétszáz", "háromszáz", "négyszáz", "ötszáz", "hatszáz", "hétszáz", "nyolcszáz", "kilencszáz"];

// Exceptional single numbers when used as ordinal numbers and the whole number is greater than 10
private static readonly Dictionary<long, string> OrdinalUnitsExceptions = new()
{
{
1, "egyedik"
},
{
2, "kettedik"
}
};

// Exceptional ten numbers when the number divided by 10 gives no remainder
private static readonly Dictionary<long, string> WholeTensExceptions = new()
{
{
10, "tíz"
},
{
20, "húsz"
}
};


public override string Convert(long number) => ConvertInternal(number, false);

public override string ConvertToOrdinal(int number) => ConvertInternal(number, true);

private static string ConvertInternal(long number, bool isOrdinal)
{
// Handle zero and negative numbers
switch (number)
{
case 0:
return isOrdinal ? "nulladik" : "nulla";
case < 0:
return $"mínusz {ConvertInternal(-number, isOrdinal)}";
}

var isLessThanTen = number < 10;
var parts = new List<string>(10);

CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000_000_000_000_000_000, "trillió", "trilliomodik");
CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000_000_000_000_000, "billiárd", "billiárdodik");
CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000_000_000_000, "billió", "billiomodik");
CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000_000_000, "milliárd", "milliárdodik");
CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000_000, "millió", "milliomodik");

// All numbers above 2000 should be separated by dashes per thousands
if (2_000 <= number)
{
CollectParts(parts, ref number, isOrdinal, isLessThanTen, 1_000, "ezer", "ezredik");
var underAThousandPart = GetUnderAThousandPart(number, isOrdinal, false, isLessThanTen);
if (underAThousandPart != string.Empty)
parts.Add(underAThousandPart);
}
else
{
// In hungarian there is no separator between one thousand and the rest of the numbers
var lastPart = 1_000 <= number ? GetOneThousandPart(ref number, isOrdinal) : "";
lastPart += GetUnderAThousandPart(number, isOrdinal, false, isLessThanTen);

if (lastPart != string.Empty)
parts.Add(lastPart);
}

return string.Join("-", parts);
}

// Thousands part for numbers between 1000 and 1999
static string GetOneThousandPart(ref long number, bool isOrdinal)
{
const int divisor = 1_000;

var oneThousandPart = isOrdinal && number == divisor ? "ezredik" : "ezer";

number %= divisor;
return oneThousandPart;
}

private static void CollectParts(List<string> parts, ref long number, bool isOrdinal, bool isLessThanTen,
long divisor, string word,
string ordinal)
{
var result = number / divisor;
if (result == 0)
{
return;
}

var prefixNumber = GetUnderAThousandPart(result, isOrdinal, true, isLessThanTen);

number %= divisor;
parts.Add(number == 0 && isOrdinal ? prefixNumber + ordinal : prefixNumber + word);
}

private static string GetUnderAThousandPart(long number, bool isOrdinal, bool isPrefix, bool originalLessThanTen)
{
var numberString = "";
if (100 <= number)
{
// Return hundred + "adik" if the number is exactly one of hundreds e.g.: századik, hétszázadik
if (isOrdinal && number % 100 == 0)
return HundredsMap[number / 100] + "adik";

numberString += HundredsMap[number / 100];
number %= 100;
}

if (10 <= number)
{
// Return an ordinal ten if the number is exactly one of tens
if (isOrdinal && number % 10 == 0)
return numberString + OrdinalTensMap[number / 10];

numberString += WholeTensExceptions.TryGetValue(number, out var value) ? value : TensMap[number / 10];
number %= 10;
}

if (isOrdinal && !isPrefix)
numberString += GetOrdinalOnes(number, originalLessThanTen);
else
numberString += isPrefix && number == 2 ? "két" : UnitsMap[number];

return numberString;
}

private static string GetOrdinalOnes(long number, bool lessThanTen)
{
if (lessThanTen)
return OrdinalUnitsMap[number];

return OrdinalUnitsExceptions.TryGetValue(number, out var value) ? value : OrdinalUnitsMap[number];
}

public override string ConvertToTuple(int number) =>
number switch
{
1 => "szimpla",
2 => "dupla",
3 => "tripla",
4 => "kvadrupla",
5 => "pentapla",
6 => "hexapla",
7 => "heptapla",
8 => "octapla",
9 => "nonapla",
10 => "dekapla",
100 => "hektapla",
1000 => "kiliapla",
_ => $"{number}"
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Humanizer;

class HungarianOrdinalizer : DefaultOrdinalizer
{
// In hungarian language ordinal numbers are marked with a dot "." at the end
public override string Convert(int number, string numberString) => numberString + ".";
}
Loading

0 comments on commit f0e379d

Please sign in to comment.