diff --git a/AppleLibs.xcodeproj/project.pbxproj b/AppleLibs.xcodeproj/project.pbxproj
index 7211c1bae4e519e0f98c93a16e832e9ec769d49e..258563d0b555034ce78d802cc22a8143dcbb37a1 100644
--- a/AppleLibs.xcodeproj/project.pbxproj
+++ b/AppleLibs.xcodeproj/project.pbxproj
@@ -25,6 +25,7 @@
 		F673A9A12635EF8D0017AD37 /* Future in Frameworks */ = {isa = PBXBuildFile; productRef = F673A9A02635EF8D0017AD37 /* Future */; };
 		F68BFE6B263B28B000E893E5 /* UIViewController+Screenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = F68BFE6A263B28B000E893E5 /* UIViewController+Screenshot.swift */; };
 		F68C2D422616482A00042967 /* IsoDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F68C2D412616482A00042967 /* IsoDateFormatter.swift */; };
+		F6991D0A275AC34200C5AFFD /* TemplatedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6991D09275AC34200C5AFFD /* TemplatedString.swift */; };
 		F6A251A0260B670000132DEC /* AppleLibs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A25196260B66FF00132DEC /* AppleLibs.framework */; };
 		F6A251A5260B670000132DEC /* AppleLibsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A251A4260B670000132DEC /* AppleLibsTests.swift */; };
 		F6A251A7260B670000132DEC /* AppleLibs.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A25199260B66FF00132DEC /* AppleLibs.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -61,6 +62,7 @@
 		F673A9932635EF510017AD37 /* RequestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestBuilder.swift; sourceTree = "<group>"; };
 		F68BFE6A263B28B000E893E5 /* UIViewController+Screenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Screenshot.swift"; sourceTree = "<group>"; };
 		F68C2D412616482A00042967 /* IsoDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IsoDateFormatter.swift; sourceTree = "<group>"; };
+		F6991D09275AC34200C5AFFD /* TemplatedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplatedString.swift; sourceTree = "<group>"; };
 		F6A25196260B66FF00132DEC /* AppleLibs.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppleLibs.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		F6A25199260B66FF00132DEC /* AppleLibs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleLibs.h; sourceTree = "<group>"; };
 		F6A2519A260B66FF00132DEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -129,6 +131,7 @@
 			children = (
 				F6A251BD260B699100132DEC /* String+Html.swift */,
 				F62B93A6261A492700D7F8E6 /* String+Size.swift */,
+				F6991D09275AC34200C5AFFD /* TemplatedString.swift */,
 			);
 			path = String;
 			sourceTree = "<group>";
@@ -343,6 +346,7 @@
 				F623A66C2635B57500F50371 /* NumberFormatter.swift in Sources */,
 				F6CD575026D246470051B38E /* Float+Rounded.swift in Sources */,
 				F6A251BE260B699100132DEC /* String+Html.swift in Sources */,
+				F6991D0A275AC34200C5AFFD /* TemplatedString.swift in Sources */,
 				F673A9952635EF510017AD37 /* HttpStatusCode.swift in Sources */,
 				F6450F6D26A0CEA200076347 /* UIColor+Hex.swift in Sources */,
 				F673A9982635EF510017AD37 /* ResponseHandler.swift in Sources */,
diff --git a/AppleLibs/Utils/String/TemplatedString.swift b/AppleLibs/Utils/String/TemplatedString.swift
new file mode 100644
index 0000000000000000000000000000000000000000..04b1399be63d5c30975cb1563c9d702727e0a73f
--- /dev/null
+++ b/AppleLibs/Utils/String/TemplatedString.swift
@@ -0,0 +1,48 @@
+//
+//  TemplatedString.swift
+//  AppleLibs
+//
+//  Created by Tobias on 03.12.21.
+//
+//  Source: https://medium.com/flawless-app-stories/minimal-templating-system-in-swift-in-only-10-lines-b031963150e7
+//
+
+import Foundation
+
+@dynamicMemberLookup
+struct TemplatedString
+{
+    var template : String
+    private var data : [String:String]
+    var evaluatedString : String { data.reduce(template) { $0.replacingOccurrences(of: "${#\($1.key)}", with: $1.value) } }
+    
+    init(template: String, data: [String:String] = [:]) {
+        self.template = template
+        self.data = data
+    }
+    
+    subscript (dynamicMember member: String) -> CustomStringConvertible? {
+        get {
+            data[member]
+        }
+        set {
+            data[member] = newValue?.description
+        }
+    }
+    
+    subscript (dynamicMember member: String) -> Date {
+        get {
+            dateFormatter.date(from: data[member] ?? "") ?? Date(timeIntervalSince1970: 0)
+        }
+        set {
+            data[member] = dateFormatter.string(from: newValue)
+        }
+    }
+    
+    let dateFormatter : DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
+        formatter.calendar = Calendar(identifier: .gregorian)
+        return formatter
+    }()
+}