From 72b2f453e2a2663c07cedd9b2e9749c55087d222 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 1 Nov 2026 17:42:47 +0100 Subject: [PATCH 1/2] StringToTitleCase: move to .NET 10, culture-safe all-caps fix, minor-words helper and tests --- .../StringToTitleCase/Program.cs | 35 ++++---- .../StringToTitleCase.csproj | 4 +- .../StringToTitleCase/TitleCaseExtensions.cs | 26 ++++++ .../StringToTitleCaseUnitTest.cs | 89 +++++++++++++++---- .../StringToTitleCaseUnitTest.csproj | 17 ++-- 5 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 strings-csharp/StringToTitleCase/StringToTitleCase/TitleCaseExtensions.cs diff --git a/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs b/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs index 2fdc9f9885..416229c9d7 100644 --- a/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs +++ b/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs @@ -1,21 +1,24 @@ -using System; -using System.Globalization; +using System.Globalization; +using StringToTitleCase; -namespace StringToTitleCase -{ - class Program - { - static void Main(string[] args) - { - var textinfo = new CultureInfo("en-US", false).TextInfo; +var textinfo = new CultureInfo("en-US", false).TextInfo; - var textInfo = CultureInfo.CurrentCulture.TextInfo; +Console.WriteLine(textinfo.ToTitleCase("a tale oF tWo citIes")); +Console.WriteLine(textinfo.ToTitleCase("harry potter and the Deathly hallows")); - Console.WriteLine(textinfo.ToTitleCase("a tale oF tWo citIes")); +Console.WriteLine(textinfo.ToTitleCase("OLIVER TWIST")); +Console.WriteLine(textinfo.ToTitleCase(textinfo.ToLower("OLIVER TWIST"))); +Console.WriteLine(textinfo.ToTitleCase(textinfo.ToLower("THE NASA MISSION"))); - Console.WriteLine(textinfo.ToTitleCase("harry potter and the Deathly hallows")); +Console.WriteLine("a tale oF tWo citIes".ToTitleCaseWithMinorWords(textinfo)); +Console.WriteLine("harry potter and the Deathly hallows".ToTitleCaseWithMinorWords(textinfo)); - Console.WriteLine(textinfo.ToTitleCase("OLIVER TWIST".ToLower())); - } - } -} +var turkish = new CultureInfo("tr-TR", false).TextInfo; +Console.WriteLine(turkish.ToTitleCase("a tale oF tWo citIes")); +Console.WriteLine(turkish.ToTitleCase("istanbul")); +Console.WriteLine(CultureInfo.InvariantCulture.TextInfo.ToTitleCase("a tale oF tWo citIes")); + +Console.WriteLine(textinfo.ToTitleCase("the 2nd edition")); +Console.WriteLine(textinfo.ToTitleCase("my iPhone")); +Console.WriteLine(textinfo.ToTitleCase("mcdonald's menu")); +Console.WriteLine(textinfo.ToTitleCase("x-ray vision")); diff --git a/strings-csharp/StringToTitleCase/StringToTitleCase/StringToTitleCase.csproj b/strings-csharp/StringToTitleCase/StringToTitleCase/StringToTitleCase.csproj index 41f1d5ad4b..dfb40caafc 100644 --- a/strings-csharp/StringToTitleCase/StringToTitleCase/StringToTitleCase.csproj +++ b/strings-csharp/StringToTitleCase/StringToTitleCase/StringToTitleCase.csproj @@ -2,7 +2,9 @@ Exe - net6.0 + net10.0 + enable + enable diff --git a/strings-csharp/StringToTitleCase/StringToTitleCase/TitleCaseExtensions.cs b/strings-csharp/StringToTitleCase/StringToTitleCase/TitleCaseExtensions.cs new file mode 100644 index 0000000000..debd77993b --- /dev/null +++ b/strings-csharp/StringToTitleCase/StringToTitleCase/TitleCaseExtensions.cs @@ -0,0 +1,26 @@ +using System.Globalization; + +namespace StringToTitleCase; + +public static class TitleCaseExtensions +{ + private static readonly HashSet MinorWords = new(StringComparer.OrdinalIgnoreCase) + { + "a", "an", "and", "as", "at", "but", "by", "for", "in", "nor", "of", "on", "or", "the", "to" + }; + + public static string ToTitleCaseWithMinorWords(this string input, TextInfo textInfo) + { + var words = textInfo.ToTitleCase(textInfo.ToLower(input)).Split(' '); + + for (var i = 1; i < words.Length - 1; i++) + { + if (MinorWords.Contains(words[i])) + { + words[i] = textInfo.ToLower(words[i]); + } + } + + return string.Join(' ', words); + } +} diff --git a/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.cs b/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.cs index 4c20206d1b..3f6c8ce173 100644 --- a/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.cs +++ b/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.cs @@ -1,31 +1,84 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Globalization; +using StringToTitleCase; -namespace StringToTitleCaseUnitTest +namespace StringToTitleCaseUnitTest; + +[TestClass] +public class StringToTitleCaseUnitTest { - [TestClass] - public class StringToTitleCaseUnitTest + private readonly TextInfo textInfo = new CultureInfo("en-US", false).TextInfo; + + [TestMethod] + public void GivenToTitleCase_WhenStringIsPassed_ConversionToTitleCaseSuccessful() { - private readonly TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo; + Assert.AreEqual("A Tale Of Two Cities", textInfo.ToTitleCase("a tale oF tWo citIes")); - [TestMethod] - public void GivenToTitleCase_WhenStringIsPassed_CoversionToTitleCaseSuccessful() - { - Assert.AreEqual("A Tale Of Two Cities", textInfo.ToTitleCase("a tale oF tWo citIes")); + Assert.AreEqual("Harry Potter And The Deathly Hallows", textInfo.ToTitleCase("harry potter and the Deathly hallows")); + } - Assert.AreEqual("Harry Potter And The Deathly Hallows", textInfo.ToTitleCase("harry potter and the Deathly hallows")); - } + [TestMethod] + public void GivenToTitleCase_WhenWordWithAllUpperCaseIsPassed_ConversionToTitleCaseNotSuccessful() + { + Assert.AreEqual("OLIVER TWIST", textInfo.ToTitleCase("OLIVER TWIST")); + } - [TestMethod] - public void GivenToTitleCase_WhenWordWithAllUpperCaseIsPassed_ConversionToTitleCaseNotSuccessful() + [TestMethod] + public void GivenToTitleCase_WhenWordWithAllUpperCaseIsPassed_ConversionToTitleCaseSuccessful() + { + Assert.AreEqual("Oliver Twist", textInfo.ToTitleCase(textInfo.ToLower("OLIVER TWIST"))); + } + + [TestMethod] + public void GivenToTitleCase_WhenAcronymIsLowercasedFirst_AcronymIsLost() + { + Assert.AreEqual("The NASA Mission", textInfo.ToTitleCase("the NASA mission")); + + Assert.AreEqual("The Nasa Mission", textInfo.ToTitleCase(textInfo.ToLower("THE NASA MISSION"))); + } + + [TestMethod] + public void GivenCurrentCultureIsTurkish_WhenStringToLowerIsUsed_DotlessIAppears() + { + var original = CultureInfo.CurrentCulture; + try { - Assert.AreNotEqual("Oliver Twist", textInfo.ToTitleCase("OLIVER TWIST")); - } + CultureInfo.CurrentCulture = new CultureInfo("tr-TR"); - [TestMethod] - public void GivenToTitleCase_WhenWordWithAllUpperCaseIsPassed_ConversionToTitleCaseSuccessful() + Assert.AreEqual("Olıver Twıst", textInfo.ToTitleCase("OLIVER TWIST".ToLower())); + Assert.AreEqual("Oliver Twist", textInfo.ToTitleCase(textInfo.ToLower("OLIVER TWIST"))); + } + finally { - Assert.AreEqual("Oliver Twist", textInfo.ToTitleCase("OLIVER TWIST".ToLower())); + CultureInfo.CurrentCulture = original; } } + + [TestMethod] + public void GivenToTitleCaseWithMinorWords_WhenStringIsPassed_MinorWordsStayLowercase() + { + Assert.AreEqual("A Tale of Two Cities", "a tale oF tWo citIes".ToTitleCaseWithMinorWords(textInfo)); + + Assert.AreEqual("Harry Potter and the Deathly Hallows", "harry potter and the Deathly hallows".ToTitleCaseWithMinorWords(textInfo)); + + Assert.AreEqual("The Lord of the Rings", "the lord of the rings".ToTitleCaseWithMinorWords(textInfo)); + } + + [TestMethod] + public void GivenToTitleCase_WhenCultureIsTurkish_ResultUsesTurkishCasing() + { + var turkish = new CultureInfo("tr-TR", false).TextInfo; + + Assert.AreEqual("A Tale Of Two Citıes", turkish.ToTitleCase("a tale oF tWo citIes")); + Assert.AreEqual("İstanbul", turkish.ToTitleCase("istanbul")); + Assert.AreEqual("A Tale Of Two Cities", CultureInfo.InvariantCulture.TextInfo.ToTitleCase("a tale oF tWo citIes")); + } + + [TestMethod] + public void GivenToTitleCase_WhenWordHasDigitsOrInnerCapitals_ResultIsNotWhatAHumanWrites() + { + Assert.AreEqual("The 2Nd Edition", textInfo.ToTitleCase("the 2nd edition")); + Assert.AreEqual("My Iphone", textInfo.ToTitleCase("my iPhone")); + Assert.AreEqual("Mcdonald's Menu", textInfo.ToTitleCase("mcdonald's menu")); + Assert.AreEqual("X-Ray Vision", textInfo.ToTitleCase("x-ray vision")); + } } diff --git a/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.csproj b/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.csproj index 0ef0e0cb63..4df3b9a29c 100644 --- a/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.csproj +++ b/strings-csharp/StringToTitleCase/StringToTitleCaseUnitTest/StringToTitleCaseUnitTest.csproj @@ -1,16 +1,21 @@ - net6.0 - + net10.0 + enable + enable false - - - - + + + + + + + + From 06e5cebd2367dd3b0d1b6b88d8b2e88a5494088a Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 1 Nov 2026 17:58:32 +0100 Subject: [PATCH 2/2] StringToTitleCase: set the console to UTF-8 so the Turkish letters print on Windows --- strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs b/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs index 416229c9d7..f789764c4e 100644 --- a/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs +++ b/strings-csharp/StringToTitleCase/StringToTitleCase/Program.cs @@ -13,6 +13,7 @@ Console.WriteLine("a tale oF tWo citIes".ToTitleCaseWithMinorWords(textinfo)); Console.WriteLine("harry potter and the Deathly hallows".ToTitleCaseWithMinorWords(textinfo)); +Console.OutputEncoding = System.Text.Encoding.UTF8; var turkish = new CultureInfo("tr-TR", false).TextInfo; Console.WriteLine(turkish.ToTitleCase("a tale oF tWo citIes")); Console.WriteLine(turkish.ToTitleCase("istanbul"));