@@ -35,6 +35,43 @@ public enum Punycode {
3535 /// Delimiter used in Punycode to separate basic and encoded code points.
3636private static let labelDelimiter : Character = " - "
3737
38+ /// Encodes a full domain name (e.g. `bücher.com` or `🍿.com`) using IDNA rules.
39+ ///
40+ /// This function splits the domain into labels, encodes each with Punycode if needed,
41+ /// and prepends "xn--" to any encoded label as per IDNA.
42+ ///
43+ /// - Parameter domain: A full domain name (may include Unicode).
44+ /// - Returns: The encoded domain (e.g. "xn--bcher-kva.com")
45+ public static func encodeDomain( _ domain: String ) throws -> String {
46+ return try domain
47+ . split ( separator: " . " )
48+ . map { labelin
49+ if label. allSatisfy ( \. isASCII) {
50+ return String ( label)
51+ } else {
52+ let encoded = try Punycode . encode ( String ( label) )
53+ return " xn-- " + encoded
54+ }
55+ } . joined ( separator: " . " )
56+ }
57+
58+ /// Decodes a full Punycode-based domain back to its Unicode representation.
59+ ///
60+ /// - Parameter domain: A domain name with potential "xn--" labels.
61+ /// - Returns: A Unicode-decoded domain name.
62+ public static func decodeDomain( _ domain: String ) throws -> String {
63+ return try domain
64+ . split ( separator: " . " )
65+ . map { labelin
66+ if label. hasPrefix ( " xn-- " ) {
67+ let punycode = String ( label. dropFirst ( 4 ) )
68+ return try Punycode . decode ( punycode)
69+ } else {
70+ return String ( label)
71+ }
72+ } . joined ( separator: " . " )
73+ }
74+
3875 /// Decodes a Punycode string into a Unicode string.
3976 ///
4077 /// - Parameter punycode: The Punycode-encoded string (must be ASCII only).