diff --git a/Source/FileManager/ReplacementCharacters.cs b/Source/FileManager/ReplacementCharacters.cs index 8609e1b86..22c604f00 100644 --- a/Source/FileManager/ReplacementCharacters.cs +++ b/Source/FileManager/ReplacementCharacters.cs @@ -53,6 +53,13 @@ public void Update(char charToReplace, string replacementString, string descript public static Replacement OpenAngleBracket(string replacement) => new('<', replacement, "Open Angle Bracket"); public static Replacement CloseAngleBracket(string replacement) => new('>', replacement, "Close Angle Bracket"); public static Replacement Pipe(string replacement) => new('|', replacement, "Vertical Line"); + public static Replacement a_umlaut(string replacement = "ae") => new('ä', replacement, "a umlaut (ä)"); + public static Replacement o_umlaut(string replacement = "oe") => new('ö', replacement, "o umlaut (ö)"); + public static Replacement u_umlaut(string replacement = "ue") => new('ü', replacement, "u umlaut (ü)"); + public static Replacement A_umlaut(string replacement = "Ae") => new('Ä', replacement, "A umlaut (Ä)"); + public static Replacement O_umlaut(string replacement = "Oe") => new('Ö', replacement, "O umlaut (Ö)"); + public static Replacement U_umlaut(string replacement = "Ue") => new('Ü', replacement, "U umlaut (Ü)"); + public static Replacement Eszett(string replacement = "ss") => new('ß', replacement, "Eszett (ß)"); } @@ -92,7 +99,14 @@ public override bool Equals(object? obj) Replacement.Colon("_"), Replacement.Asterisk("✱"), Replacement.QuestionMark("?"), - Replacement.Pipe("⏐")] + Replacement.Pipe("⏐"), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; private static readonly ReplacementCharacters HiFi_Other = new() @@ -103,7 +117,14 @@ public override bool Equals(object? obj) Replacement.FilenameBackSlash("\\"), Replacement.OpenQuote("“"), Replacement.CloseQuote("”"), - Replacement.OtherQuote("\"")] + Replacement.OtherQuote("\""), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; private static readonly ReplacementCharacters LoFi_NTFS = new() @@ -117,7 +138,14 @@ public override bool Equals(object? obj) Replacement.OtherQuote("'"), Replacement.OpenAngleBracket("{"), Replacement.CloseAngleBracket("}"), - Replacement.Colon("-")] + Replacement.Colon("-"), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; private static readonly ReplacementCharacters LoFi_Other = new() @@ -128,7 +156,14 @@ public override bool Equals(object? obj) Replacement.FilenameBackSlash("\\"), Replacement.OpenQuote("\""), Replacement.CloseQuote("\""), - Replacement.OtherQuote("\"")] + Replacement.OtherQuote("\""), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; private static readonly ReplacementCharacters BareBones_NTFS = new() @@ -139,7 +174,14 @@ public override bool Equals(object? obj) Replacement.FilenameBackSlash("_"), Replacement.OpenQuote("_"), Replacement.CloseQuote("_"), - Replacement.OtherQuote("_")] + Replacement.OtherQuote("_"), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; private static readonly ReplacementCharacters BareBones_Other = new() @@ -150,7 +192,14 @@ public override bool Equals(object? obj) Replacement.FilenameBackSlash("\\"), Replacement.OpenQuote("\""), Replacement.CloseQuote("\""), - Replacement.OtherQuote("\"")] + Replacement.OtherQuote("\""), + Replacement.a_umlaut(), + Replacement.o_umlaut(), + Replacement.u_umlaut(), + Replacement.A_umlaut(), + Replacement.O_umlaut(), + Replacement.U_umlaut(), + Replacement.Eszett()] }; #endregion /// diff --git a/Source/_Tests/FileManager.Tests/FileUtilityTests.cs b/Source/_Tests/FileManager.Tests/FileUtilityTests.cs index 6c0e7f7bf..753e540c4 100644 --- a/Source/_Tests/FileManager.Tests/FileUtilityTests.cs +++ b/Source/_Tests/FileManager.Tests/FileUtilityTests.cs @@ -221,6 +221,48 @@ public void Tests(string input, string extension, string expected, PlatformID pl } } +[TestClass] +public class UmlautReplacements +{ + static readonly ReplacementCharacters Default = ReplacementCharacters.Default(true); + static readonly ReplacementCharacters LoFi = ReplacementCharacters.LoFiDefault(true); + static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones(true); + + [TestMethod] + [DataRow("Märchen", "Maerchen")] + [DataRow("Über den Wolken", "Ueber den Wolken")] + [DataRow("schön", "schoen")] + [DataRow("grün", "gruen")] + [DataRow("Straße", "Strasse")] + [DataRow("Ärger", "Aerger")] + [DataRow("Ökonom", "Oekonom")] + [DataRow("Übel", "Uebel")] + public void DefaultPreset_ReplacesUmlauts(string input, string expected) + => Default.ReplaceFilenameChars(input).Should().Be(expected); + + [TestMethod] + [DataRow("Märchen", "Maerchen")] + [DataRow("Straße", "Strasse")] + public void LoFiPreset_ReplacesUmlauts(string input, string expected) + => LoFi.ReplaceFilenameChars(input).Should().Be(expected); + + [TestMethod] + [DataRow("Märchen", "Maerchen")] + [DataRow("Straße", "Strasse")] + public void BarebonesPreset_ReplacesUmlauts(string input, string expected) + => Barebones.ReplaceFilenameChars(input).Should().Be(expected); + + // Regression guard: the Defaults block once had its ASCII string delimiters + // auto-formatted into curly quotes (U+201C/U+201D), which broke compilation. + // Pin the actual Unicode replacement codepoints so a future re-corruption + // is caught here even if the file still compiles. + [TestMethod] + [DataRow("\"foo\"", "“foo”")] // ASCII " between non-letters → OpenQuote U+201C / CloseQuote U+201D + [DataRow("\"a\"b\"", "“a"b”")] // ASCII " surrounded by letters on both sides → OtherQuote U+FF02 + public void DefaultPreset_UsesUnicodeCurlyQuotesForOpenAndClose(string input, string expected) + => Default.ReplaceFilenameChars(input).Should().Be(expected); +} + [TestClass] public class RemoveLastCharacter {