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