From 8654b51a2c6ef4fd385704ed8a181da78bbc03be Mon Sep 17 00:00:00 2001
From: Andreas Bauer <andreas.bauer@stanford.edu>
Date: Wed, 8 Nov 2023 15:45:59 -0800
Subject: [PATCH] Migrate to String Catalogs and introduce Markdown Lint Check

---
 .github/workflows/markdown-lint-check.yml     |  19 +
 .github/workflows/pull_request.yml            |   3 +
 ...rebaseIdentityProviderAccountService.swift |   2 +-
 .../FirebaseEmailVerifiedKey.swift            |   2 +-
 .../FirebaseOAuthCredential.swift             |   4 +-
 .../Resources/Localizable.xcstrings           | 400 ++++++++++++++++++
 .../Resources/de.lproj/Localizable.strings    |  43 --
 .../Resources/en.lproj/Localizable.strings    |  43 --
 .../Resources/Localizable.xcstrings           | 310 ++++++++++++++
 .../Resources/de.lproj/Localizable.strings    |  30 --
 .../Resources/en.lproj/Localizable.strings    |  30 --
 .../UITests/UITests.xcodeproj/project.pbxproj |   4 +
 12 files changed, 740 insertions(+), 150 deletions(-)
 create mode 100644 .github/workflows/markdown-lint-check.yml
 create mode 100644 Sources/SpeziFirebaseAccount/Resources/Localizable.xcstrings
 delete mode 100644 Sources/SpeziFirebaseAccount/Resources/de.lproj/Localizable.strings
 delete mode 100644 Sources/SpeziFirebaseAccount/Resources/en.lproj/Localizable.strings
 create mode 100644 Sources/SpeziFirestore/Resources/Localizable.xcstrings
 delete mode 100644 Sources/SpeziFirestore/Resources/de.lproj/Localizable.strings
 delete mode 100644 Sources/SpeziFirestore/Resources/en.lproj/Localizable.strings

diff --git a/.github/workflows/markdown-lint-check.yml b/.github/workflows/markdown-lint-check.yml
new file mode 100644
index 0000000..9a2d1f2
--- /dev/null
+++ b/.github/workflows/markdown-lint-check.yml
@@ -0,0 +1,19 @@
+#
+# This source file is part of the Stanford Spezi open source project
+#
+# SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+#
+# SPDX-License-Identifier: MIT
+#
+
+name: Monthly Markdown Link Check
+
+on:
+  # Runs at midnight on the first of every month
+  schedule:
+    - cron: "0 0 1 * *"
+
+jobs:
+  markdown_link_check:
+    name: Markdown Link Check
+    uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index eb53387..5a7d62c 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -19,3 +19,6 @@ jobs:
   swiftlint:
     name: SwiftLint
     uses: StanfordSpezi/.github/.github/workflows/swiftlint.yml@v2
+  markdown_link_check:
+    name: Markdown Link Check
+    uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
diff --git a/Sources/SpeziFirebaseAccount/Account Services/FirebaseIdentityProviderAccountService.swift b/Sources/SpeziFirebaseAccount/Account Services/FirebaseIdentityProviderAccountService.swift
index bfbe962..bb093ca 100644
--- a/Sources/SpeziFirebaseAccount/Account Services/FirebaseIdentityProviderAccountService.swift	
+++ b/Sources/SpeziFirebaseAccount/Account Services/FirebaseIdentityProviderAccountService.swift	
@@ -103,7 +103,7 @@ actor FirebaseIdentityProviderAccountService: IdentityProvider, FirebaseAccountS
             throw FirebaseAccountError.notSignedIn
         }
 
-        try await context.dispatchFirebaseAuthAction(on: self) { () -> Void in
+        try await context.dispatchFirebaseAuthAction(on: self) {
             guard let credential = try await requestAppleSignInCredential() else {
                 return // user canceled
             }
diff --git a/Sources/SpeziFirebaseAccount/AccountValues/FirebaseEmailVerifiedKey.swift b/Sources/SpeziFirebaseAccount/AccountValues/FirebaseEmailVerifiedKey.swift
index 605e8d7..7d32861 100644
--- a/Sources/SpeziFirebaseAccount/AccountValues/FirebaseEmailVerifiedKey.swift
+++ b/Sources/SpeziFirebaseAccount/AccountValues/FirebaseEmailVerifiedKey.swift
@@ -43,7 +43,7 @@ extension FirebaseEmailVerifiedKey {
         public typealias Key = FirebaseEmailVerifiedKey
 
         public var body: some View {
-            Text("The FirebaseEmailVerifiedKey cannot be set!")
+            Text(verbatim: "The FirebaseEmailVerifiedKey cannot be set!")
         }
 
         public init(_ value: Binding<Value>) {}
diff --git a/Sources/SpeziFirebaseAccount/AccountValues/FirebaseOAuthCredential.swift b/Sources/SpeziFirebaseAccount/AccountValues/FirebaseOAuthCredential.swift
index 55425c1..0fda2a4 100644
--- a/Sources/SpeziFirebaseAccount/AccountValues/FirebaseOAuthCredential.swift
+++ b/Sources/SpeziFirebaseAccount/AccountValues/FirebaseOAuthCredential.swift
@@ -59,7 +59,7 @@ extension FirebaseOAuthCredentialKey {
         public typealias Key = FirebaseOAuthCredentialKey
 
         public var body: some View {
-            Text("The FirebaseOAuthCredentialKey cannot be set!")
+            Text(verbatim: "The FirebaseOAuthCredentialKey cannot be set!")
         }
 
         public init(_ value: Binding<Value>) {}
@@ -69,7 +69,7 @@ extension FirebaseOAuthCredentialKey {
         public typealias Key = FirebaseOAuthCredentialKey
 
         public var body: some View {
-            Text("The FirebaseOAuthCredentialKey cannot be displayed!")
+            Text(verbatim: "The FirebaseOAuthCredentialKey cannot be displayed!")
         }
 
         public init(_ value: Value) {}
diff --git a/Sources/SpeziFirebaseAccount/Resources/Localizable.xcstrings b/Sources/SpeziFirebaseAccount/Resources/Localizable.xcstrings
new file mode 100644
index 0000000..89eed46
--- /dev/null
+++ b/Sources/SpeziFirebaseAccount/Resources/Localizable.xcstrings
@@ -0,0 +1,400 @@
+{
+  "sourceLanguage" : "en",
+  "strings" : {
+    "E-Mail Verified" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "E-Mail verifiziert"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_ALREADY_IN_USE" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Benutzerkonto existiert bereits"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Account already exists"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_ALREADY_IN_USE_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ein Benutzerkonto mit dieser E-Mail Adresse existiert bereits. Bitte loggen dich mit dem Account ein."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "An account with this email already exists. Please login using your email and password."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_DEFAULT_PASSWORD_RULE_ERROR %lld" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dein Passwort muss mindestens %lld Zeichen lang sein."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Your password must be at least %lld characters long."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Inkorrekte E-Mail Adresse"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Invalid email address"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Bitte gebe eine korrekte E-Mail Adresse ein."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Please enter a valid email address."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Zurücksetzten des Passworts fehlgeschlagen"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Failed to reset password"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Es ist ein Fehler aufgetreten bei dem Versuch Ihr Passwort zurückzusetzen. Bitte versuche es später erneut."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "There was an issue delivering the password reset email. Please try again."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_INVALID_CREDENTIALS" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ungültige Zugangsdaten"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Invalid Credentials"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_INVALID_CREDENTIALS_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Bitte überprüfe die eingegebene E-Mail Adresse und dein Passwort."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Please verify that your email and password are correct."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Anfrage fehlgeschlagen"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Couldn't complete operation"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Diese Anfrage is sicherheitsrelevant und erfordert einen kürzlichen Login."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "This is a security relevant operation that requires a recent login."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_SETUP_ERROR" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Anfrage fehlgeschlagen"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Could not complete account operation"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_SETUP_ERROR_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Bei der Anfrage für dein Benutzerkonto ist ein Fehler aufgetreten. Bitte versuche es später erneut."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "There was an internal error when trying to perform the account operation. Please try again later."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_SIGN_IN_ERROR" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Nicht eingeloggt"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Not signed in"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_SIGN_IN_ERROR_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Die Anfrage konnte nicht ausgeführt werden weil kein verknüpftes Benutzerkonto gefunden wurde."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Couldn't complete this operation as there is no current user account."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_UNKNOWN" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Anfrage fehlgeschlagen"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Failed account operation"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_UNKNOWN_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Bitte überprüfen deine Internet Verbindung und versuche es erneut."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Please check your internet connection and try again."
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_WEAK_PASSWORD" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Schwaches Passwort"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Weak Password"
+          }
+        }
+      }
+    },
+    "FIREBASE_ACCOUNT_WEAK_PASSWORD_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Bitte gebe ein stärkeres Password ein."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Please choose a safer password."
+          }
+        }
+      }
+    },
+    "FIREBASE_APPLE_FAILED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mit Apple anmelden ist fehlgeschlagen"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Sign in with Apple failed"
+          }
+        }
+      }
+    },
+    "FIREBASE_APPLE_FAILED_SUGGESTION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Wir hatten Probleme dich mit Apple anzumelden. Bitte versuche es später erneut."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "We had issues completing your Sign in with Apple request. Please try again later."
+          }
+        }
+      }
+    },
+    "FIREBASE_EMAIL_AND_PASSWORD" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "E-Mail und Password"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "E-Mail and Password"
+          }
+        }
+      }
+    },
+    "FIREBASE_IDENTITY_PROVIDER" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Single Sign-On"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Single Sign-On"
+          }
+        }
+      }
+    },
+    "OAuth Credential" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "OAuth Berechtigung"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "OAuth Credential"
+          }
+        }
+      }
+    }
+  },
+  "version" : "1.0"
+}
\ No newline at end of file
diff --git a/Sources/SpeziFirebaseAccount/Resources/de.lproj/Localizable.strings b/Sources/SpeziFirebaseAccount/Resources/de.lproj/Localizable.strings
deleted file mode 100644
index 1264c8f..0000000
--- a/Sources/SpeziFirebaseAccount/Resources/de.lproj/Localizable.strings
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// This source file is part of the Stanford Spezi open-source project
-//
-// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
-//
-// SPDX-License-Identifier: MIT
-//
-
-"FIREBASE_EMAIL_AND_PASSWORD" = "E-Mail und Password";
-"FIREBASE_IDENTITY_PROVIDER" = "Single Sign-On";
-
-// MARK: ERRORS
-"FIREBASE_ACCOUNT_DEFAULT_PASSWORD_RULE_ERROR %lld" = "Dein Passwort muss mindestens %lld Zeichen lang sein.";
-
-"FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL" = "Inkorrekte E-Mail Adresse";
-"FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL_SUGGESTION" = "Bitte gebe eine korrekte E-Mail Adresse ein.";
-
-"FIREBASE_ACCOUNT_ALREADY_IN_USE" = "Benutzerkonto existiert bereits";
-"FIREBASE_ACCOUNT_ALREADY_IN_USE_SUGGESTION" = "Ein Benutzerkonto mit dieser E-Mail Adresse existiert bereits. Bitte loggen dich mit dem Account ein.";
-
-"FIREBASE_ACCOUNT_WEAK_PASSWORD" = "Schwaches Passwort";
-"FIREBASE_ACCOUNT_WEAK_PASSWORD_SUGGESTION" = "Bitte gebe ein stärkeres Password ein.";
-
-"FIREBASE_ACCOUNT_INVALID_CREDENTIALS" = "Ungültige Zugangsdaten";
-"FIREBASE_ACCOUNT_INVALID_CREDENTIALS_SUGGESTION" = "Bitte überprüfe die eingegebene E-Mail Adresse und dein Passwort.";
-
-"FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET" = "Zurücksetzten des Passworts fehlgeschlagen";
-"FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET_SUGGESTION" = "Es ist ein Fehler aufgetreten bei dem Versuch Ihr Passwort zurückzusetzen. Bitte versuche es später erneut.";
-
-"FIREBASE_ACCOUNT_SETUP_ERROR" = "Anfrage fehlgeschlagen";
-"FIREBASE_ACCOUNT_SETUP_ERROR_SUGGESTION" = "Bei der Anfrage für dein Benutzerkonto ist ein Fehler aufgetreten. Bitte versuche es später erneut.";
-
-"FIREBASE_ACCOUNT_SIGN_IN_ERROR" = "Nicht eingeloggt";
-"FIREBASE_ACCOUNT_SIGN_IN_ERROR_SUGGESTION" = "Die Anfrage konnte nicht ausgeführt werden weil kein verknüpftes Benutzerkonto gefunden wurde.";
-
-"FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR" = "Anfrage fehlgeschlagen";
-"FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR_SUGGESTION" = "Diese Anfrage is sicherheitsrelevant und erfordert einen kürzlichen Login.";
-
-"FIREBASE_APPLE_FAILED" = "Mit Apple anmelden ist fehlgeschlagen";
-"FIREBASE_APPLE_FAILED_SUGGESTION" = "Wir hatten Probleme dich mit Apple anzumelden. Bitte versuche es später erneut.";
-
-"FIREBASE_ACCOUNT_UNKNOWN" = "Anfrage fehlgeschlagen";
-"FIREBASE_ACCOUNT_UNKNOWN_SUGGESTION" = "Bitte überprüfen deine Internet Verbindung und versuche es erneut.";
diff --git a/Sources/SpeziFirebaseAccount/Resources/en.lproj/Localizable.strings b/Sources/SpeziFirebaseAccount/Resources/en.lproj/Localizable.strings
deleted file mode 100644
index 9851fcb..0000000
--- a/Sources/SpeziFirebaseAccount/Resources/en.lproj/Localizable.strings
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// This source file is part of the Stanford Spezi open-source project
-//
-// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
-//
-// SPDX-License-Identifier: MIT
-//
-
-"FIREBASE_EMAIL_AND_PASSWORD" = "E-Mail and Password";
-"FIREBASE_IDENTITY_PROVIDER" = "Single Sign-On";
-
-// MARK: ERRORS
-"FIREBASE_ACCOUNT_DEFAULT_PASSWORD_RULE_ERROR %lld" = "Your password must be at least %lld characters long.";
-
-"FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL" = "Invalid email address";
-"FIREBASE_ACCOUNT_ERROR_INVALID_EMAIL_SUGGESTION" = "Please enter a valid email address.";
-
-"FIREBASE_ACCOUNT_ALREADY_IN_USE" = "Account already exists";
-"FIREBASE_ACCOUNT_ALREADY_IN_USE_SUGGESTION" = "An account with this email already exists. Please login using your email and password.";
-
-"FIREBASE_ACCOUNT_WEAK_PASSWORD" = "Weak Password";
-"FIREBASE_ACCOUNT_WEAK_PASSWORD_SUGGESTION" = "Please choose a safer password.";
-
-"FIREBASE_ACCOUNT_INVALID_CREDENTIALS" = "Invalid Credentials";
-"FIREBASE_ACCOUNT_INVALID_CREDENTIALS_SUGGESTION" = "Please verify that your email and password are correct.";
-
-"FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET" = "Failed to reset password";
-"FIREBASE_ACCOUNT_FAILED_PASSWORD_RESET_SUGGESTION" = "There was an issue delivering the password reset email. Please try again.";
-
-"FIREBASE_ACCOUNT_SETUP_ERROR" = "Could not complete account operation";
-"FIREBASE_ACCOUNT_SETUP_ERROR_SUGGESTION" = "There was an internal error when trying to perform the account operation. Please try again later.";
-
-"FIREBASE_ACCOUNT_SIGN_IN_ERROR" = "Not signed in";
-"FIREBASE_ACCOUNT_SIGN_IN_ERROR_SUGGESTION" = "Couldn't complete this operation as there is no current user account.";
-
-"FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR" = "Couldn't complete operation";
-"FIREBASE_ACCOUNT_REQUIRE_RECENT_LOGIN_ERROR_SUGGESTION" = "This is a security relevant operation that requires a recent login.";
-
-"FIREBASE_APPLE_FAILED" = "Sign in with Apple failed";
-"FIREBASE_APPLE_FAILED_SUGGESTION" = "We had issues completing your Sign in with Apple request. Please try again later.";
-
-"FIREBASE_ACCOUNT_UNKNOWN" = "Failed account operation";
-"FIREBASE_ACCOUNT_UNKNOWN_SUGGESTION" = "Please check your internet connection and try again.";
diff --git a/Sources/SpeziFirestore/Resources/Localizable.xcstrings b/Sources/SpeziFirestore/Resources/Localizable.xcstrings
new file mode 100644
index 0000000..21c9e57
--- /dev/null
+++ b/Sources/SpeziFirestore/Resources/Localizable.xcstrings
@@ -0,0 +1,310 @@
+{
+  "sourceLanguage" : "en",
+  "strings" : {
+    "FIRESTORE_ERROR_ABORTED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Vorgang wurde abgebrochen, typischerweise aufgrund eines Nebenläufigkeitsproblems wie Transaktionsabbrüchen usw."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "The operation was aborted, typically due to a concurrency issue like transaction aborts, etc."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_ALREADYEXISTS" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ein Dokument, das wir erstellen wollten, existiert bereits."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Some document that we attempted to create already exists."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_CANCELLED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Vorgang wurde abgebrochen (typischerweise vom Aufrufer)."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "The operation was cancelled (typically by the caller)."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_DATALOSS" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Unwiederbringlicher Datenverlust oder Beschädigung."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Unrecoverable data loss or corruption."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_DEADLINEEXCEEDED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Die Frist ist abgelaufen, bevor der Vorgang abgeschlossen werden konnte. Bei Vorgängen, die den Systemzustand ändern, kann dieser Fehler zurückgegeben werden, auch wenn der Vorgang erfolgreich abgeschlossen wurde. Zum Beispiel könnte eine erfolgreiche Antwort von einem Server so lange verzögert worden sein, dass die Frist abgelaufen ist."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Deadline expired before operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long enough for the deadline to expire."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_DECODINGFIELDCONFLICT %@" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Feldkonflikt während der Dekodierung: %@"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Field conflict during the decoding: %@"
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_DECODINGNOTSUPPORTED %@" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dekodierung wird nicht unterstützt: %@"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Decoding is not supported: %@"
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_ENCODINGNOTSUPPORTED %@" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Kodierung wird nicht unterstützt: %@"
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Encoding is not supported: %@"
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_FAILEDPRECONDITION" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Vorgang wurde abgelehnt, weil das System nicht im für die Ausführung des Vorgangs erforderlichen Zustand ist."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Operation was rejected because the system is not in a state required for the operation's execution."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_INTERNAL" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Interne Fehler. Das bedeutet, dass einige von dem zugrunde liegenden System erwartete Invarianten verletzt wurden. Wenn Sie einen dieser Fehler sehen, ist etwas sehr kaputt."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Internal errors. Means some invariants expected by underlying system has been broken. If you see one of these errors, something is very broken."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_INVALIDARGUMENT" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Client hat ein ungültiges Argument angegeben. Beachten Sie, dass dies sich von FailedPrecondition unterscheidet. InvalidArgument weist auf Argumente hin, die unabhängig vom Systemzustand problematisch sind (z. B. ein ungültiger Feldname)."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Client specified an invalid argument. Note that this differs from FailedPrecondition. InvalidArgument indicates arguments that are problematic regardless of the state of the system (e.g., an invalid field name)."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_NOTFOUND" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ein angefordertes Dokument wurde nicht gefunden."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Some requested document was not found."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_OUTOFRANGE" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Vorgang wurde außerhalb des gültigen Bereichs versucht."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Operation was attempted past the valid range."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_PERMISSIONDENIED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Anrufer hat keine Berechtigung, den angegebenen Vorgang auszuführen."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "The caller does not have permission to execute the specified operation."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_RESOURCEEXHAUSTED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Eine Ressource ist erschöpft, vielleicht ein Benutzerkontingent oder vielleicht ist das gesamte Dateisystem voll."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_UNAUTHENTICATED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Die Anfrage verfügt nicht über gültige Authentifizierungsdaten für den Vorgang."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "The request does not have valid authentication credentials for the operation."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_UNAVAILABLE" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Dienst ist derzeit nicht verfügbar. Dies ist höchstwahrscheinlich ein vorübergehender Zustand und kann durch erneutes Versuchen mit einer Verzögerung korrigiert werden."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "The service is currently unavailable. This is a most likely a transient condition and may be corrected by retrying with a backoff."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_UNIMPLEMENTED" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Der Vorgang ist nicht implementiert oder nicht aktiviert/unterstützt."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Operation is not implemented or not supported/enabled."
+          }
+        }
+      }
+    },
+    "FIRESTORE_ERROR_UNKNOWN" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Unbekannter Fehler oder ein Fehler aus einem anderen Fehlerdomäne."
+          }
+        },
+        "en" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Unknown error or an error from a different error domain."
+          }
+        }
+      }
+    }
+  },
+  "version" : "1.0"
+}
\ No newline at end of file
diff --git a/Sources/SpeziFirestore/Resources/de.lproj/Localizable.strings b/Sources/SpeziFirestore/Resources/de.lproj/Localizable.strings
deleted file mode 100644
index 9859125..0000000
--- a/Sources/SpeziFirestore/Resources/de.lproj/Localizable.strings
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// This source file is part of the Stanford Spezi open-source project
-//
-// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
-//
-// SPDX-License-Identifier: MIT
-//
-
-
-// MARK: ERRORS
-
-"FIRESTORE_ERROR_CANCELLED" = "Der Vorgang wurde abgebrochen (typischerweise vom Aufrufer).";
-"FIRESTORE_ERROR_INVALIDARGUMENT" = "Der Client hat ein ungültiges Argument angegeben. Beachten Sie, dass dies sich von FailedPrecondition unterscheidet. InvalidArgument weist auf Argumente hin, die unabhängig vom Systemzustand problematisch sind (z. B. ein ungültiger Feldname).";
-"FIRESTORE_ERROR_DEADLINEEXCEEDED" = "Die Frist ist abgelaufen, bevor der Vorgang abgeschlossen werden konnte. Bei Vorgängen, die den Systemzustand ändern, kann dieser Fehler zurückgegeben werden, auch wenn der Vorgang erfolgreich abgeschlossen wurde. Zum Beispiel könnte eine erfolgreiche Antwort von einem Server so lange verzögert worden sein, dass die Frist abgelaufen ist.";
-"FIRESTORE_ERROR_NOTFOUND" = "Ein angefordertes Dokument wurde nicht gefunden.";
-"FIRESTORE_ERROR_ALREADYEXISTS" = "Ein Dokument, das wir erstellen wollten, existiert bereits.";
-"FIRESTORE_ERROR_PERMISSIONDENIED" = "Der Anrufer hat keine Berechtigung, den angegebenen Vorgang auszuführen.";
-"FIRESTORE_ERROR_RESOURCEEXHAUSTED" = "Eine Ressource ist erschöpft, vielleicht ein Benutzerkontingent oder vielleicht ist das gesamte Dateisystem voll.";
-"FIRESTORE_ERROR_FAILEDPRECONDITION" = "Der Vorgang wurde abgelehnt, weil das System nicht im für die Ausführung des Vorgangs erforderlichen Zustand ist.";
-"FIRESTORE_ERROR_ABORTED" = "Der Vorgang wurde abgebrochen, typischerweise aufgrund eines Nebenläufigkeitsproblems wie Transaktionsabbrüchen usw.";
-"FIRESTORE_ERROR_OUTOFRANGE" = "Der Vorgang wurde außerhalb des gültigen Bereichs versucht.";
-"FIRESTORE_ERROR_UNIMPLEMENTED" = "Der Vorgang ist nicht implementiert oder nicht aktiviert/unterstützt.";
-"FIRESTORE_ERROR_INTERNAL" = "Interne Fehler. Das bedeutet, dass einige von dem zugrunde liegenden System erwartete Invarianten verletzt wurden. Wenn Sie einen dieser Fehler sehen, ist etwas sehr kaputt.";
-"FIRESTORE_ERROR_UNAVAILABLE" = "Der Dienst ist derzeit nicht verfügbar. Dies ist höchstwahrscheinlich ein vorübergehender Zustand und kann durch erneutes Versuchen mit einer Verzögerung korrigiert werden.";
-"FIRESTORE_ERROR_DATALOSS" = "Unwiederbringlicher Datenverlust oder Beschädigung.";
-"FIRESTORE_ERROR_UNAUTHENTICATED" = "Die Anfrage verfügt nicht über gültige Authentifizierungsdaten für den Vorgang.";
-"FIRESTORE_ERROR_DECODINGNOTSUPPORTED %@" = "Dekodierung wird nicht unterstützt: %@";
-"FIRESTORE_ERROR_DECODINGFIELDCONFLICT %@" = "Feldkonflikt während der Dekodierung: %@";
-"FIRESTORE_ERROR_ENCODINGNOTSUPPORTED %@" = "Kodierung wird nicht unterstützt: %@";
-"FIRESTORE_ERROR_UNKNOWN" = "Unbekannter Fehler oder ein Fehler aus einem anderen Fehlerdomäne.";
diff --git a/Sources/SpeziFirestore/Resources/en.lproj/Localizable.strings b/Sources/SpeziFirestore/Resources/en.lproj/Localizable.strings
deleted file mode 100644
index 38b0e5a..0000000
--- a/Sources/SpeziFirestore/Resources/en.lproj/Localizable.strings
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// This source file is part of the Stanford Spezi open-source project
-//
-// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
-//
-// SPDX-License-Identifier: MIT
-//
-
-
-// MARK: ERRORS
-
-"FIRESTORE_ERROR_CANCELLED" = "The operation was cancelled (typically by the caller).";
-"FIRESTORE_ERROR_INVALIDARGUMENT" = "Client specified an invalid argument. Note that this differs from FailedPrecondition. InvalidArgument indicates arguments that are problematic regardless of the state of the system (e.g., an invalid field name).";
-"FIRESTORE_ERROR_DEADLINEEXCEEDED" = "Deadline expired before operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long enough for the deadline to expire.";
-"FIRESTORE_ERROR_NOTFOUND" = "Some requested document was not found.";
-"FIRESTORE_ERROR_ALREADYEXISTS" = "Some document that we attempted to create already exists.";
-"FIRESTORE_ERROR_PERMISSIONDENIED" = "The caller does not have permission to execute the specified operation.";
-"FIRESTORE_ERROR_RESOURCEEXHAUSTED" = "Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.";
-"FIRESTORE_ERROR_FAILEDPRECONDITION" = "Operation was rejected because the system is not in a state required for the operation's execution.";
-"FIRESTORE_ERROR_ABORTED" = "The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.";
-"FIRESTORE_ERROR_OUTOFRANGE" = "Operation was attempted past the valid range.";
-"FIRESTORE_ERROR_UNIMPLEMENTED" = "Operation is not implemented or not supported/enabled.";
-"FIRESTORE_ERROR_INTERNAL" = "Internal errors. Means some invariants expected by underlying system has been broken. If you see one of these errors, something is very broken.";
-"FIRESTORE_ERROR_UNAVAILABLE" = "The service is currently unavailable. This is a most likely a transient condition and may be corrected by retrying with a backoff.";
-"FIRESTORE_ERROR_DATALOSS" = "Unrecoverable data loss or corruption.";
-"FIRESTORE_ERROR_UNAUTHENTICATED" = "The request does not have valid authentication credentials for the operation.";
-"FIRESTORE_ERROR_DECODINGNOTSUPPORTED %@" = "Decoding is not supported: %@";
-"FIRESTORE_ERROR_DECODINGFIELDCONFLICT %@" = "Field conflict during the decoding: %@";
-"FIRESTORE_ERROR_ENCODINGNOTSUPPORTED %@" = "Encoding is not supported: %@";
-"FIRESTORE_ERROR_UNKNOWN" = "Unknown error or an error from a different error domain.";
diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj
index 1d2732e..062f82b 100644
--- a/Tests/UITests/UITests.xcodeproj/project.pbxproj
+++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		2DC17AC2311FA737BFC62A0D /* asdf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC17EAD96B69481F8C09482 /* asdf.swift */; };
 		2F148C00298BB15900031B7F /* FirebaseAccountTestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F148BFF298BB15900031B7F /* FirebaseAccountTestsView.swift */; };
 		2F2E4B8429749C5900FF710F /* FirestoreDataStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2E4B8329749C5900FF710F /* FirestoreDataStorageTests.swift */; };
 		2F6D139A28F5F386007C25D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F6D139928F5F386007C25D6 /* Assets.xcassets */; };
@@ -38,6 +39,7 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		2DC17EAD96B69481F8C09482 /* asdf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = asdf.swift; path = ../../Sources/SpeziFirebaseAccount/Resources/asdf.swift; sourceTree = "<group>"; };
 		2F01E8CE291493560089C46B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
 		2F148BFF298BB15900031B7F /* FirebaseAccountTestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAccountTestsView.swift; sourceTree = "<group>"; };
 		2F2E4B8329749C5900FF710F /* FirestoreDataStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestoreDataStorageTests.swift; sourceTree = "<group>"; };
@@ -100,6 +102,7 @@
 				2F6D13AF28F5F386007C25D6 /* TestAppUITests */,
 				2F6D139328F5F384007C25D6 /* Products */,
 				2F6D13C228F5F3BE007C25D6 /* Frameworks */,
+				2DC17EAD96B69481F8C09482 /* asdf.swift */,
 			);
 			sourceTree = "<group>";
 		};
@@ -292,6 +295,7 @@
 				2F8A431729130BBC005D2B8F /* TestAppType.swift in Sources */,
 				2F9F07F129090B0500CDC598 /* TestAppDelegate.swift in Sources */,
 				97359F642ADB27500080CB11 /* FirebaseStorageTestsView.swift in Sources */,
+				2DC17AC2311FA737BFC62A0D /* asdf.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};