i18n.rs
| 1 | //! German texts. English is the source language and the key: `tr("Log out")`. |
| 2 | //! A `{}` in a key matches any text, so texts with values translate too, also the server's messages. |
| 3 | //! A German text uses `{}` in the same order, or `{0}`, `{1}` to reorder. |
| 4 | |
| 5 | use std::sync::OnceLock; |
| 6 | |
| 7 | fn german() -> bool { |
| 8 | static GERMAN: OnceLock<bool> = OnceLock::new(); |
| 9 | *GERMAN.get_or_init(|| { |
| 10 | web_sys::window() |
| 11 | .and_then(|w| w.navigator().language()) |
| 12 | .is_some_and(|l| l.to_lowercase().starts_with("de")) |
| 13 | }) |
| 14 | } |
| 15 | |
| 16 | /// Lets the browser pick German hyphenation, spell checking and date inputs. |
| 17 | pub fn set_document_language() { |
| 18 | if let Some(root) = web_sys::window() |
| 19 | .and_then(|w| w.document()) |
| 20 | .and_then(|d| d.document_element()) |
| 21 | { |
| 22 | let _ = root.set_attribute("lang", if german() { "de" } else { "en" }); |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | /// The text in the user's language. Falls back to English. |
| 27 | pub fn tr(en: &str) -> String { |
| 28 | if !german() { |
| 29 | return en.to_owned(); |
| 30 | } |
| 31 | DE.iter() |
| 32 | .find_map(|(key, de)| matches(key, en).map(|values| fill(de, &values))) |
| 33 | .unwrap_or_else(|| en.to_owned()) |
| 34 | } |
| 35 | |
| 36 | /// The texts that the `{}` in `key` stand for, if `text` fits the key. |
| 37 | fn matches<'a>(key: &str, text: &'a str) -> Option<Vec<&'a str>> { |
| 38 | let parts: Vec<&str> = key.split("{}").collect(); |
| 39 | let mut rest = text.strip_prefix(parts[0])?; |
| 40 | let mut values = Vec::new(); |
| 41 | for (i, part) in parts.iter().enumerate().skip(1) { |
| 42 | let end = if i == parts.len() - 1 { |
| 43 | rest.strip_suffix(part).map(str::len)? |
| 44 | } else { |
| 45 | rest.find(part)? |
| 46 | }; |
| 47 | values.push(&rest[..end]); |
| 48 | rest = &rest[end + part.len()..]; |
| 49 | } |
| 50 | rest.is_empty().then_some(values) |
| 51 | } |
| 52 | |
| 53 | fn fill(de: &str, values: &[&str]) -> String { |
| 54 | let mut out = de.to_owned(); |
| 55 | for (i, v) in values.iter().enumerate() { |
| 56 | out = out.replace(&format!("{{{i}}}"), v); |
| 57 | } |
| 58 | for v in values { |
| 59 | out = out.replacen("{}", v, 1); |
| 60 | } |
| 61 | out |
| 62 | } |
| 63 | |
| 64 | #[cfg(test)] |
| 65 | mod tests { |
| 66 | use super::*; |
| 67 | |
| 68 | #[test] |
| 69 | fn keys_with_values_match() { |
| 70 | assert_eq!(matches("{} points.", "12 points."), Some(vec!["12"])); |
| 71 | assert_eq!( |
| 72 | matches( |
| 73 | "Theme: {}. Click to change.", |
| 74 | "Theme: dark. Click to change." |
| 75 | ), |
| 76 | Some(vec!["dark"]) |
| 77 | ); |
| 78 | assert_eq!(matches("{} points.", "12 points!"), None); |
| 79 | assert_eq!(matches("Log out", "Log out"), Some(vec![])); |
| 80 | assert_eq!(fill("{1}, dann {0}", &["a", "b"]), "b, dann a"); |
| 81 | assert_eq!(fill("{} Punkte", &["3"]), "3 Punkte"); |
| 82 | } |
| 83 | |
| 84 | #[test] |
| 85 | fn german_texts_keep_every_value() { |
| 86 | for (en, de) in DE { |
| 87 | let values = en.matches("{}").count(); |
| 88 | let used = de.matches("{}").count() |
| 89 | + (0..values) |
| 90 | .filter(|i| de.contains(&format!("{{{i}}}"))) |
| 91 | .count(); |
| 92 | assert_eq!(values, used, "{en}"); |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// English key, German text. |
| 98 | const DE: &[(&str, &str)] = &[ |
| 99 | ("Theme: light", "Design: hell"), |
| 100 | ("Theme: dark", "Design: dunkel"), |
| 101 | ("Theme: auto", "Design: automatisch"), |
| 102 | ( |
| 103 | "Theme: light. Click to change.", |
| 104 | "Design: hell. Klicken zum Wechseln.", |
| 105 | ), |
| 106 | ( |
| 107 | "Theme: dark. Click to change.", |
| 108 | "Design: dunkel. Klicken zum Wechseln.", |
| 109 | ), |
| 110 | ( |
| 111 | "Theme: auto. Click to change.", |
| 112 | "Design: automatisch. Klicken zum Wechseln.", |
| 113 | ), |
| 114 | ("1 day", "1 Tag"), |
| 115 | ("1 hour", "1 Stunde"), |
| 116 | ("1 month", "1 Monat"), |
| 117 | ("1 point.", "1 Punkt."), |
| 118 | ("1 week", "1 Woche"), |
| 119 | ("8 hours", "8 Stunden"), |
| 120 | ("Add", "Hinzufügen"), |
| 121 | ("Add a device", "Gerät hinzufügen"), |
| 122 | ("Add a user", "Benutzer hinzufügen"), |
| 123 | ("Add passkey", "Passkey hinzufügen"), |
| 124 | ("Added", "Hinzugefügt"), |
| 125 | ("Added \"{}\".", "\"{}\" hinzugefügt."), |
| 126 | ("Admin", "Admin"), |
| 127 | ( |
| 128 | "All devices, also ones I add later", |
| 129 | "Alle Geräte, auch später hinzugefügte", |
| 130 | ), |
| 131 | ( |
| 132 | "Anyone with the link sees your location, without an account.", |
| 133 | "Jeder mit dem Link sieht deinen Standort, ohne Konto.", |
| 134 | ), |
| 135 | ("Connect", "Verbinden"), |
| 136 | ("Connect the app", "App verbinden"), |
| 137 | ("Change", "Ändern"), |
| 138 | ("Copy", "Kopieren"), |
| 139 | ("Copy this link:", "Diesen Link kopieren:"), |
| 140 | ("Create a guest link", "Gastlink erstellen"), |
| 141 | ("Create admin account", "Admin-Konto erstellen"), |
| 142 | ("Create link", "Link erstellen"), |
| 143 | ("Create token", "Token erstellen"), |
| 144 | ("Created", "Erstellt"), |
| 145 | ("Created {}.", "{} erstellt."), |
| 146 | ("Current", "Aktuell"), |
| 147 | ("Cycling", "Fahrrad"), |
| 148 | ("Day", "Tag"), |
| 149 | ("Delete", "Löschen"), |
| 150 | ( |
| 151 | "Delete this link? Everyone who has it loses access.", |
| 152 | "Diesen Link löschen? Alle, die ihn haben, verlieren den Zugriff.", |
| 153 | ), |
| 154 | ( |
| 155 | "Delete {} and all their data?", |
| 156 | "{} und alle zugehörigen Daten löschen?", |
| 157 | ), |
| 158 | ( |
| 159 | "Delete the location history of the web app? It starts again when a browser sends a position.", |
| 160 | "Den Standortverlauf der Web-App löschen? Er beginnt neu, sobald ein Browser einen Standort sendet.", |
| 161 | ), |
| 162 | ("Device", "Gerät"), |
| 163 | ("Device name", "Gerätename"), |
| 164 | ("Devices", "Geräte"), |
| 165 | ( |
| 166 | "Done. Return to the app to finish.", |
| 167 | "Fertig. Kehre zur App zurück, um abzuschließen.", |
| 168 | ), |
| 169 | ("Enter a number of days.", "Gib eine Anzahl Tage ein."), |
| 170 | ("Expires", "Läuft ab"), |
| 171 | ("For", "Dauer"), |
| 172 | ("From", "Von"), |
| 173 | ("Guest links", "Gastlinks"), |
| 174 | ("History", "Verlauf"), |
| 175 | ("Humanitarian", "Humanitär"), |
| 176 | ("I share my location with", "Ich teile meinen Standort mit"), |
| 177 | ( |
| 178 | "Keep my points for (days)", |
| 179 | "Meine Punkte aufbewahren (Tage)", |
| 180 | ), |
| 181 | ("Last 24 hours", "Letzte 24 Stunden"), |
| 182 | ("Last sent at {}.", "Zuletzt gesendet um {}."), |
| 183 | ("Last upload", "Letzter Upload"), |
| 184 | ("Last used", "Zuletzt verwendet"), |
| 185 | ( |
| 186 | "Leave the field empty to use the server setting.", |
| 187 | "Lass das Feld leer, um die Servereinstellung zu verwenden.", |
| 188 | ), |
| 189 | ("Link copied.", "Link kopiert."), |
| 190 | ("Link created and copied.", "Link erstellt und kopiert."), |
| 191 | ("Link created.", "Link erstellt."), |
| 192 | ("Location history", "Standortverlauf"), |
| 193 | ("Location of {}", "Standort von {}"), |
| 194 | ("Log in", "Anmelden"), |
| 195 | ("Log out", "Abmelden"), |
| 196 | ("Map", "Karte"), |
| 197 | ("Map layers", "Kartenebenen"), |
| 198 | ("Name", "Name"), |
| 199 | ( |
| 200 | "Needs a passkey, and two-factor sign-in turned off.", |
| 201 | "Braucht einen Passkey und ausgeschaltete Zwei-Faktor-Anmeldung.", |
| 202 | ), |
| 203 | ("New", "Neu"), |
| 204 | ( |
| 205 | "New password for {}. This also removes their passkeys, disconnects their devices, turns off two-factor sign-in and logs them out.", |
| 206 | "Neues Passwort für {}. Dabei werden auch die Passkeys entfernt, die Geräte getrennt, die Zwei-Faktor-Anmeldung ausgeschaltet und alle Sitzungen abgemeldet.", |
| 207 | ), |
| 208 | ("No devices yet.", "Noch keine Geräte."), |
| 209 | ("No links yet.", "Noch keine Links."), |
| 210 | ("No passkeys yet.", "Noch keine Passkeys."), |
| 211 | ("Nobody.", "Niemand."), |
| 212 | ( |
| 213 | "None, current position only", |
| 214 | "Keine, nur aktueller Standort", |
| 215 | ), |
| 216 | ("One day", "Ein Tag"), |
| 217 | ("Only these devices:", "Nur diese Geräte:"), |
| 218 | ("Open the app", "App öffnen"), |
| 219 | ("Passkeys", "Passkeys"), |
| 220 | ("Password", "Passwort"), |
| 221 | ("Password (optional)", "Passwort (optional)"), |
| 222 | ( |
| 223 | "Password and passkey (two-factor)", |
| 224 | "Passwort und Passkey (Zwei-Faktor)", |
| 225 | ), |
| 226 | ("Password or passkey", "Passwort oder Passkey"), |
| 227 | ("Password removed.", "Passwort entfernt."), |
| 228 | ("Password reset for {}.", "Passwort für {} zurückgesetzt."), |
| 229 | ( |
| 230 | "Password saved. Your other sessions are logged out.", |
| 231 | "Passwort gespeichert. Deine anderen Sitzungen sind abgemeldet.", |
| 232 | ), |
| 233 | ( |
| 234 | "Pick a start and an end.", |
| 235 | "Wähle einen Anfang und ein Ende.", |
| 236 | ), |
| 237 | ( |
| 238 | "Pick when the trail starts.", |
| 239 | "Wähle, wann die Spur beginnt.", |
| 240 | ), |
| 241 | ( |
| 242 | "Points older than this are deleted now. Continue?", |
| 243 | "Ältere Punkte werden jetzt gelöscht. Fortfahren?", |
| 244 | ), |
| 245 | ("Precision", "Genauigkeit"), |
| 246 | ("Remove", "Entfernen"), |
| 247 | ( |
| 248 | "Remove device \"{}\" and delete its location history? It can no longer upload positions.", |
| 249 | "Gerät \"{}\" entfernen und seinen Standortverlauf löschen? Es kann dann keine Standorte mehr senden.", |
| 250 | ), |
| 251 | ("Remove password", "Passwort entfernen"), |
| 252 | ( |
| 253 | "Remove the passkey \"{}\"?", |
| 254 | "Den Passkey \"{}\" entfernen?", |
| 255 | ), |
| 256 | ( |
| 257 | "Remove your password? You then sign in with a passkey only.", |
| 258 | "Dein Passwort entfernen? Du meldest dich dann nur noch mit einem Passkey an.", |
| 259 | ), |
| 260 | ("Repeat new", "Neues wiederholen"), |
| 261 | ("Repeat password", "Passwort wiederholen"), |
| 262 | ("Reset password", "Passwort zurücksetzen"), |
| 263 | ("Role", "Rolle"), |
| 264 | ("Satellite", "Satellit"), |
| 265 | ("Save", "Speichern"), |
| 266 | ("Saved.", "Gespeichert."), |
| 267 | ("Set password", "Passwort setzen"), |
| 268 | ("Seconds between sends", "Sekunden zwischen zwei Sendungen"), |
| 269 | ("Security", "Sicherheit"), |
| 270 | ( |
| 271 | "Select a person to show their trail.", |
| 272 | "Wähle eine Person, um ihre Spur zu zeigen.", |
| 273 | ), |
| 274 | ("Send every", "Senden alle"), |
| 275 | ("Send my location now", "Meinen Standort jetzt senden"), |
| 276 | ("Settings", "Einstellungen"), |
| 277 | ("Share", "Teilen"), |
| 278 | ("Share from this browser", "Aus diesem Browser teilen"), |
| 279 | ("Share my location", "Meinen Standort teilen"), |
| 280 | ("Shared location", "Geteilter Standort"), |
| 281 | ("Shared with me", "Mit mir geteilt"), |
| 282 | ("Sharing", "Teilen"), |
| 283 | ( |
| 284 | "Sharing again with the same person replaces the settings.", |
| 285 | "Erneutes Teilen mit derselben Person ersetzt die Einstellungen.", |
| 286 | ), |
| 287 | ("Show location", "Standort anzeigen"), |
| 288 | ("Shows", "Zeigt"), |
| 289 | ("Sign in with a passkey", "Mit Passkey anmelden"), |
| 290 | ("Sign-in", "Anmeldung"), |
| 291 | ("Sign-in failed.", "Anmeldung fehlgeschlagen."), |
| 292 | ("Since", "Seit"), |
| 293 | ("Stop", "Beenden"), |
| 294 | ( |
| 295 | "Stop seeing the location of {}?", |
| 296 | "Den Standort von {} nicht mehr sehen?", |
| 297 | ), |
| 298 | ( |
| 299 | "Stop sharing your location with {}?", |
| 300 | "Deinen Standort nicht mehr mit {} teilen?", |
| 301 | ), |
| 302 | ( |
| 303 | "The Android app needs no token. Enter this address in the app and sign in:", |
| 304 | "Die Android-App braucht kein Token. Gib diese Adresse in der App ein und melde dich an:", |
| 305 | ), |
| 306 | ( |
| 307 | "The new passwords do not match.", |
| 308 | "Die neuen Passwörter stimmen nicht überein.", |
| 309 | ), |
| 310 | ( |
| 311 | "The opentracker app asks to upload positions to your account as a new device.", |
| 312 | "Die opentracker-App möchte als neues Gerät Standorte an dein Konto senden.", |
| 313 | ), |
| 314 | ( |
| 315 | "The passkey was not used ({}).", |
| 316 | "Der Passkey wurde nicht verwendet ({}).", |
| 317 | ), |
| 318 | ( |
| 319 | "The passkey was not used.", |
| 320 | "Der Passkey wurde nicht verwendet.", |
| 321 | ), |
| 322 | ( |
| 323 | "The passwords do not match.", |
| 324 | "Die Passwörter stimmen nicht überein.", |
| 325 | ), |
| 326 | ( |
| 327 | "The range can be at most 31 days.", |
| 328 | "Der Zeitraum darf höchstens 31 Tage lang sein.", |
| 329 | ), |
| 330 | ( |
| 331 | "The server keeps points for {} days. You can choose a shorter time.", |
| 332 | "Der Server bewahrt Punkte {} Tage auf. Du kannst eine kürzere Zeit wählen.", |
| 333 | ), |
| 334 | ( |
| 335 | "The server keeps points forever. You can choose a limit.", |
| 336 | "Der Server bewahrt Punkte unbegrenzt auf. Du kannst eine Grenze wählen.", |
| 337 | ), |
| 338 | ( |
| 339 | "The start must be before the end.", |
| 340 | "Der Anfang muss vor dem Ende liegen.", |
| 341 | ), |
| 342 | ( |
| 343 | "The token is shown only once. Set up the device with:", |
| 344 | "Das Token wird nur einmal angezeigt. Richte das Gerät so ein:", |
| 345 | ), |
| 346 | ( |
| 347 | "The web app, in any browser", |
| 348 | "Die Web-App, in jedem Browser", |
| 349 | ), |
| 350 | ( |
| 351 | "This account also needs its password.", |
| 352 | "Dieses Konto braucht auch sein Passwort.", |
| 353 | ), |
| 354 | ( |
| 355 | "This browser cannot share its location.", |
| 356 | "Dieser Browser kann seinen Standort nicht teilen.", |
| 357 | ), |
| 358 | ( |
| 359 | "This device was removed. Connect the app again.", |
| 360 | "Dieses Gerät wurde entfernt. Verbinde die App erneut.", |
| 361 | ), |
| 362 | ( |
| 363 | "This link is incomplete. Start again in the app.", |
| 364 | "Dieser Link ist unvollständig. Beginne erneut in der App.", |
| 365 | ), |
| 366 | ( |
| 367 | "This link is no longer valid.", |
| 368 | "Dieser Link ist nicht mehr gültig.", |
| 369 | ), |
| 370 | ( |
| 371 | "This link is no longer valid. It may have expired or been deleted.", |
| 372 | "Dieser Link ist nicht mehr gültig. Er ist vielleicht abgelaufen oder wurde gelöscht.", |
| 373 | ), |
| 374 | ("This link works until {}.", "Dieser Link gilt bis {}."), |
| 375 | ( |
| 376 | "This page is for the opentracker app.", |
| 377 | "Diese Seite ist für die opentracker-App.", |
| 378 | ), |
| 379 | ( |
| 380 | "This shared location is protected with a password.", |
| 381 | "Dieser geteilte Standort ist mit einem Passwort geschützt.", |
| 382 | ), |
| 383 | ( |
| 384 | "The location is not available.", |
| 385 | "Der Standort ist nicht verfügbar.", |
| 386 | ), |
| 387 | ( |
| 388 | "User denied Geolocation", |
| 389 | "Der Standortzugriff wurde abgelehnt.", |
| 390 | ), |
| 391 | ("Time range", "Zeitraum"), |
| 392 | ("To", "Bis"), |
| 393 | ( |
| 394 | "Too many failed attempts. Try again in 15 minutes.", |
| 395 | "Zu viele Fehlversuche. Versuche es in 15 Minuten erneut.", |
| 396 | ), |
| 397 | ("Topographic", "Topografisch"), |
| 398 | ("Trail", "Spur"), |
| 399 | ( |
| 400 | "Two-factor sign-in needs a password and at least one passkey.", |
| 401 | "Die Zwei-Faktor-Anmeldung braucht ein Passwort und mindestens einen Passkey.", |
| 402 | ), |
| 403 | ("User", "Benutzer"), |
| 404 | ("Username", "Benutzername"), |
| 405 | ("Users", "Benutzer"), |
| 406 | ( |
| 407 | "Welcome. Create the admin account to finish the setup.", |
| 408 | "Willkommen. Erstelle das Admin-Konto, um die Einrichtung abzuschließen.", |
| 409 | ), |
| 410 | ("With", "Mit"), |
| 411 | ("Wrong password.", "Falsches Passwort."), |
| 412 | ( |
| 413 | "Wrong username or password.", |
| 414 | "Falscher Benutzername oder falsches Passwort.", |
| 415 | ), |
| 416 | ("Zoom in", "Vergrößern"), |
| 417 | ("Zoom out", "Verkleinern"), |
| 418 | ("about 1 km", "etwa 1 km"), |
| 419 | ("about 10 km", "etwa 10 km"), |
| 420 | ("about 100 m", "etwa 100 m"), |
| 421 | ("admin", "Admin"), |
| 422 | ("all devices", "alle Geräte"), |
| 423 | ("current position only", "nur aktueller Standort"), |
| 424 | ("exact", "genau"), |
| 425 | ("for Anna", "für Anna"), |
| 426 | ("forever", "unbegrenzt"), |
| 427 | ("from now on", "ab jetzt"), |
| 428 | ("full history", "gesamter Verlauf"), |
| 429 | ("full trail", "ganze Spur"), |
| 430 | ("laptop, phone, key", "Laptop, Handy, Schlüssel"), |
| 431 | ("never", "nie"), |
| 432 | ("no position yet", "noch kein Standort"), |
| 433 | ( |
| 434 | "none, current position only", |
| 435 | "keine, nur aktueller Standort", |
| 436 | ), |
| 437 | ("password", "Passwort"), |
| 438 | ("phone", "Handy"), |
| 439 | ("seconds", "Sekunden"), |
| 440 | ("since a time", "seit einem Zeitpunkt"), |
| 441 | ("some devices", "einige Geräte"), |
| 442 | ("unnamed", "ohne Namen"), |
| 443 | ("until I stop it", "bis ich es beende"), |
| 444 | ("user", "Benutzer"), |
| 445 | ("username", "Benutzername"), |
| 446 | ("{} is now an admin.", "{} ist jetzt Admin."), |
| 447 | ("{} is now a user.", "{} ist jetzt Benutzer."), |
| 448 | ("Unauthorized", "Nicht angemeldet"), |
| 449 | ("Forbidden", "Nicht erlaubt"), |
| 450 | ("Not Found", "Nicht gefunden"), |
| 451 | ( |
| 452 | "Too Many Requests", |
| 453 | "Zu viele Anfragen. Versuche es später erneut.", |
| 454 | ), |
| 455 | ("Internal Server Error", "Interner Serverfehler"), |
| 456 | ( |
| 457 | "add a passkey before removing your password", |
| 458 | "füge einen Passkey hinzu, bevor du dein Passwort entfernst", |
| 459 | ), |
| 460 | ( |
| 461 | "add a passkey before turning on two-factor sign-in", |
| 462 | "füge einen Passkey hinzu, bevor du die Zwei-Faktor-Anmeldung einschaltest", |
| 463 | ), |
| 464 | ( |
| 465 | "challenge must be a SHA-256 in hex", |
| 466 | "die Challenge muss ein SHA-256 in Hex sein", |
| 467 | ), |
| 468 | ( |
| 469 | "device name must have 1 to 100 characters", |
| 470 | "der Gerätename muss 1 bis 100 Zeichen haben", |
| 471 | ), |
| 472 | ( |
| 473 | "expiry must be in the future", |
| 474 | "der Ablauf muss in der Zukunft liegen", |
| 475 | ), |
| 476 | ("no Host header", "kein Host-Header"), |
| 477 | ("no such device", "dieses Gerät gibt es nicht"), |
| 478 | ("no such user", "diesen Benutzer gibt es nicht"), |
| 479 | ( |
| 480 | "password needs at least 8 characters", |
| 481 | "das Passwort braucht mindestens 8 Zeichen", |
| 482 | ), |
| 483 | ( |
| 484 | "passkeys need a domain name, not an IP address", |
| 485 | "Passkeys brauchen einen Domainnamen, keine IP-Adresse", |
| 486 | ), |
| 487 | ( |
| 488 | "range must be 0 to 31 days", |
| 489 | "der Zeitraum muss 0 bis 31 Tage lang sein", |
| 490 | ), |
| 491 | ( |
| 492 | "retention must be at least 1 days", |
| 493 | "die Aufbewahrung muss mindestens 1 Tag betragen", |
| 494 | ), |
| 495 | ("select at least one device", "wähle mindestens ein Gerät"), |
| 496 | ( |
| 497 | "set a password before removing your last passkey", |
| 498 | "setze ein Passwort, bevor du deinen letzten Passkey entfernst", |
| 499 | ), |
| 500 | ( |
| 501 | "set a password before turning on two-factor sign-in", |
| 502 | "setze ein Passwort, bevor du die Zwei-Faktor-Anmeldung einschaltest", |
| 503 | ), |
| 504 | ( |
| 505 | "that passkey could not be used", |
| 506 | "dieser Passkey konnte nicht verwendet werden", |
| 507 | ), |
| 508 | ( |
| 509 | "that passkey is already registered", |
| 510 | "dieser Passkey ist schon registriert", |
| 511 | ), |
| 512 | ( |
| 513 | "that took too long, please try again", |
| 514 | "das hat zu lange gedauert, bitte versuche es erneut", |
| 515 | ), |
| 516 | ("that username is taken", "dieser Benutzername ist vergeben"), |
| 517 | ( |
| 518 | "the name can have at most 100 characters", |
| 519 | "der Name darf höchstens 100 Zeichen haben", |
| 520 | ), |
| 521 | ( |
| 522 | "the server is already set up", |
| 523 | "der Server ist schon eingerichtet", |
| 524 | ), |
| 525 | ("this link has no password", "dieser Link hat kein Passwort"), |
| 526 | ( |
| 527 | "turn off two-factor sign-in before removing your last passkey", |
| 528 | "schalte die Zwei-Faktor-Anmeldung aus, bevor du deinen letzten Passkey entfernst", |
| 529 | ), |
| 530 | ( |
| 531 | "turn off two-factor sign-in before removing your password", |
| 532 | "schalte die Zwei-Faktor-Anmeldung aus, bevor du dein Passwort entfernst", |
| 533 | ), |
| 534 | ("unreadable credential", "unlesbare Anmeldedaten"), |
| 535 | ( |
| 536 | "username must have 1 to 64 characters", |
| 537 | "der Benutzername muss 1 bis 64 Zeichen haben", |
| 538 | ), |
| 539 | ("wrong current password", "falsches aktuelles Passwort"), |
| 540 | ( |
| 541 | "you cannot change your own role", |
| 542 | "du kannst deine eigene Rolle nicht ändern", |
| 543 | ), |
| 544 | ( |
| 545 | "you cannot delete your own account", |
| 546 | "du kannst dein eigenes Konto nicht löschen", |
| 547 | ), |
| 548 | ( |
| 549 | "you cannot share with yourself", |
| 550 | "du kannst nicht mit dir selbst teilen", |
| 551 | ), |
| 552 | ("about {} km", "etwa {} km"), |
| 553 | ("about {} m", "etwa {} m"), |
| 554 | ("approximate, {} km", "ungefähr, {} km"), |
| 555 | ("approximate, {} m", "ungefähr, {} m"), |
| 556 | ("battery {}%", "Akku {}%"), |
| 557 | ("network error: {}", "Netzwerkfehler: {}"), |
| 558 | ("trail since {}", "Spur seit {}"), |
| 559 | ( |
| 560 | "retention must be 1 to {} days", |
| 561 | "die Aufbewahrung muss 1 bis {} Tage betragen", |
| 562 | ), |
| 563 | ( |
| 564 | "at most {} points per request", |
| 565 | "höchstens {} Punkte pro Anfrage", |
| 566 | ), |
| 567 | ( |
| 568 | "precision must be at most {} metres", |
| 569 | "die Genauigkeit darf höchstens {} Meter betragen", |
| 570 | ), |
| 571 | ( |
| 572 | "you can have at most {} passkeys", |
| 573 | "du kannst höchstens {} Passkeys haben", |
| 574 | ), |
| 575 | ( |
| 576 | "passkeys are set up for {}, but this page is {}. Set --public-url to the address you use.", |
| 577 | "Passkeys sind für {} eingerichtet, diese Seite ist aber {}. Setze --public-url auf die Adresse, die du verwendest.", |
| 578 | ), |
| 579 | ("{} (you)", "{} (du)"), |
| 580 | ("{} h ago", "vor {} h"), |
| 581 | ("{} min ago", "vor {} min"), |
| 582 | ("{} points.", "{} Punkte."), |
| 583 | ("{} s ago", "vor {} s"), |
| 584 | ( |
| 585 | "{} shares only their current position.", |
| 586 | "{} teilt nur den aktuellen Standort.", |
| 587 | ), |
| 588 | ( |
| 589 | "{} shares the trail since {}.", |
| 590 | "{} teilt die Spur seit {}.", |
| 591 | ), |
| 592 | ]; |
| 593 |