diff --git a/apps/web/e2e/language.spec.ts b/apps/web/e2e/language.spec.ts new file mode 100644 index 0000000..1f7d640 --- /dev/null +++ b/apps/web/e2e/language.spec.ts @@ -0,0 +1,69 @@ +import AxeBuilder from "@axe-core/playwright"; +import { type Page, expect, test } from "@playwright/test"; + +// The language of the account pages: ?lang= (the launcher passes its own), then the cookie, then +// Accept-Language, English otherwise. The mock plays the cloud. + +const shots = process.env.SHOTS; +async function shoot(page: Page, name: string) { + if (shots) await page.screenshot({ path: `${shots}/${name}-${page.viewportSize()!.width}.png`, fullPage: true }); +} + +async function noViolations(page: Page) { + // The flow loads its session first; the audit is of the page that stands afterwards. + await expect(page.getByRole("heading", { level: 1 }).first()).toBeVisible(); + const { violations } = await new AxeBuilder({ page }).disableRules(["color-contrast"]).analyze(); + expect(violations.map((v) => `${v.id}: ${v.help}`)).toEqual([]); +} + +test("an explicit ?lang= wins, is remembered, and sets the document language", async ({ page }) => { + await page.goto("/sign-in?mock=signedout&lang=de"); + await expect(page.locator("html")).toHaveAttribute("lang", "de"); + await expect(page).toHaveTitle(/Anmelden/); + await shoot(page, "sign-in-de"); + await noViolations(page); + // The next page, opened without the parameter, keeps German through the cookie. + await page.goto("/create?mock=signedout"); + await expect(page.locator("html")).toHaveAttribute("lang", "de"); + await expect(page).toHaveTitle(/Erstelle dein Konto/); +}); + +test("Turkish comes out whole, with its own letters", async ({ page }) => { + await page.goto("/sign-in?mock=signedout&lang=tr"); + await expect(page.locator("html")).toHaveAttribute("lang", "tr"); + await expect(page).toHaveTitle(/Giriş yap/); + await shoot(page, "sign-in-tr"); + await noViolations(page); + const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth); + expect(overflow).toBe(0); +}); + +test("without a parameter or cookie the browser's language decides, and English is the fallback", async ({ browser }) => { + const french = await browser.newContext({ locale: "fr-FR", extraHTTPHeaders: { "accept-language": "fr-FR,fr;q=0.9,en;q=0.5" } }); + const page = await french.newPage(); + await page.goto("/sign-in?mock=signedout"); + await expect(page.locator("html")).toHaveAttribute("lang", "fr"); + await french.close(); + + const japanese = await browser.newContext({ extraHTTPHeaders: { "accept-language": "ja" } }); + const other = await japanese.newPage(); + await other.goto("/sign-in?mock=signedout"); + await expect(other.locator("html")).toHaveAttribute("lang", "en"); + await expect(other).toHaveTitle(/Sign in/); + await japanese.close(); +}); + +test("an unknown ?lang= is ignored", async ({ page }) => { + await page.goto("/sign-in?mock=signedout&lang=xx"); + await expect(page.locator("html")).toHaveAttribute("lang", "en"); +}); + +test("the account area speaks the language too, and the long words fit", async ({ page }) => { + await page.goto("/account?lang=de"); + await expect(page.locator("html")).toHaveAttribute("lang", "de"); + await expect(page.getByRole("heading", { level: 1 }).first()).toBeVisible(); + await shoot(page, "account-de"); + await noViolations(page); + const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth); + expect(overflow).toBe(0); +}); diff --git a/apps/web/messages/de/account.json b/apps/web/messages/de/account.json new file mode 100644 index 0000000..c358d8e --- /dev/null +++ b/apps/web/messages/de/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "nie", + "on": "An", + "off": "Aus", + "cancel": "Abbrechen", + "working": "Wird ausgeführt…", + "typeToConfirm": "Gib zum Bestätigen {word} ein", + "audience": { + "everyone": "Alle", + "friends": "Freunde", + "nobody": "Nur ich" + }, + "stepUp": { + "title": "Bestätige, dass du es bist", + "needed": "Für diese Änderung ist eine aktuelle Anmeldung nötig. Sie gilt 15 Minuten.", + "sent": "Wir haben einen 6-stelligen Code an {hint} gesendet.", + "passkey": "Passkey verwenden", + "sending": "Wird gesendet …", + "sendCode": "Code per E-Mail senden" + } + }, + "shell": { + "home": "AnyPlay, Startseite", + "signOut": "Abmelden", + "nav": "Konto", + "nav_overview": "Übersicht", + "nav_profile": "Profil", + "nav_friends": "Freunde", + "nav_trophies": "Trophäen", + "nav_security": "Sicherheit", + "nav_notifications": "Benachrichtigungen", + "nav_privacy": "Datenschutz", + "nav_connections": "Verknüpfungen", + "nav_data": "Deine Daten", + "unreachable": "AnyPlay ist nicht erreichbar. Prüfe deine Verbindung und versuche es erneut.", + "loading": "Lädt" + }, + "level": { + "top": "Höchstes Level erreicht", + "toNext": "Noch {count, plural, one {# Punkt} other {# Punkte}} bis Level {level}", + "level": "Level {level}", + "points": "{count, plural, one {# Punkt} other {# Punkte}}", + "progress": "Level-Fortschritt", + "progressTo": "Fortschritt zu Level {level}" + }, + "meta": { + "title": "Dein Konto", + "template": "%s · Dein Konto · AnyPlay" + }, + "state": { + "loading": "Wird geladen", + "tryAgain": "Erneut versuchen" + }, + "overview": { + "label": "Dein Konto", + "memberSince": "Mitglied seit {date}", + "appearingOffline": "Du erscheinst offline", + "deletion": "Dein Konto wird am {date} gelöscht. Mein Konto behalten", + "passkeys": "{count, plural, one {# Passkey} other {# Passkeys}}", + "noPasskey": "Noch kein Passkey", + "launchersBrowsers": "{launchers, plural, one {# Launcher} other {# Launcher}} · {browsers, plural, one {# Browser} other {# Browser}}", + "trophiesAudience": "Trophäen: {audience}", + "pictureAudience": "Profilbild: {audience}", + "audience": { + "everyone": "Alle", + "friends": "Freunde", + "nobody": "Nur du" + }, + "audienceInline": { + "everyone": "alle", + "friends": "Freunde", + "nobody": "nur du" + }, + "linked": "{count} verknüpft", + "nothingLinked": "Nichts verknüpft", + "connectionsDetail": "Discord, Steam", + "dataValue": "Exportieren oder löschen", + "dataDetail": "Alles, was wir über dich speichern", + "launcher": { + "title": "Hol dir den Launcher", + "description": "Melde dich mit diesem Konto im Launcher an, und deine Freunde, Partys und Trophäen sind da." + } + }, + "profile": { + "lead": "Das sehen deine Freunde, wenn sie dein Profil öffnen. Wer welchen Teil sieht, bestimmst du auf der Seite Privatsphäre.", + "avatar": { + "title": "Bild", + "description": "PNG oder JPEG, bis zu 1 MB. Es wird rund angezeigt, halte das Gesicht also in der Mitte.", + "uploading": "Wird hochgeladen…", + "change": "Bild ändern", + "upload": "Bild hochladen", + "remove": "Entfernen" + }, + "about": { + "title": "Über mich", + "description": "Ein bis zwei Zeilen über dich: was du spielst, wann du da bist.", + "placeholder": "Koop, wenn es geht, solo, wenn nicht.", + "saved": "Gespeichert", + "saving": "Wird gespeichert…", + "save": "Speichern" + }, + "favourites": { + "title": "Lieblingsspiele", + "description": "Bis zu {max}, per Titel-ID. Diese Seite zeigt die ID. Der Launcher zeigt den Namen jedes Spiels aus deiner eigenen Kopie.", + "empty": "Noch keine Lieblingsspiele. Füge unten eine Titel-ID hinzu.", + "listLabel": "Deine Lieblingsspiele", + "remove": "{id} entfernen", + "fieldLabel": "Titel-ID", + "full": "Du hast {max} Spiele. Entferne eins, um ein weiteres hinzuzufügen.", + "add": "Spiel hinzufügen", + "saved": "Gespeichert", + "saving": "Wird gespeichert…", + "save": "Speichern", + "badId": "Verwende nur Buchstaben, Zahlen, Bindestriche und Unterstriche.", + "duplicate": "{id} steht schon auf der Liste." + } + }, + "security": { + "lead": "Wie du dich anmeldest, und jeder Launcher und Browser, der bei deinem Konto angemeldet ist.", + "email": { + "title": "E-Mail", + "description": "Dorthin gehen deine Anmeldecodes. Zum Ändern brauchst du einen Code, der an die neue Adresse geschickt wird.", + "change": "Ändern", + "invalid": "Gib die neue Adresse ein, zum Beispiel name@beispiel.de.", + "done": "Deine Anmeldeadresse ist jetzt {email}.", + "newAddress": "Neue Adresse", + "sending": "Wird gesendet…", + "send": "Code senden", + "cancel": "Abbrechen", + "sentTo": "Wir haben einen 6-stelligen Code an {email} gesendet." + }, + "passkeys": { + "title": "Passkeys", + "description": "Melde dich mit Fingerabdruck, Gesicht oder Geräte-PIN statt mit einem Code an. Passkeys lassen sich nicht phishen.", + "add": "Passkey hinzufügen", + "waiting": "Warte auf dein Gerät…", + "detail": "Hinzugefügt {added} · Zuletzt verwendet: {used}", + "remove": "Entfernen", + "empty": "Noch kein Passkey. Füge einen hinzu, und die Anmeldung geht mit einer Berührung statt mit einem E-Mail-Code.", + "full": "Du hast die meisten Passkeys, die ein Konto behalten kann ({max}). Entferne einen, um einen weiteren hinzuzufügen.", + "confirmTitle": "Diesen Passkey entfernen?", + "confirmDescription": "Dieses Gerät kann dich damit nicht mehr anmelden. Mit einem Code per E-Mail kannst du dich immer anmelden.", + "confirmLabel": "Passkey entfernen", + "defaultName": "Passkey auf {system}", + "thisDevice": "diesem Gerät" + }, + "devices": { + "title": "Launcher", + "description": "Jeder PC, auf dem der Launcher bei deinem Konto angemeldet ist.", + "detail": "{platform} · Zuletzt verwendet: {used}", + "signOut": "Abmelden", + "empty": "Noch kein Launcher angemeldet.", + "confirmTitle": "Diesen Launcher abmelden?", + "confirmDescription": "Er geht offline, bis sich jemand auf diesem PC wieder anmeldet.", + "confirmLabel": "Launcher abmelden" + }, + "browsers": { + "title": "Browser", + "description": "Jeder Browser, der auf dieser Kontoseite angemeldet ist. Wir speichern über sie nicht mehr als den Zeitpunkt der Nutzung.", + "thisBrowser": "Dieser Browser", + "here": "Du bist hier", + "other": "Anderer Browser", + "detail": "Angemeldet {created} · Zuletzt verwendet: {used}", + "signOut": "Abmelden", + "confirmThisTitle": "Von diesem Browser abmelden?", + "confirmOtherTitle": "Diesen anderen Browser abmelden?", + "confirmThisDescription": "Du kommst zurück zum Start und meldest dich beim nächsten Mal neu an.", + "confirmOtherDescription": "Er muss sich neu anmelden, um dein Konto zu öffnen.", + "confirmLabel": "Abmelden" + }, + "everywhere": { + "title": "Überall abmelden", + "description": "Jeder Launcher und jeder Browser, auch dieser. Nutze das, wenn ein Gerät verloren ging oder sich jemand anderes angemeldet haben könnte.", + "button": "Überall abmelden", + "confirmTitle": "Überall abmelden?", + "confirmDescription": "Jeder Launcher und Browser wird sofort abgemeldet, auch dieser.", + "confirmLabel": "Überall abmelden" + } + }, + "privacy": { + "lead": "Wer dein Profil und deine Aktivität sieht. Dein Name ist immer sichtbar, damit Freunde dich finden.", + "profile": { + "title": "Dein Profil", + "description": "Änderungen gelten sofort, auch im Launcher." + }, + "parts": { + "trophies": { + "title": "Trophäen", + "detail": "Deine Trophäenzahlen, Punkte und dein Level" + }, + "playtime": { + "title": "Spielzeit", + "detail": "Wie lange du gespielt hast, pro Spiel" + }, + "about": { + "title": "Über mich", + "detail": "Die Zeile in deinem Profil" + }, + "favourites": { + "title": "Lieblingsspiele", + "detail": "Bis zu drei Spiele, die du anheftest" + }, + "avatar": { + "title": "Bild", + "detail": "Dein Profilbild" + } + }, + "whoSees": "Wer sieht: {part}", + "activity": { + "title": "Deine Aktivität", + "description": "Was du auf AnyPlay tust, so wie deine Freunde es in ihrem Feed sehen.", + "row": "Aktivität", + "detail": "Freunde sehen sie in ihrem Feed. Fremde sehen keinen Feed. Wähle „{nobody}“, um zu verbergen, was dort steht, und neue Einträge zu stoppen." + }, + "appearOffline": { + "title": "Online-Status", + "description": "Gilt für jeden Launcher und Browser, der bei deinem Konto angemeldet ist.", + "label": "Offline erscheinen", + "detail": "Freunde sehen dich als offline. Du kannst trotzdem spielen, chatten und beitreten." + }, + "publicProfile": { + "title": "Öffentliches Profil", + "description": "Aus, bis du es einschaltest. Gilt für jeden Launcher und Browser, der bei deinem Konto angemeldet ist.", + "label": "Öffentliches Profil anzeigen", + "detail": "Jeder mit dem Link sieht dein Level, deine Trophäen und gespielte Spiele. Ob du online bist, zeigt es nie.", + "page": "Deine Seite: {url}" + }, + "playedWith": { + "title": "Zuletzt gespielt mit", + "description": "Wenn du es ausschaltest, kommt niemand Neues dazu. Die Liste bleibt, bis du sie leerst.", + "label": "Mitspieler merken", + "detail": "Merkt sich die Leute, mit denen du eine Party oder Spielsitzung geteilt hast, 30 Tage lang, bis zu 50. Nur du siehst die Liste.", + "clear": "Liste leeren", + "cleared": "Die Liste ist leer.", + "confirmTitle": "Liste leeren?", + "confirmDescription": "Alle auf deiner Liste der Mitspieler werden entfernt. Die Listen anderer ändern sich nicht, und du kannst das nicht rückgängig machen." + } + }, + "connections": { + "lead": "Verknüpfe die Konten, mit denen du schon spielst, damit Freunde dich finden und sehen, wo du sonst noch bist.", + "result": { + "linked": "{name} ist mit deinem Konto verknüpft.", + "denied": "{name} wurde nicht verknüpft: Die Anfrage wurde dort abgelehnt.", + "expired": "Das Verknüpfen von {name} hat zu lange gedauert. Starte neu.", + "fresh": "Zum Verknüpfen von {name} brauchst du eine frische Anmeldung. Starte neu und bestätige, dass du es bist.", + "taken": "Dieses {name}-Konto ist schon mit einem anderen AnyPlay-Konto verknüpft.", + "exists": "Du hast schon ein {name}-Konto verknüpft. Hebe zuerst diese Verknüpfung auf.", + "failed": "Das Verknüpfen von {name} ist fehlgeschlagen. Versuche es in einer Minute erneut." + }, + "whoSees": "Wer dein {name}-Konto sieht", + "linkedAgo": "Verknüpft {ago}", + "unlink": "Verknüpfung aufheben", + "link": "{name} verknüpfen", + "opening": "Wird geöffnet…", + "soon": "Bald", + "notLinked": "Nicht verknüpft", + "arrivesSoon": "Verknüpfung kommt bald", + "confirmTitle": "Verknüpfung mit {name} aufheben?", + "confirmDescription": "Dein {name}-Konto erscheint nicht mehr in deinem Profil. Du kannst es jederzeit wieder verknüpfen.", + "confirmLabel": "Verknüpfung mit {name} aufheben" + }, + "data": { + "lead": "Alles, was AnyPlay über dich speichert, gehört dir: Hol dir eine Kopie oder lösche das Konto.", + "export": { + "title": "Deine Daten herunterladen", + "description": "Dein Konto, dein Profil, Freunde, Trophäen, Spielzeit und verknüpfte Konten als eine JSON-Datei.", + "preparing": "Wird vorbereitet…", + "button": "Herunterladen" + }, + "scheduled": { + "title": "Löschung geplant", + "description": "Dein Konto und alles darin wird am {date} gelöscht. Bis dahin kannst du es behalten.", + "keep": "Mein Konto behalten" + }, + "delete": { + "title": "Dein Konto löschen", + "description": "Dein Konto wird nach 30 Tagen gelöscht; bis dahin kannst du es dir anders überlegen. Danach ist es weg, samt Freunden, Trophäen und Spielzeit.", + "button": "Konto löschen" + }, + "confirm": { + "title": "Dein Konto löschen?", + "description": "Es wird in 30 Tagen gelöscht. Jeder Launcher und Browser wird jetzt abgemeldet." + } + }, + "compare": { + "title": "Trophäen vergleichen", + "description": "Deine Trophäen neben denen von {handle}.", + "descriptionShared": "Du und {handle}, in einem Spiel, das ihr beide habt.", + "signInDescription": "Sieh, wie deine Trophäen im Vergleich zu denen von {handle} aussehen.", + "signIn": "Anmelden, um Trophäen zu vergleichen", + "hidden": "Die Trophäen von {handle} sind nicht zum Vergleichen verfügbar.", + "loading": "Wird geladen", + "loadingComparison": "Der Vergleich wird geladen", + "empty": "Du und {handle} habt noch kein gemeinsames Spiel. Spiel ein Spiel, das {handle} gespielt hat, dann erscheint es hier.", + "game": "Spiel", + "gameNamed": "Spiel {title}", + "verdictNone": "Keiner von euch hat in diesem Spiel eine Trophäe erhalten.", + "verdictLevel": "In diesem Spiel steht ihr punktgleich.", + "verdictAhead": "Du liegst in diesem Spiel {count, plural, one {# Punkt} other {# Punkte}} vorn.", + "verdictBehind": "{handle} liegt in diesem Spiel {count, plural, one {# Punkt} other {# Punkte}} vorn.", + "caption": "Trophäen in {titleId}: du neben {handle}", + "stat": "Kategorie", + "you": "Du", + "ahead": "(vorn)", + "rowTrophies": "Trophäen", + "rowPoints": "Punkte in diesem Spiel", + "rowLevel": "Level insgesamt", + "grade": { + "platinum": "Platin", + "gold": "Gold", + "silver": "Silber", + "bronze": "Bronze" + }, + "both": "Von beiden erhalten", + "onlyYou": "Nur du", + "onlyThem": "Nur {handle}" + }, + "publicProfile": { + "home": "AnyPlay, Startseite", + "yourAccount": "Dein Konto", + "signIn": "Anmelden", + "loading": "Wird geladen", + "missingTitle": "Kein Spieler namens {handle}", + "missingBody": "Prüfe die Schreibweise des Namens oder bitte den Spieler, ihn noch einmal zu schicken.", + "goHome": "Zu AnyPlay", + "player": "Spieler", + "trophies": "Trophäen", + "trophiesEarned": "{count, number} erhalten.", + "noTrophies": "{handle} hat noch keine Trophäe erhalten.", + "favourites": "Lieblingsspiele", + "favouritesNote": "Angezeigt wird die Titel-ID.", + "game": "Spiel", + "connections": "Verknüpfungen", + "playtime": "Gespielte Spiele", + "playtimeNote": "{count, plural, one {# Spiel} other {# Spiele}}, insgesamt {total}.", + "nothing": "Von {handle} gibt es noch nichts weiter zu zeigen.", + "openProfile": "Profil öffnen", + "openProfileOn": "auf {brand} (öffnet in einem neuen Tab)", + "signInToAdd": "Anmelden, um {handle} hinzuzufügen", + "editProfile": "Profil bearbeiten", + "blocked": "Blockiert", + "friends": "Freunde", + "requestSent": "Anfrage gesendet", + "accepting": "Wird angenommen…", + "accept": "Anfrage annehmen", + "sending": "Wird gesendet…", + "addFriend": "Freund hinzufügen", + "addInAnyPlay": "Freund in AnyPlay hinzufügen", + "minutes": "{n} Min.", + "hours": "{n} Std." + }, + "friends": { + "title": "Freunde", + "lead": "Füge die Leute hinzu, mit denen du spielst, beantworte Anfragen und entscheide, wer draußen bleibt.", + "add": { + "title": "Freund hinzufügen", + "description": "Gib den Namen eines Spielers ein, um ihm eine Anfrage zu senden.", + "handle": "Name", + "hint": "Sein Name, mit oder ohne @.", + "send": "Anfrage senden", + "sending": "Wird gesendet…", + "enterHandle": "Gib den Namen des Spielers ein, den du hinzufügen möchtest.", + "nowFriends": "Du und @{handle} seid jetzt Freunde.", + "sent": "Anfrage an @{handle} gesendet. Ihr seid Freunde, sobald die Anfrage angenommen wird." + }, + "requests": { + "title": "Anfragen", + "description": "Spieler, die mit dir befreundet sein möchten, und die Anfragen, die du gesendet hast.", + "empty": "Keine offenen Anfragen. Wenn dich jemand hinzufügt, erscheint die Anfrage hier.", + "received": "Erhalten", + "sent": "Gesendet", + "receivedAgo": "Erhalten: {when}", + "sentAgo": "Gesendet: {when}", + "decline": "Ablehnen", + "accept": "Annehmen", + "withdraw": "Zurückziehen" + }, + "list": { + "title": "Deine Freunde", + "description": "Sieh, wer online ist und was gespielt wird.", + "empty": "Noch keine Freunde. Füge oben jemanden über den Namen hinzu; sobald die Anfrage angenommen wird, erscheint er hier.", + "online": "Online", + "offline": "Offline", + "playing": "Spielt {title}", + "block": "Blockieren", + "remove": "Entfernen" + }, + "blocked": { + "title": "Blockiert", + "description": "Blockierte Spieler werden aus deinen Freunden und Anfragen entfernt.", + "empty": "Du hast niemanden blockiert. Blockiere einen Spieler in deiner Freundesliste, wenn du es brauchst.", + "unblock": "Blockierung aufheben" + }, + "confirm": { + "blockTitle": "@{handle} blockieren?", + "removeTitle": "@{handle} als Freund entfernen?", + "blockText": "Ihr seid dann keine Freunde mehr, und offene Anfragen werden gelöscht. Du kannst die Blockierung jederzeit aufheben.", + "removeText": "Ihr seht nicht mehr, wann der andere online ist. Später kannst du eine neue Anfrage senden.", + "blockLabel": "Spieler blockieren", + "removeLabel": "Freund entfernen" + } + }, + "trophies": { + "title": "Trophäen", + "lead": "Was du in deinen Spielen verdient hast. AnyPlay speichert nur Anzahlen pro Spiel und Stufe, nie Trophäennamen oder Grafiken.", + "totals": { + "title": "Gesamt", + "description": "{total} verdient in {games, plural, one {# Spiel} other {# Spielen}}.", + "empty": "Noch keine Trophäen. Spiele ein Spiel über AnyPlay, dann werden deine Trophäen hier gezählt." + }, + "byGame": { + "title": "Nach Spiel", + "description": "Die Spiele sind nach Titel-ID sortiert, die neuesten zuerst.", + "detail": "{earned, plural, one {# Trophäe} other {# Trophäen}} · zuletzt verdient: {when}" + }, + "grades": { + "platinum": "Platin", + "gold": "Gold", + "silver": "Silber", + "bronze": "Bronze" + } + }, + "notifications": { + "title": "Benachrichtigungen", + "lead": "Erhalte auf diesem Gerät eine Benachrichtigung, wenn dir ein Freund eine Anfrage sendet, dich in eine Party einlädt oder dir schreibt.", + "device": { + "title": "Dieses Gerät", + "description": "Jeder Browser und jedes Handy wird einzeln eingerichtet. Schalte das dort ein, wo du benachrichtigt werden möchtest.", + "notEnabled": "Diese Cloud sendet keine Benachrichtigungen.", + "checking": "Dieses Gerät wird geprüft…", + "unsupported": "Dieser Browser kann keine Benachrichtigungen empfangen. Nutze ein aktuelles Chrome, Edge, Firefox oder Safari.", + "needsInstall": "Auf dem iPhone und iPad braucht AnyPlay für Benachrichtigungen einen Platz auf dem Home-Bildschirm. Tippe auf Teilen, wähle „Zum Home-Bildschirm“ und öffne AnyPlay von dort.", + "blocked": "Benachrichtigungen sind für diese Seite blockiert. Erlaube sie in den Website-Einstellungen deines Browsers und komm dann zurück.", + "switch": "Dieses Gerät benachrichtigen", + "others": "{count, plural, one {Ein weiteres Gerät erhält ebenfalls Benachrichtigungen. Schalte sie auf diesem Gerät aus.} other {# weitere Geräte erhalten ebenfalls Benachrichtigungen. Schalte sie auf diesen Geräten aus.}}" + }, + "kinds": { + "title": "Worüber benachrichtigt werden soll", + "description": "Nur für dieses Gerät.", + "friend_request": { + "label": "Freundschaftsanfragen", + "detail": "Jemand möchte mit dir befreundet sein." + }, + "party_invite": { + "label": "Party-Einladungen", + "detail": "Ein Freund lädt dich in eine Party ein." + }, + "message": { + "label": "Nachrichten", + "detail": "Ein Freund schreibt dir. Die Nachricht selbst steht nicht in der Benachrichtigung." + } + }, + "errors": { + "timeout": "Der Benachrichtigungsdienst deines Browsers hat nicht geantwortet. Prüfe deine Verbindung und versuche es erneut.", + "noKey": "Diese Cloud hat keinen Schlüssel zum Senden von Benachrichtigungen.", + "lostSubscription": "Dieser Browser hat sein Abonnement verloren. Schalte Benachrichtigungen aus und wieder ein.", + "notAllowed": "Dieser Browser hat Benachrichtigungen nicht erlaubt. In einem privaten Fenster funktionieren sie nicht; prüfe sonst die Website-Einstellungen." + } + } +} diff --git a/apps/web/messages/de/auth.json b/apps/web/messages/de/auth.json new file mode 100644 index 0000000..9fe0d1f --- /dev/null +++ b/apps/web/messages/de/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Anmelden", + "description": "Melde dich bei AnyPlay mit einem Passkey oder einem Code per E-Mail an." + }, + "create": { + "title": "Erstelle dein Konto", + "description": "Erstelle dein AnyPlay-Konto: eine E-Mail-Adresse und ein Name genügen." + }, + "link": { + "title": "Öffne dein Konto", + "description": "Öffne dein AnyPlay-Konto aus dem Launcher in diesem Browser." + }, + "join": { + "title": "Einladung", + "description": "Eine Einladung, gemeinsam auf AnyPlay zu spielen." + } + }, + "shell": { + "home": "AnyPlay, Startseite", + "back": "Zurück zum Anfang", + "promise": "Spiele deine Spiele gemeinsam.", + "toastOnline": "mira ist online", + "toastParty": "kit ist deiner Party beigetreten", + "toastTrophy": "Trophäe erhalten · Into the Ember", + "help": "Brauchst du Hilfe?", + "helpLink": "Frag auf Discord", + "legalNav": "Rechtliches", + "legalNotice": "Impressum", + "privacyNotice": "Datenschutzhinweis", + "termsOfUse": "Nutzungsbedingungen" + }, + "step": { + "loading": "Wird geladen", + "unreachable": "AnyPlay ist nicht erreichbar", + "tryAgain": "Erneut versuchen", + "newTab": "(öffnet in einem neuen Tab)", + "backToStart": "Zurück zum Anfang", + "signIn": "Anmelden", + "createAccount": "Konto erstellen", + "getApp": "AnyPlay holen", + "continue": "Weiter" + }, + "fields": { + "code": "Code" + }, + "flow": { + "authorize": "AnyPlay auf {device} wartet auf diese Anmeldung." + }, + "email": { + "create": { + "title": "Erstelle dein Konto", + "lead": "Beginne mit deiner E-Mail-Adresse. Wir senden dir einen Code zur Bestätigung." + }, + "signIn": { + "title": "Anmelden", + "lead": "Nutze deinen Passkey oder lass dir einen Code per E-Mail schicken." + }, + "passkey": "Mit Passkey anmelden", + "or": "oder", + "label": "E-Mail-Adresse", + "placeholder": "name@example.com", + "invalid": "Gib deine E-Mail-Adresse ein, zum Beispiel name@example.com.", + "sending": "Wird gesendet…", + "send": "Code senden", + "haveAccount": "Du hast schon ein Konto? Anmelden", + "newHere": "Neu bei AnyPlay? Konto erstellen" + }, + "code": { + "title": "Prüfe deine E-Mails", + "lead": "Wir haben einen 6-stelligen Code an {email} gesendet. Er gilt 10 Minuten.", + "incomplete": "Gib die 6 Ziffern aus der E-Mail ein.", + "checking": "Wird geprüft…", + "resent": "Ein neuer Code ist unterwegs.", + "noMail": "Keine E-Mail da?", + "resendIn": "Neuen Code in {seconds} s senden", + "resend": "Neuen Code senden" + }, + "details": { + "title": "Wähle einen Namen", + "lead": "Deine Adresse {email} ist bestätigt. Andere Spieler sehen deinen Namen.", + "handle": "Name", + "handleHint": "Verwende 3 bis 16 Buchstaben, Ziffern, Bindestriche oder Unterstriche. Beginne mit einem Buchstaben.", + "ageHint": "Das heißt: 16 oder älter.", + "age": "Ich bin alt genug, um den Bedingungen selbst zuzustimmen.", + "terms": "Ich akzeptiere die Nutzungsbedingungen (Version {version}) und habe den Datenschutzhinweis gelesen.", + "creating": "Wird erstellt…", + "submit": "Konto erstellen" + }, + "terms": { + "title": "Bedingungen ansehen", + "lead": "Die Bedingungen haben sich geändert. Lies die Nutzungsbedingungen und akzeptiere Version {version}, um fortzufahren.", + "accept": "Ich akzeptiere die Nutzungsbedingungen (Version {version}).", + "required": "Akzeptiere die Bedingungen, um fortzufahren.", + "submit": "Akzeptieren und fortfahren" + }, + "banned": { + "title": "Dieses Konto ist gesperrt", + "leadUntil": "Die Sperre endet am {date}.", + "leadForever": "Die Sperre endet nicht von selbst.", + "rule": "Regel", + "reason": "Grund", + "appeal": "Stellungnahme lesen und Einspruch einlegen" + }, + "handoff": { + "title": "Angemeldet", + "lead": "Du wirst zu AnyPlay zurückgebracht. Passiert nichts, nutze die Schaltfläche.", + "open": "AnyPlay öffnen", + "note": "Du kannst diesen Tab schließen, sobald AnyPlay dich als angemeldet zeigt." + }, + "done": { + "title": "Du bist angemeldet", + "inviteLead": "Du wirst zur Einladung zurückgebracht.", + "welcome": "Willkommen, {handle}. Deine Freunde, Partys und Trophäen warten im Launcher.", + "account": "Zu deinem Konto" + }, + "join": { + "expiredTitle": "Diese Einladung ist abgelaufen", + "expiredLead": "Der Link ist vielleicht abgelaufen, oder die Person spielt nicht mehr. Bitte um einen neuen.", + "title": "{handle} hat dich eingeladen", + "leadSignedIn": "Öffne AnyPlay auf deinem PC, um die Einladung zu sehen. Dort entscheidest du, ob du beitrittst.", + "leadSignedOut": "Melde dich zuerst an oder erstelle ein Konto. Danach kommst du hierher zurück, um die Einladung zu öffnen.", + "party": "Party", + "game": "Spiel", + "partyName": "Eine AnyPlay-Party", + "gameFallback": "Ein Spiel", + "joined": "Beigetreten", + "players": "{members} von {max, plural, one {# Spieler} other {# Spielern}}", + "open": "In AnyPlay öffnen", + "noApp": "Noch kein AnyPlay auf diesem PC? Hol es dir zuerst und öffne diese Seite dann erneut." + }, + "link": { + "expiredTitle": "Dieser Link ist abgelaufen", + "expiredLead": "Öffne deine Kontoseite noch einmal aus AnyPlay.", + "close": "Du kannst diesen Tab schließen.", + "title": "Öffne dein Konto", + "lead": "AnyPlay auf deinem PC möchte das Konto von {handle} in diesem Browser öffnen.", + "open": "Konto öffnen", + "opening": "Wird geöffnet…", + "notYou": "Das warst nicht du? Schließe den Tab." + } +} diff --git a/apps/web/messages/de/common.json b/apps/web/messages/de/common.json new file mode 100644 index 0000000..e689a5c --- /dev/null +++ b/apps/web/messages/de/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Etwas ist schiefgelaufen. Versuche es erneut.", + "network": "AnyPlay ist nicht erreichbar. Prüfe deine Verbindung und versuche es erneut.", + "internal": "Bei AnyPlay ist ein Problem aufgetreten. Versuche es in einer Minute erneut.", + "unauthorized": "Deine Sitzung ist abgelaufen. Melde dich erneut an.", + "forbidden": "Das kannst du mit diesem Konto nicht tun.", + "banned": "Dieses Konto ist gesperrt.", + "not_found": "Das gibt es nicht mehr.", + "limit": "Du hast das Limit dafür erreicht. Entferne zuerst eines.", + "too_large": "Das ist zu groß zum Senden.", + "code_wrong": "Dieser Code ist falsch. Prüfe ihn und versuche es erneut.", + "code_expired": "Dieser Code ist abgelaufen. Fordere einen neuen an.", + "handle_taken": "Dieser Name ist vergeben. Versuche einen anderen.", + "handle_invalid": "Verwende 3 bis 16 Buchstaben, Ziffern, Bindestriche oder Unterstriche. Beginne mit einem Buchstaben.", + "rate_limited": "Zu viele Versuche. Warte einen Moment und versuche es erneut.", + "rate_limited_wait": "Zu viele Versuche. Warte {seconds, plural, one {# Sekunde} other {# Sekunden}} und versuche es erneut.", + "passkey_unsupported": "Dieser Browser unterstützt keine Passkeys. Verwende stattdessen den E-Mail-Code." + } +} diff --git a/apps/web/messages/en/account.json b/apps/web/messages/en/account.json new file mode 100644 index 0000000..33ba1e5 --- /dev/null +++ b/apps/web/messages/en/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "never", + "on": "On", + "off": "Off", + "cancel": "Cancel", + "working": "Working…", + "typeToConfirm": "Type {word} to confirm", + "audience": { + "everyone": "Everyone", + "friends": "Friends", + "nobody": "Only me" + }, + "stepUp": { + "title": "Confirm it’s you", + "needed": "This change needs a recent sign-in. It lasts 15 minutes.", + "sent": "We sent a 6-digit code to {hint}.", + "passkey": "Use your passkey", + "sending": "Sending…", + "sendCode": "Send a code by e-mail" + } + }, + "shell": { + "home": "AnyPlay, home", + "signOut": "Sign out", + "nav": "Account", + "nav_overview": "Overview", + "nav_profile": "Profile", + "nav_friends": "Friends", + "nav_trophies": "Trophies", + "nav_security": "Security", + "nav_notifications": "Notifications", + "nav_privacy": "Privacy", + "nav_connections": "Connections", + "nav_data": "Your data", + "unreachable": "Can’t reach AnyPlay. Check your connection and try again.", + "loading": "Loading" + }, + "level": { + "top": "Top level reached", + "toNext": "{count, plural, one {# point} other {# points}} to level {level}", + "level": "Level {level}", + "points": "{count, plural, one {# point} other {# points}}", + "progress": "Level progress", + "progressTo": "Progress to level {level}" + }, + "meta": { + "title": "Your account", + "template": "%s · Your account · AnyPlay" + }, + "state": { + "loading": "Loading", + "tryAgain": "Try again" + }, + "overview": { + "label": "Your account", + "memberSince": "Member since {date}", + "appearingOffline": "Appearing offline", + "deletion": "Your account will be deleted on {date}. Keep my account", + "passkeys": "{count, plural, one {# passkey} other {# passkeys}}", + "noPasskey": "No passkey yet", + "launchersBrowsers": "{launchers, plural, one {# launcher} other {# launchers}} · {browsers, plural, one {# browser} other {# browsers}}", + "trophiesAudience": "Trophies: {audience}", + "pictureAudience": "Profile picture: {audience}", + "audience": { + "everyone": "Everyone", + "friends": "Friends", + "nobody": "Only you" + }, + "audienceInline": { + "everyone": "everyone", + "friends": "friends", + "nobody": "only you" + }, + "linked": "{count} linked", + "nothingLinked": "Nothing linked", + "connectionsDetail": "Discord, Steam", + "dataValue": "Export or delete", + "dataDetail": "Everything we keep about you", + "launcher": { + "title": "Get the launcher", + "description": "Sign in to the launcher with this account, and your friends, parties and trophies are there." + } + }, + "profile": { + "lead": "What your friends see when they open your profile. Who sees each part is up to you on the Privacy page.", + "avatar": { + "title": "Picture", + "description": "PNG or JPEG, up to 1 MB. It shows round, so keep the face in the middle.", + "uploading": "Uploading…", + "change": "Change picture", + "upload": "Upload picture", + "remove": "Remove" + }, + "about": { + "title": "About", + "description": "A line or two about you: what you play, when you're around.", + "placeholder": "Co-op when I can, solo when I can't.", + "saved": "Saved", + "saving": "Saving…", + "save": "Save" + }, + "favourites": { + "title": "Favourite games", + "description": "Up to {max}, by title ID. This site shows the ID. The launcher shows each game's name from your own copy.", + "empty": "No favourite games yet. Add a title ID below.", + "listLabel": "Your favourite games", + "remove": "Remove {id}", + "fieldLabel": "Title ID", + "full": "You have {max} games. Remove one to add another.", + "add": "Add game", + "saved": "Saved", + "saving": "Saving…", + "save": "Save", + "badId": "Use only letters, numbers, hyphens and underscores.", + "duplicate": "{id} is on the list already." + } + }, + "security": { + "lead": "How you sign in, and every launcher and browser that is signed in to your account.", + "email": { + "title": "E-mail", + "description": "Where your sign-in codes go. Changing it needs a code sent to the new address.", + "change": "Change", + "invalid": "Enter the new address, like name@example.com.", + "done": "Your sign-in address is now {email}.", + "newAddress": "New address", + "sending": "Sending…", + "send": "Send code", + "cancel": "Cancel", + "sentTo": "We sent a 6-digit code to {email}." + }, + "passkeys": { + "title": "Passkeys", + "description": "Sign in with your fingerprint, face or device PIN instead of a code. Nobody can phish a passkey.", + "add": "Add a passkey", + "waiting": "Waiting for your device…", + "detail": "Added {added} · Last used {used}", + "remove": "Remove", + "empty": "No passkey yet. Add one and signing in takes a touch instead of an e-mail code.", + "full": "You have the most passkeys an account can keep ({max}). Remove one to add another.", + "confirmTitle": "Remove this passkey?", + "confirmDescription": "That device can no longer sign you in with it. You can always sign in with a code by e-mail.", + "confirmLabel": "Remove passkey", + "defaultName": "Passkey on {system}", + "thisDevice": "this device" + }, + "devices": { + "title": "Launchers", + "description": "Every PC where the launcher is signed in to your account.", + "detail": "{platform} · Last used {used}", + "signOut": "Sign out", + "empty": "No launcher is signed in yet.", + "confirmTitle": "Sign out this launcher?", + "confirmDescription": "It goes offline until someone signs in on that PC again.", + "confirmLabel": "Sign out launcher" + }, + "browsers": { + "title": "Browsers", + "description": "Every browser signed in to this account page. We keep no more about them than when they were used.", + "thisBrowser": "This browser", + "here": "You are here", + "other": "Other browser", + "detail": "Signed in {created} · Last used {used}", + "signOut": "Sign out", + "confirmThisTitle": "Sign out of this browser?", + "confirmOtherTitle": "Sign out that browser?", + "confirmThisDescription": "You go back to the start and sign in again next time.", + "confirmOtherDescription": "It has to sign in again to open your account.", + "confirmLabel": "Sign out" + }, + "everywhere": { + "title": "Sign out everywhere", + "description": "Every launcher and every browser, this one too. Use it when a device was lost or someone else might have signed in.", + "button": "Sign out everywhere", + "confirmTitle": "Sign out everywhere?", + "confirmDescription": "Every launcher and browser is signed out at once, this one too.", + "confirmLabel": "Sign out everywhere" + } + }, + "privacy": { + "lead": "Who sees your profile and your activity. Your handle is always visible, so friends can find you.", + "profile": { + "title": "Your profile", + "description": "Changes count at once, in the launcher too." + }, + "parts": { + "trophies": { + "title": "Trophies", + "detail": "Your trophy counts, points and level" + }, + "playtime": { + "title": "Playtime", + "detail": "How long you played, per game" + }, + "about": { + "title": "About", + "detail": "The line on your profile" + }, + "favourites": { + "title": "Favourite games", + "detail": "Up to three games you pin" + }, + "avatar": { + "title": "Picture", + "detail": "Your profile picture" + } + }, + "whoSees": "Who sees: {part}", + "activity": { + "title": "Your activity", + "description": "What you do on AnyPlay, as your friends see it in their feed.", + "row": "Activity", + "detail": "Friends see it in their feed. Strangers see no feed. Choose “{nobody}” to hide what is there and stop new items." + }, + "appearOffline": { + "title": "Online status", + "description": "Applies to every launcher and browser signed in to your account.", + "label": "Appear offline", + "detail": "Friends see you as offline. You can still play, chat and join." + }, + "publicProfile": { + "title": "Public profile", + "description": "Off until you turn it on. Applies to every launcher and browser signed in to your account.", + "label": "Show a public profile", + "detail": "Anyone with the link sees your level, trophies and games played. It never shows whether you are online.", + "page": "Your page: {url}" + }, + "playedWith": { + "title": "People you played with", + "description": "Turning it off adds no one new. The list stays until you clear it.", + "label": "Remember people I play with", + "detail": "Keeps the people you shared a party or game session with for 30 days, up to 50. Only you see the list.", + "clear": "Clear the list", + "cleared": "The list is empty.", + "confirmTitle": "Clear the list?", + "confirmDescription": "Everyone on your list of people you played with is removed. It does not change anyone else’s list, and you can’t undo it." + } + }, + "connections": { + "lead": "Link the accounts you already play with, so friends can find you and see where else you are.", + "result": { + "linked": "{name} is linked to your account.", + "denied": "{name} was not linked: the request was declined there.", + "expired": "Linking {name} took too long. Start again.", + "fresh": "Linking {name} needs a recent sign-in. Start again and confirm it's you.", + "taken": "That {name} account is already linked to another AnyPlay account.", + "exists": "You already have a {name} account linked. Unlink it first.", + "failed": "Linking {name} failed. Try again in a minute." + }, + "whoSees": "Who sees your {name} account", + "linkedAgo": "Linked {ago}", + "unlink": "Unlink", + "link": "Link {name}", + "opening": "Opening…", + "soon": "Soon", + "notLinked": "Not linked", + "arrivesSoon": "Linking arrives soon", + "confirmTitle": "Unlink {name}?", + "confirmDescription": "Your {name} account no longer shows on your profile. You can link it again any time.", + "confirmLabel": "Unlink {name}" + }, + "data": { + "lead": "Everything AnyPlay keeps about you is yours: take a copy, or delete the account.", + "export": { + "title": "Download your data", + "description": "Your account, profile, friends, trophies, playtime and linked accounts, as one JSON file.", + "preparing": "Preparing…", + "button": "Download" + }, + "scheduled": { + "title": "Deletion scheduled", + "description": "Your account and everything in it will be deleted on {date}. Until then you can keep it.", + "keep": "Keep my account" + }, + "delete": { + "title": "Delete your account", + "description": "Your account is deleted after 30 days; until then you can change your mind. After that it is gone, with your friends, trophies and playtime.", + "button": "Delete account" + }, + "confirm": { + "title": "Delete your account?", + "description": "It is deleted in 30 days. Every launcher and browser is signed out now." + } + }, + "compare": { + "title": "Compare trophies", + "description": "Your trophies next to {handle}’s.", + "descriptionShared": "You and {handle}, in a game you both have.", + "signInDescription": "See how your trophies stack up against {handle}.", + "signIn": "Sign in to compare trophies", + "hidden": "{handle}’s trophies aren’t available to compare.", + "loading": "Loading", + "loadingComparison": "Loading the comparison", + "empty": "You and {handle} have no game in common yet. Play a game {handle} has played and it shows up here.", + "game": "Game", + "gameNamed": "Game {title}", + "verdictNone": "Neither of you has earned a trophy in this game.", + "verdictLevel": "You are level on points in this game.", + "verdictAhead": "You are ahead by {count, plural, one {# point} other {# points}} in this game.", + "verdictBehind": "{handle} is ahead by {count, plural, one {# point} other {# points}} in this game.", + "caption": "Trophies in {titleId}, you next to {handle}", + "stat": "Stat", + "you": "You", + "ahead": "(ahead)", + "rowTrophies": "Trophies", + "rowPoints": "Points in this game", + "rowLevel": "Level overall", + "grade": { + "platinum": "Platinum", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze" + }, + "both": "Earned by both", + "onlyYou": "Only you", + "onlyThem": "Only {handle}" + }, + "publicProfile": { + "home": "AnyPlay, home", + "yourAccount": "Your account", + "signIn": "Sign in", + "loading": "Loading", + "missingTitle": "No player named {handle}", + "missingBody": "Check the spelling of the handle, or ask the player to send it again.", + "goHome": "Go to AnyPlay", + "player": "Player", + "trophies": "Trophies", + "trophiesEarned": "{count, number} earned.", + "noTrophies": "{handle} has not earned a trophy yet.", + "favourites": "Favourite games", + "favouritesNote": "Shown by title ID.", + "game": "Game", + "connections": "Connections", + "playtime": "Games played", + "playtimeNote": "{count, plural, one {# game} other {# games}}, {total} in total.", + "nothing": "There is nothing more to show of {handle} yet.", + "openProfile": "Open profile", + "openProfileOn": "on {brand} (opens in a new tab)", + "signInToAdd": "Sign in to add {handle}", + "editProfile": "Edit your profile", + "blocked": "Blocked", + "friends": "Friends", + "requestSent": "Request sent", + "accepting": "Accepting…", + "accept": "Accept request", + "sending": "Sending…", + "addFriend": "Add friend", + "addInAnyPlay": "Add friend in AnyPlay", + "minutes": "{n} min", + "hours": "{n} h" + }, + "friends": { + "title": "Friends", + "lead": "Add the people you play with, answer requests and decide who stays out.", + "add": { + "title": "Add a friend", + "description": "Enter a player’s handle to send them a request.", + "handle": "Handle", + "hint": "Their handle, with or without the @.", + "send": "Send request", + "sending": "Sending…", + "enterHandle": "Enter the handle of the player you want to add.", + "nowFriends": "You and @{handle} are now friends.", + "sent": "Request sent to @{handle}. You are friends once they accept." + }, + "requests": { + "title": "Requests", + "description": "Players who want to be your friend, and the requests you sent.", + "empty": "No open requests. When someone adds you, the request shows up here.", + "received": "Received", + "sent": "Sent", + "receivedAgo": "Received {when}", + "sentAgo": "Sent {when}", + "decline": "Decline", + "accept": "Accept", + "withdraw": "Withdraw" + }, + "list": { + "title": "Your friends", + "description": "See who is online and what they are playing.", + "empty": "No friends yet. Add someone by their handle above; they show up here once they accept.", + "online": "Online", + "offline": "Offline", + "playing": "Playing {title}", + "block": "Block", + "remove": "Remove" + }, + "blocked": { + "title": "Blocked", + "description": "Blocked players are removed from your friends and requests.", + "empty": "You have not blocked anyone. Block a player from your friends list when you need to.", + "unblock": "Unblock" + }, + "confirm": { + "blockTitle": "Block @{handle}?", + "removeTitle": "Remove @{handle} from your friends?", + "blockText": "You are no longer friends and any open request is dropped. You can unblock them any time.", + "removeText": "You stop seeing when each other is online. You can send a new request later.", + "blockLabel": "Block player", + "removeLabel": "Remove friend" + } + }, + "trophies": { + "title": "Trophies", + "lead": "What you earned in your games. AnyPlay keeps counts per game and grade, never trophy names or art.", + "totals": { + "title": "Totals", + "description": "{total} earned across {games, plural, one {# game} other {# games}}.", + "empty": "No trophies yet. Play a game through AnyPlay and the ones you earn are counted here." + }, + "byGame": { + "title": "By game", + "description": "Games are listed by title id, the most recent first.", + "detail": "{earned, plural, one {# trophy} other {# trophies}} · last earned {when}" + }, + "grades": { + "platinum": "Platinum", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze" + } + }, + "notifications": { + "title": "Notifications", + "lead": "Get a notification on this device when a friend asks to be your friend, invites you to a party or writes to you.", + "device": { + "title": "This device", + "description": "Each browser or phone is set up on its own. Turn this on where you want to be notified.", + "notEnabled": "This cloud does not send notifications.", + "checking": "Checking this device…", + "unsupported": "This browser can’t receive notifications. Use a current Chrome, Edge, Firefox or Safari.", + "needsInstall": "On iPhone and iPad, notifications need AnyPlay on your Home Screen. Tap Share, choose Add to Home Screen, then open AnyPlay from there.", + "blocked": "Notifications are blocked for this site. Allow them in your browser’s site settings, then come back.", + "switch": "Notify this device", + "others": "{count, plural, one {One other device also gets notifications. Turn them off on that device.} other {# other devices also get notifications. Turn them off on that device.}}" + }, + "kinds": { + "title": "What to notify about", + "description": "For this device only.", + "friend_request": { + "label": "Friend requests", + "detail": "Someone asks to be your friend." + }, + "party_invite": { + "label": "Party invitations", + "detail": "A friend invites you to a party." + }, + "message": { + "label": "Messages", + "detail": "A friend writes to you. The message itself stays out of the notification." + } + }, + "errors": { + "timeout": "Your browser’s notification service did not answer. Check your connection and try again.", + "noKey": "This cloud has no key to send notifications with.", + "lostSubscription": "This browser lost its subscription. Turn notifications off and on again.", + "notAllowed": "This browser did not allow notifications. They don’t work in a private window; otherwise check the site settings." + } + } +} diff --git a/apps/web/messages/en/auth.json b/apps/web/messages/en/auth.json new file mode 100644 index 0000000..fb8324e --- /dev/null +++ b/apps/web/messages/en/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Sign in", + "description": "Sign in to AnyPlay with a passkey or a code by e-mail." + }, + "create": { + "title": "Create your account", + "description": "Create your AnyPlay account: an e-mail address and a handle." + }, + "link": { + "title": "Open your account", + "description": "Open your AnyPlay account in this browser, from the launcher." + }, + "join": { + "title": "Invitation", + "description": "An invitation to play together on AnyPlay." + } + }, + "shell": { + "home": "AnyPlay, home", + "back": "Back to the start", + "promise": "Play your games together.", + "toastOnline": "mira is online", + "toastParty": "kit joined your party", + "toastTrophy": "Trophy earned · Into the Ember", + "help": "Need help?", + "helpLink": "Ask on Discord", + "legalNav": "Legal", + "legalNotice": "Legal notice", + "privacyNotice": "Privacy notice", + "termsOfUse": "Terms of use" + }, + "step": { + "loading": "Loading", + "unreachable": "Can’t reach AnyPlay", + "tryAgain": "Try again", + "newTab": "(opens in a new tab)", + "backToStart": "Back to the start", + "signIn": "Sign in", + "createAccount": "Create an account", + "getApp": "Get AnyPlay", + "continue": "Continue" + }, + "fields": { + "code": "Code" + }, + "flow": { + "authorize": "AnyPlay on {device} is waiting for this sign-in." + }, + "email": { + "create": { + "title": "Create your account", + "lead": "Start with your e-mail address. We send a code to confirm it." + }, + "signIn": { + "title": "Sign in", + "lead": "Use your passkey, or get a code by e-mail." + }, + "passkey": "Sign in with a passkey", + "or": "or", + "label": "E-mail address", + "placeholder": "name@example.com", + "invalid": "Enter your e-mail address, like name@example.com.", + "sending": "Sending…", + "send": "Send code", + "haveAccount": "Already have an account? Sign in", + "newHere": "New to AnyPlay? Create an account" + }, + "code": { + "title": "Check your e-mail", + "lead": "We sent a 6-digit code to {email}. It works for 10 minutes.", + "incomplete": "Enter the 6 digits from the e-mail.", + "checking": "Checking…", + "resent": "A new code is on its way.", + "noMail": "No e-mail?", + "resendIn": "Send a new code in {seconds} s", + "resend": "Send a new code" + }, + "details": { + "title": "Choose a handle", + "lead": "Your address {email} is confirmed. Other players see your handle.", + "handle": "Handle", + "handleHint": "Use 3 to 16 letters, digits, hyphens or underscores. Start with a letter.", + "ageHint": "That means 16 or older.", + "age": "I am old enough to agree to the terms on my own.", + "terms": "I accept the terms (version {version}) and have read the privacy notice.", + "creating": "Creating…", + "submit": "Create account" + }, + "terms": { + "title": "Review the terms", + "lead": "The terms changed. Read the terms and accept version {version} to continue.", + "accept": "I accept the terms (version {version}).", + "required": "Accept the terms to continue.", + "submit": "Accept and continue" + }, + "banned": { + "title": "This account is banned", + "leadUntil": "The ban ends on {date}.", + "leadForever": "The ban does not end on its own.", + "rule": "Rule", + "reason": "Reason", + "appeal": "Read the statement and appeal" + }, + "handoff": { + "title": "Signed in", + "lead": "Taking you back to AnyPlay. If nothing happens, use the button.", + "open": "Open AnyPlay", + "note": "You can close this tab once AnyPlay shows you as signed in." + }, + "done": { + "title": "You’re signed in", + "inviteLead": "Taking you back to the invitation.", + "welcome": "Welcome, {handle}. Your friends, parties and trophies are waiting in the launcher.", + "account": "Go to your account" + }, + "join": { + "expiredTitle": "This invitation has expired", + "expiredLead": "The link may have run out, or the player has stopped playing. Ask them for a new one.", + "title": "{handle} invited you", + "leadSignedIn": "Open AnyPlay on your PC to see the invitation. You choose there whether to join.", + "leadSignedOut": "Sign in or create an account first. You come back here to open the invitation.", + "party": "Party", + "game": "Game", + "partyName": "An AnyPlay party", + "gameFallback": "A game", + "joined": "Joined", + "players": "{members} of {max, plural, one {# player} other {# players}}", + "open": "Open in AnyPlay", + "noApp": "No AnyPlay on this PC yet? Get it first, then open this page again." + }, + "link": { + "expiredTitle": "This link has expired", + "expiredLead": "Open your account page from AnyPlay again.", + "close": "You can close this tab.", + "title": "Open your account", + "lead": "AnyPlay on your PC asked to open the account of {handle} in this browser.", + "open": "Open account", + "opening": "Opening…", + "notYou": "Did not ask for this? Close the tab." + } +} diff --git a/apps/web/messages/en/common.json b/apps/web/messages/en/common.json new file mode 100644 index 0000000..e7c6bd5 --- /dev/null +++ b/apps/web/messages/en/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Something went wrong. Try again.", + "network": "Can’t reach AnyPlay. Check your connection and try again.", + "internal": "AnyPlay had a problem on its side. Try again in a minute.", + "unauthorized": "Your session ended. Sign in again.", + "forbidden": "You can’t do that with this account.", + "banned": "This account is banned.", + "not_found": "That doesn’t exist any more.", + "limit": "You’ve reached the limit for this. Remove one first.", + "too_large": "That is too large to send.", + "code_wrong": "That code is wrong. Check it and try again.", + "code_expired": "That code has expired. Request a new one.", + "handle_taken": "That handle is taken. Try another.", + "handle_invalid": "Use 3 to 16 letters, digits, hyphens or underscores. Start with a letter.", + "rate_limited": "Too many tries. Wait a moment and try again.", + "rate_limited_wait": "Too many tries. Wait {seconds, plural, one {# second} other {# seconds}} and try again.", + "passkey_unsupported": "This browser can’t use passkeys. Use the e-mail code instead." + } +} diff --git a/apps/web/messages/es/account.json b/apps/web/messages/es/account.json new file mode 100644 index 0000000..da0d603 --- /dev/null +++ b/apps/web/messages/es/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "nunca", + "on": "Activado", + "off": "Desactivado", + "cancel": "Cancelar", + "working": "Procesando…", + "typeToConfirm": "Escribe {word} para confirmar", + "audience": { + "everyone": "Todos", + "friends": "Amigos", + "nobody": "Solo yo" + }, + "stepUp": { + "title": "Confirma que eres tú", + "needed": "Este cambio requiere un inicio de sesión reciente. Dura 15 minutos.", + "sent": "Hemos enviado un código de 6 dígitos a {hint}.", + "passkey": "Usa tu llave de acceso", + "sending": "Enviando…", + "sendCode": "Enviar un código por correo" + } + }, + "shell": { + "home": "AnyPlay, inicio", + "signOut": "Cerrar sesión", + "nav": "Cuenta", + "nav_overview": "Resumen", + "nav_profile": "Perfil", + "nav_friends": "Amigos", + "nav_trophies": "Trofeos", + "nav_security": "Seguridad", + "nav_notifications": "Notificaciones", + "nav_privacy": "Privacidad", + "nav_connections": "Conexiones", + "nav_data": "Tus datos", + "unreachable": "No se puede acceder a AnyPlay. Revisa tu conexión e inténtalo de nuevo.", + "loading": "Cargando" + }, + "level": { + "top": "Nivel máximo alcanzado", + "toNext": "{count, plural, one {# punto} other {# puntos}} para el nivel {level}", + "level": "Nivel {level}", + "points": "{count, plural, one {# punto} other {# puntos}}", + "progress": "Progreso de nivel", + "progressTo": "Progreso hacia el nivel {level}" + }, + "meta": { + "title": "Tu cuenta", + "template": "%s · Tu cuenta · AnyPlay" + }, + "state": { + "loading": "Cargando", + "tryAgain": "Reintentar" + }, + "overview": { + "label": "Tu cuenta", + "memberSince": "Miembro desde {date}", + "appearingOffline": "Apareces sin conexión", + "deletion": "Tu cuenta se eliminará el {date}. Conservar mi cuenta", + "passkeys": "{count, plural, one {# llave de acceso} other {# llaves de acceso}}", + "noPasskey": "Aún no hay llaves de acceso", + "launchersBrowsers": "{launchers, plural, one {# launcher} other {# launchers}} · {browsers, plural, one {# navegador} other {# navegadores}}", + "trophiesAudience": "Trofeos: {audience}", + "pictureAudience": "Foto de perfil: {audience}", + "audience": { + "everyone": "Todos", + "friends": "Amigos", + "nobody": "Solo tú" + }, + "audienceInline": { + "everyone": "todos", + "friends": "amigos", + "nobody": "solo tú" + }, + "linked": "{count} vinculadas", + "nothingLinked": "Nada vinculado", + "connectionsDetail": "Discord, Steam", + "dataValue": "Exportar o eliminar", + "dataDetail": "Todo lo que guardamos sobre ti", + "launcher": { + "title": "Consigue el launcher", + "description": "Inicia sesión en el launcher con esta cuenta y tus amigos, parties y trofeos estarán ahí." + } + }, + "profile": { + "lead": "Lo que ven tus amigos al abrir tu perfil. Quién ve cada parte lo decides tú en la página de Privacidad.", + "avatar": { + "title": "Foto", + "description": "PNG o JPEG, de hasta 1 MB. Se muestra redonda, así que deja la cara en el centro.", + "uploading": "Subiendo…", + "change": "Cambiar foto", + "upload": "Subir foto", + "remove": "Quitar" + }, + "about": { + "title": "Sobre mí", + "description": "Una o dos líneas sobre ti: a qué juegas, cuándo estás por aquí.", + "placeholder": "Cooperativo cuando puedo, solo cuando no.", + "saved": "Guardado", + "saving": "Guardando…", + "save": "Guardar" + }, + "favourites": { + "title": "Juegos favoritos", + "description": "Hasta {max}, por ID de título. Esta web muestra el ID. El launcher muestra el nombre de cada juego desde tu propia copia.", + "empty": "Aún no hay juegos favoritos. Añade un ID de título abajo.", + "listLabel": "Tus juegos favoritos", + "remove": "Quitar {id}", + "fieldLabel": "ID de título", + "full": "Tienes {max} juegos. Quita uno para añadir otro.", + "add": "Añadir juego", + "saved": "Guardado", + "saving": "Guardando…", + "save": "Guardar", + "badId": "Usa solo letras, números, guiones y guiones bajos.", + "duplicate": "{id} ya está en la lista." + } + }, + "security": { + "lead": "Cómo inicias sesión y todos los launchers y navegadores con la sesión iniciada en tu cuenta.", + "email": { + "title": "Correo electrónico", + "description": "Adónde van tus códigos de inicio de sesión. Para cambiarlo hace falta un código enviado a la nueva dirección.", + "change": "Cambiar", + "invalid": "Escribe la nueva dirección, por ejemplo nombre@ejemplo.com.", + "done": "Tu dirección de inicio de sesión ahora es {email}.", + "newAddress": "Nueva dirección", + "sending": "Enviando…", + "send": "Enviar código", + "cancel": "Cancelar", + "sentTo": "Hemos enviado un código de 6 dígitos a {email}." + }, + "passkeys": { + "title": "Llaves de acceso", + "description": "Inicia sesión con tu huella, tu cara o el PIN del dispositivo en lugar de un código. Nadie puede suplantar una llave de acceso.", + "add": "Añadir una llave de acceso", + "waiting": "Esperando a tu dispositivo…", + "detail": "Añadida {added} · Último uso: {used}", + "remove": "Quitar", + "empty": "Aún no hay llaves de acceso. Añade una y iniciar sesión será un toque en lugar de un código por correo.", + "full": "Tienes el máximo de llaves de acceso que puede tener una cuenta ({max}). Quita una para añadir otra.", + "confirmTitle": "¿Quitar esta llave de acceso?", + "confirmDescription": "Ese dispositivo ya no podrá iniciar tu sesión con ella. Siempre puedes iniciar sesión con un código por correo.", + "confirmLabel": "Quitar llave de acceso", + "defaultName": "Llave de acceso en {system}", + "thisDevice": "este dispositivo" + }, + "devices": { + "title": "Launchers", + "description": "Todos los PC donde el launcher tiene la sesión iniciada en tu cuenta.", + "detail": "{platform} · Último uso: {used}", + "signOut": "Cerrar sesión", + "empty": "Aún no hay ningún launcher con la sesión iniciada.", + "confirmTitle": "¿Cerrar sesión en este launcher?", + "confirmDescription": "Se quedará sin conexión hasta que alguien inicie sesión de nuevo en ese PC.", + "confirmLabel": "Cerrar sesión del launcher" + }, + "browsers": { + "title": "Navegadores", + "description": "Todos los navegadores con la sesión iniciada en esta página de cuenta. De ellos solo guardamos cuándo se usaron.", + "thisBrowser": "Este navegador", + "here": "Estás aquí", + "other": "Otro navegador", + "detail": "Sesión iniciada {created} · Último uso: {used}", + "signOut": "Cerrar sesión", + "confirmThisTitle": "¿Cerrar sesión en este navegador?", + "confirmOtherTitle": "¿Cerrar sesión en ese navegador?", + "confirmThisDescription": "Vuelves al inicio y tendrás que iniciar sesión de nuevo la próxima vez.", + "confirmOtherDescription": "Tendrá que iniciar sesión de nuevo para abrir tu cuenta.", + "confirmLabel": "Cerrar sesión" + }, + "everywhere": { + "title": "Cerrar sesión en todas partes", + "description": "Todos los launchers y todos los navegadores, también este. Úsalo si has perdido un dispositivo o alguien más pudo iniciar sesión.", + "button": "Cerrar sesión en todas partes", + "confirmTitle": "¿Cerrar sesión en todas partes?", + "confirmDescription": "Se cierra la sesión de todos los launchers y navegadores a la vez, también este.", + "confirmLabel": "Cerrar sesión en todas partes" + } + }, + "privacy": { + "lead": "Quién ve tu perfil y tu actividad. Tu nombre siempre es visible, para que tus amigos te encuentren.", + "profile": { + "title": "Tu perfil", + "description": "Los cambios cuentan al instante, también en el launcher." + }, + "parts": { + "trophies": { + "title": "Trofeos", + "detail": "Tu número de trofeos, tus puntos y tu nivel" + }, + "playtime": { + "title": "Tiempo de juego", + "detail": "Cuánto has jugado, por juego" + }, + "about": { + "title": "Sobre mí", + "detail": "La línea de tu perfil" + }, + "favourites": { + "title": "Juegos favoritos", + "detail": "Hasta tres juegos que fijas" + }, + "avatar": { + "title": "Foto", + "detail": "Tu foto de perfil" + } + }, + "whoSees": "Quién ve: {part}", + "activity": { + "title": "Tu actividad", + "description": "Lo que haces en AnyPlay, tal como lo ven tus amigos en su feed.", + "row": "Actividad", + "detail": "Tus amigos la ven en su feed. Los desconocidos no ven ningún feed. Elige «{nobody}» para ocultar lo que hay y detener los elementos nuevos." + }, + "appearOffline": { + "title": "Estado en línea", + "description": "Se aplica a todos los launchers y navegadores con la sesión iniciada en tu cuenta.", + "label": "Aparecer sin conexión", + "detail": "Tus amigos te ven sin conexión. Aun así puedes jugar, chatear y unirte." + }, + "publicProfile": { + "title": "Perfil público", + "description": "Desactivado hasta que lo actives. Se aplica a todos los launchers y navegadores con la sesión iniciada en tu cuenta.", + "label": "Mostrar un perfil público", + "detail": "Cualquiera con el enlace ve tu nivel, tus trofeos y los juegos a los que has jugado. Nunca muestra si estás en línea.", + "page": "Tu página: {url}" + }, + "playedWith": { + "title": "Jugadores recientes", + "description": "Si lo desactivas, no se añade a nadie nuevo. La lista se queda hasta que la vacíes.", + "label": "Recordar con quién juego", + "detail": "Guarda durante 30 días a las personas con quienes compartiste una party o una sesión de juego, hasta 50. Solo tú ves la lista.", + "clear": "Vaciar la lista", + "cleared": "La lista está vacía.", + "confirmTitle": "¿Vaciar la lista?", + "confirmDescription": "Se quita a todos de tu lista de personas con las que has jugado. No cambia la lista de nadie más y no se puede deshacer." + } + }, + "connections": { + "lead": "Vincula las cuentas con las que ya juegas, para que tus amigos te encuentren y vean dónde más estás.", + "result": { + "linked": "{name} está vinculado a tu cuenta.", + "denied": "{name} no se vinculó: la solicitud fue rechazada allí.", + "expired": "Vincular {name} tardó demasiado. Empieza de nuevo.", + "fresh": "Vincular {name} requiere un inicio de sesión reciente. Empieza de nuevo y confirma que eres tú.", + "taken": "Esa cuenta de {name} ya está vinculada a otra cuenta de AnyPlay.", + "exists": "Ya tienes una cuenta de {name} vinculada. Desvincúlala primero.", + "failed": "No se pudo vincular {name}. Inténtalo de nuevo en un minuto." + }, + "whoSees": "Quién ve tu cuenta de {name}", + "linkedAgo": "Vinculada {ago}", + "unlink": "Desvincular", + "link": "Vincular {name}", + "opening": "Abriendo…", + "soon": "Próximamente", + "notLinked": "Sin vincular", + "arrivesSoon": "La vinculación llega pronto", + "confirmTitle": "¿Desvincular {name}?", + "confirmDescription": "Tu cuenta de {name} ya no aparece en tu perfil. Puedes volver a vincularla cuando quieras.", + "confirmLabel": "Desvincular {name}" + }, + "data": { + "lead": "Todo lo que AnyPlay guarda sobre ti es tuyo: llévate una copia o elimina la cuenta.", + "export": { + "title": "Descargar tus datos", + "description": "Tu cuenta, perfil, amigos, trofeos, tiempo de juego y cuentas vinculadas, en un solo archivo JSON.", + "preparing": "Preparando…", + "button": "Descargar" + }, + "scheduled": { + "title": "Eliminación programada", + "description": "Tu cuenta y todo lo que contiene se eliminarán el {date}. Hasta entonces puedes conservarla.", + "keep": "Conservar mi cuenta" + }, + "delete": { + "title": "Eliminar tu cuenta", + "description": "Tu cuenta se elimina a los 30 días; hasta entonces puedes cambiar de opinión. Después desaparece, junto con tus amigos, trofeos y tiempo de juego.", + "button": "Eliminar cuenta" + }, + "confirm": { + "title": "¿Eliminar tu cuenta?", + "description": "Se elimina en 30 días. Se cierra ahora la sesión de todos los launchers y navegadores." + } + }, + "compare": { + "title": "Comparar trofeos", + "description": "Tus trofeos junto a los de {handle}.", + "descriptionShared": "Tú y {handle}, en un juego que tenéis los dos.", + "signInDescription": "Mira cómo se comparan tus trofeos con los de {handle}.", + "signIn": "Inicia sesión para comparar trofeos", + "hidden": "Los trofeos de {handle} no están disponibles para comparar.", + "loading": "Cargando", + "loadingComparison": "Cargando la comparación", + "empty": "Tú y {handle} aún no tenéis ningún juego en común. Juega a un juego que {handle} haya jugado y aparecerá aquí.", + "game": "Juego", + "gameNamed": "Juego {title}", + "verdictNone": "Ninguno de los dos ha conseguido un trofeo en este juego.", + "verdictLevel": "Estáis igualados en puntos en este juego.", + "verdictAhead": "Vas {count, plural, one {# punto} other {# puntos}} por delante en este juego.", + "verdictBehind": "{handle} va {count, plural, one {# punto} other {# puntos}} por delante en este juego.", + "caption": "Trofeos en {titleId}: tú junto a {handle}", + "stat": "Categoría", + "you": "Tú", + "ahead": "(por delante)", + "rowTrophies": "Trofeos", + "rowPoints": "Puntos en este juego", + "rowLevel": "Nivel general", + "grade": { + "platinum": "Platino", + "gold": "Oro", + "silver": "Plata", + "bronze": "Bronce" + }, + "both": "Conseguidos por ambos", + "onlyYou": "Solo tú", + "onlyThem": "Solo {handle}" + }, + "publicProfile": { + "home": "AnyPlay, inicio", + "yourAccount": "Tu cuenta", + "signIn": "Iniciar sesión", + "loading": "Cargando", + "missingTitle": "No hay ningún jugador llamado {handle}", + "missingBody": "Revisa cómo está escrito el nombre o pídele al jugador que lo envíe de nuevo.", + "goHome": "Ir a AnyPlay", + "player": "Jugador", + "trophies": "Trofeos", + "trophiesEarned": "{count, number} conseguidos.", + "noTrophies": "{handle} aún no ha conseguido ningún trofeo.", + "favourites": "Juegos favoritos", + "favouritesNote": "Se muestran por ID del título.", + "game": "Juego", + "connections": "Conexiones", + "playtime": "Juegos jugados", + "playtimeNote": "{count, plural, one {# juego} other {# juegos}}, {total} en total.", + "nothing": "Aún no hay nada más que mostrar de {handle}.", + "openProfile": "Abrir perfil", + "openProfileOn": "en {brand} (se abre en una pestaña nueva)", + "signInToAdd": "Inicia sesión para añadir a {handle}", + "editProfile": "Editar tu perfil", + "blocked": "Bloqueado", + "friends": "Amigos", + "requestSent": "Solicitud enviada", + "accepting": "Aceptando…", + "accept": "Aceptar solicitud", + "sending": "Enviando…", + "addFriend": "Añadir amigo", + "addInAnyPlay": "Añadir amigo en AnyPlay", + "minutes": "{n} min", + "hours": "{n} h" + }, + "friends": { + "title": "Amigos", + "lead": "Añade a la gente con la que juegas, responde solicitudes y decide quién se queda fuera.", + "add": { + "title": "Añadir un amigo", + "description": "Escribe el nombre de un jugador para enviarle una solicitud.", + "handle": "Nombre", + "hint": "Su nombre, con o sin la @.", + "send": "Enviar solicitud", + "sending": "Enviando…", + "enterHandle": "Escribe el nombre del jugador que quieres añadir.", + "nowFriends": "Tú y @{handle} ya sois amigos.", + "sent": "Solicitud enviada a @{handle}. Seréis amigos cuando la acepte." + }, + "requests": { + "title": "Solicitudes", + "description": "Jugadores que quieren ser tus amigos y las solicitudes que has enviado.", + "empty": "No hay solicitudes abiertas. Cuando alguien te añada, la solicitud aparecerá aquí.", + "received": "Recibidas", + "sent": "Enviadas", + "receivedAgo": "Recibida {when}", + "sentAgo": "Enviada {when}", + "decline": "Rechazar", + "accept": "Aceptar", + "withdraw": "Retirar" + }, + "list": { + "title": "Tus amigos", + "description": "Mira quién está en línea y a qué juega.", + "empty": "Aún no tienes amigos. Añade a alguien por su nombre arriba; aparecerá aquí cuando acepte.", + "online": "En línea", + "offline": "Sin conexión", + "playing": "Jugando a {title}", + "block": "Bloquear", + "remove": "Quitar" + }, + "blocked": { + "title": "Bloqueados", + "description": "Los jugadores bloqueados se quitan de tus amigos y solicitudes.", + "empty": "No has bloqueado a nadie. Bloquea a un jugador desde tu lista de amigos cuando lo necesites.", + "unblock": "Desbloquear" + }, + "confirm": { + "blockTitle": "¿Bloquear a @{handle}?", + "removeTitle": "¿Quitar a @{handle} de tus amigos?", + "blockText": "Dejáis de ser amigos y se descarta cualquier solicitud abierta. Puedes desbloquearlo cuando quieras.", + "removeText": "Dejáis de ver cuándo está en línea el otro. Puedes enviar una solicitud nueva más tarde.", + "blockLabel": "Bloquear jugador", + "removeLabel": "Quitar amigo" + } + }, + "trophies": { + "title": "Trofeos", + "lead": "Lo que has conseguido en tus juegos. AnyPlay guarda recuentos por juego y categoría, nunca nombres ni imágenes de trofeos.", + "totals": { + "title": "Totales", + "description": "{total} conseguidos en {games, plural, one {# juego} other {# juegos}}.", + "empty": "Aún no hay trofeos. Juega a algo con AnyPlay y los que consigas se contarán aquí." + }, + "byGame": { + "title": "Por juego", + "description": "Los juegos se ordenan por ID de título, los más recientes primero.", + "detail": "{earned, plural, one {# trofeo} other {# trofeos}} · último conseguido: {when}" + }, + "grades": { + "platinum": "Platino", + "gold": "Oro", + "silver": "Plata", + "bronze": "Bronce" + } + }, + "notifications": { + "title": "Notificaciones", + "lead": "Recibe una notificación en este dispositivo cuando un amigo te envíe una solicitud, te invite a una party o te escriba.", + "device": { + "title": "Este dispositivo", + "description": "Cada navegador o móvil se configura por separado. Actívalo donde quieras recibir avisos.", + "notEnabled": "Esta nube no envía notificaciones.", + "checking": "Comprobando este dispositivo…", + "unsupported": "Este navegador no puede recibir notificaciones. Usa una versión actual de Chrome, Edge, Firefox o Safari.", + "needsInstall": "En iPhone y iPad, las notificaciones necesitan AnyPlay en tu pantalla de inicio. Toca Compartir, elige Añadir a pantalla de inicio y abre AnyPlay desde ahí.", + "blocked": "Las notificaciones están bloqueadas para este sitio. Permítelas en los ajustes del sitio de tu navegador y vuelve.", + "switch": "Notificar a este dispositivo", + "others": "{count, plural, one {Otro dispositivo también recibe notificaciones. Desactívalas en ese dispositivo.} other {Otros # dispositivos también reciben notificaciones. Desactívalas en esos dispositivos.}}" + }, + "kinds": { + "title": "Sobre qué avisar", + "description": "Solo para este dispositivo.", + "friend_request": { + "label": "Solicitudes de amistad", + "detail": "Alguien quiere ser tu amigo." + }, + "party_invite": { + "label": "Invitaciones a party", + "detail": "Un amigo te invita a una party." + }, + "message": { + "label": "Mensajes", + "detail": "Un amigo te escribe. El mensaje en sí no aparece en la notificación." + } + }, + "errors": { + "timeout": "El servicio de notificaciones de tu navegador no ha respondido. Comprueba tu conexión e inténtalo de nuevo.", + "noKey": "Esta nube no tiene una clave para enviar notificaciones.", + "lostSubscription": "Este navegador ha perdido su suscripción. Desactiva las notificaciones y vuelve a activarlas.", + "notAllowed": "Este navegador no ha permitido las notificaciones. No funcionan en una ventana privada; si no, revisa los ajustes del sitio." + } + } +} diff --git a/apps/web/messages/es/auth.json b/apps/web/messages/es/auth.json new file mode 100644 index 0000000..0c87adf --- /dev/null +++ b/apps/web/messages/es/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Iniciar sesión", + "description": "Inicia sesión en AnyPlay con una llave de acceso o con un código por correo." + }, + "create": { + "title": "Crea tu cuenta", + "description": "Crea tu cuenta de AnyPlay: un correo electrónico y un nombre." + }, + "link": { + "title": "Abre tu cuenta", + "description": "Abre tu cuenta de AnyPlay en este navegador desde el launcher." + }, + "join": { + "title": "Invitación", + "description": "Una invitación para jugar juntos en AnyPlay." + } + }, + "shell": { + "home": "AnyPlay, inicio", + "back": "Volver al inicio", + "promise": "Juega tus juegos juntos.", + "toastOnline": "mira está en línea", + "toastParty": "kit se unió a tu party", + "toastTrophy": "Trofeo conseguido · Into the Ember", + "help": "¿Necesitas ayuda?", + "helpLink": "Pregunta en Discord", + "legalNav": "Legal", + "legalNotice": "Aviso legal", + "privacyNotice": "Aviso de privacidad", + "termsOfUse": "Condiciones de uso" + }, + "step": { + "loading": "Cargando", + "unreachable": "No se puede conectar con AnyPlay", + "tryAgain": "Reintentar", + "newTab": "(se abre en una pestaña nueva)", + "backToStart": "Volver al inicio", + "signIn": "Iniciar sesión", + "createAccount": "Crear cuenta", + "getApp": "Obtener AnyPlay", + "continue": "Continuar" + }, + "fields": { + "code": "Código" + }, + "flow": { + "authorize": "AnyPlay en {device} está esperando este inicio de sesión." + }, + "email": { + "create": { + "title": "Crea tu cuenta", + "lead": "Empieza con tu correo electrónico. Te enviamos un código para confirmarlo." + }, + "signIn": { + "title": "Iniciar sesión", + "lead": "Usa tu llave de acceso o recibe un código por correo." + }, + "passkey": "Iniciar sesión con una llave de acceso", + "or": "o", + "label": "Correo electrónico", + "placeholder": "name@example.com", + "invalid": "Escribe tu correo electrónico, por ejemplo name@example.com.", + "sending": "Enviando…", + "send": "Enviar código", + "haveAccount": "¿Ya tienes una cuenta? Iniciar sesión", + "newHere": "¿Eres nuevo en AnyPlay? Crear cuenta" + }, + "code": { + "title": "Revisa tu correo", + "lead": "Enviamos un código de 6 dígitos a {email}. Es válido durante 10 minutos.", + "incomplete": "Escribe los 6 dígitos del correo.", + "checking": "Comprobando…", + "resent": "Un código nuevo está en camino.", + "noMail": "¿No llega el correo?", + "resendIn": "Enviar un código nuevo en {seconds} s", + "resend": "Enviar un código nuevo" + }, + "details": { + "title": "Elige un nombre", + "lead": "Tu dirección {email} está confirmada. Los demás jugadores ven tu nombre.", + "handle": "Nombre", + "handleHint": "Usa de 3 a 16 letras, dígitos, guiones o guiones bajos. Empieza con una letra.", + "ageHint": "Es decir, 16 años o más.", + "age": "Tengo edad suficiente para aceptar las condiciones por mi cuenta.", + "terms": "Acepto las condiciones (versión {version}) y he leído el aviso de privacidad.", + "creating": "Creando…", + "submit": "Crear cuenta" + }, + "terms": { + "title": "Revisa las condiciones", + "lead": "Las condiciones han cambiado. Lee las condiciones y acepta la versión {version} para continuar.", + "accept": "Acepto las condiciones (versión {version}).", + "required": "Acepta las condiciones para continuar.", + "submit": "Aceptar y continuar" + }, + "banned": { + "title": "Esta cuenta está suspendida", + "leadUntil": "La suspensión termina el {date}.", + "leadForever": "La suspensión no termina por sí sola.", + "rule": "Norma", + "reason": "Motivo", + "appeal": "Leer la declaración y apelar" + }, + "handoff": { + "title": "Sesión iniciada", + "lead": "Te llevamos de vuelta a AnyPlay. Si no pasa nada, usa el botón.", + "open": "Abrir AnyPlay", + "note": "Puedes cerrar esta pestaña cuando AnyPlay te muestre con la sesión iniciada." + }, + "done": { + "title": "Has iniciado sesión", + "inviteLead": "Te llevamos de vuelta a la invitación.", + "welcome": "Te damos la bienvenida, {handle}. Tus amigos, partys y trofeos te esperan en el launcher.", + "account": "Ir a tu cuenta" + }, + "join": { + "expiredTitle": "Esta invitación ha caducado", + "expiredLead": "Puede que el enlace haya caducado o que el jugador haya dejado de jugar. Pide uno nuevo.", + "title": "{handle} te ha invitado", + "leadSignedIn": "Abre AnyPlay en tu PC para ver la invitación. Allí decides si te unes.", + "leadSignedOut": "Primero inicia sesión o crea una cuenta. Después vuelves aquí para abrir la invitación.", + "party": "Party", + "game": "Juego", + "partyName": "Una party de AnyPlay", + "gameFallback": "Un juego", + "joined": "Se han unido", + "players": "{members} de {max, plural, one {# jugador} other {# jugadores}}", + "open": "Abrir en AnyPlay", + "noApp": "¿Aún no tienes AnyPlay en este PC? Consíguelo primero y vuelve a abrir esta página." + }, + "link": { + "expiredTitle": "Este enlace ha caducado", + "expiredLead": "Vuelve a abrir tu página de cuenta desde AnyPlay.", + "close": "Puedes cerrar esta pestaña.", + "title": "Abre tu cuenta", + "lead": "AnyPlay en tu PC ha pedido abrir la cuenta de {handle} en este navegador.", + "open": "Abrir cuenta", + "opening": "Abriendo…", + "notYou": "¿No lo has pedido tú? Cierra la pestaña." + } +} diff --git a/apps/web/messages/es/common.json b/apps/web/messages/es/common.json new file mode 100644 index 0000000..8fad371 --- /dev/null +++ b/apps/web/messages/es/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Algo salió mal. Inténtalo de nuevo.", + "network": "No se puede conectar con AnyPlay. Comprueba tu conexión e inténtalo de nuevo.", + "internal": "AnyPlay tuvo un problema. Inténtalo de nuevo en un minuto.", + "unauthorized": "Tu sesión ha terminado. Inicia sesión de nuevo.", + "forbidden": "No puedes hacer eso con esta cuenta.", + "banned": "Esta cuenta está bloqueada.", + "not_found": "Eso ya no existe.", + "limit": "Has llegado al límite. Quita uno primero.", + "too_large": "Eso es demasiado grande para enviarlo.", + "code_wrong": "Ese código es incorrecto. Revísalo e inténtalo de nuevo.", + "code_expired": "Ese código ha caducado. Solicita uno nuevo.", + "handle_taken": "Ese nombre de usuario ya está en uso. Prueba con otro.", + "handle_invalid": "Usa de 3 a 16 letras, dígitos, guiones o guiones bajos. Empieza con una letra.", + "rate_limited": "Demasiados intentos. Espera un momento e inténtalo de nuevo.", + "rate_limited_wait": "Demasiados intentos. Espera {seconds, plural, one {# segundo} other {# segundos}} e inténtalo de nuevo.", + "passkey_unsupported": "Este navegador no admite passkeys. Usa el código por correo electrónico." + } +} diff --git a/apps/web/messages/fr/account.json b/apps/web/messages/fr/account.json new file mode 100644 index 0000000..fc41a8b --- /dev/null +++ b/apps/web/messages/fr/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "jamais", + "on": "Activé", + "off": "Désactivé", + "cancel": "Annuler", + "working": "En cours…", + "typeToConfirm": "Saisis {word} pour confirmer", + "audience": { + "everyone": "Tout le monde", + "friends": "Amis", + "nobody": "Moi seul" + }, + "stepUp": { + "title": "Confirme que c’est toi", + "needed": "Ce changement demande une connexion récente. Elle reste valable 15 minutes.", + "sent": "Nous avons envoyé un code à 6 chiffres à {hint}.", + "passkey": "Utilise ta clé d’accès", + "sending": "Envoi…", + "sendCode": "Envoyer un code par e-mail" + } + }, + "shell": { + "home": "AnyPlay, accueil", + "signOut": "Se déconnecter", + "nav": "Compte", + "nav_overview": "Aperçu", + "nav_profile": "Profil", + "nav_friends": "Amis", + "nav_trophies": "Trophées", + "nav_security": "Sécurité", + "nav_notifications": "Notifications", + "nav_privacy": "Confidentialité", + "nav_connections": "Connexions", + "nav_data": "Tes données", + "unreachable": "Impossible de joindre AnyPlay. Vérifie ta connexion et réessaie.", + "loading": "Chargement" + }, + "level": { + "top": "Niveau maximal atteint", + "toNext": "{count, plural, one {# point} other {# points}} pour le niveau {level}", + "level": "Niveau {level}", + "points": "{count, plural, one {# point} other {# points}}", + "progress": "Progression du niveau", + "progressTo": "Progression vers le niveau {level}" + }, + "meta": { + "title": "Ton compte", + "template": "%s · Ton compte · AnyPlay" + }, + "state": { + "loading": "Chargement", + "tryAgain": "Réessayer" + }, + "overview": { + "label": "Ton compte", + "memberSince": "Membre depuis le {date}", + "appearingOffline": "Tu apparais hors ligne", + "deletion": "Ton compte sera supprimé le {date}. Garder mon compte", + "passkeys": "{count, plural, one {# clé d’accès} other {# clés d’accès}}", + "noPasskey": "Pas encore de clé d’accès", + "launchersBrowsers": "{launchers, plural, one {# launcher} other {# launchers}} · {browsers, plural, one {# navigateur} other {# navigateurs}}", + "trophiesAudience": "Trophées : {audience}", + "pictureAudience": "Photo de profil : {audience}", + "audience": { + "everyone": "Tout le monde", + "friends": "Amis", + "nobody": "Toi seul" + }, + "audienceInline": { + "everyone": "tout le monde", + "friends": "amis", + "nobody": "toi seul" + }, + "linked": "{count} liés", + "nothingLinked": "Rien de lié", + "connectionsDetail": "Discord, Steam", + "dataValue": "Exporter ou supprimer", + "dataDetail": "Tout ce que nous gardons sur toi", + "launcher": { + "title": "Récupère le launcher", + "description": "Connecte-toi au launcher avec ce compte, et tes amis, tes parties et tes trophées y sont." + } + }, + "profile": { + "lead": "Ce que tes amis voient quand ils ouvrent ton profil. Qui voit chaque partie, tu le choisis sur la page Confidentialité.", + "avatar": { + "title": "Photo", + "description": "PNG ou JPEG, jusqu’à 1 Mo. Elle s’affiche en rond, alors garde le visage au centre.", + "uploading": "Envoi…", + "change": "Changer la photo", + "upload": "Envoyer une photo", + "remove": "Retirer" + }, + "about": { + "title": "À propos", + "description": "Une ou deux lignes sur toi : à quoi tu joues, quand tu es là.", + "placeholder": "En coop quand je peux, en solo sinon.", + "saved": "Enregistré", + "saving": "Enregistrement…", + "save": "Enregistrer" + }, + "favourites": { + "title": "Jeux favoris", + "description": "Jusqu’à {max}, par ID de titre. Ce site affiche l’ID. Le launcher affiche le nom de chaque jeu à partir de ta propre copie.", + "empty": "Pas encore de jeux favoris. Ajoute un ID de titre ci-dessous.", + "listLabel": "Tes jeux favoris", + "remove": "Retirer {id}", + "fieldLabel": "ID de titre", + "full": "Tu as {max} jeux. Retire-en un pour en ajouter un autre.", + "add": "Ajouter un jeu", + "saved": "Enregistré", + "saving": "Enregistrement…", + "save": "Enregistrer", + "badId": "Utilise uniquement des lettres, des chiffres, des traits d’union et des tirets bas.", + "duplicate": "{id} est déjà dans la liste." + } + }, + "security": { + "lead": "Comment tu te connectes, et chaque launcher et navigateur connecté à ton compte.", + "email": { + "title": "E-mail", + "description": "L’adresse où arrivent tes codes de connexion. Pour la changer, il faut un code envoyé à la nouvelle adresse.", + "change": "Changer", + "invalid": "Saisis la nouvelle adresse, par exemple nom@exemple.com.", + "done": "Ton adresse de connexion est maintenant {email}.", + "newAddress": "Nouvelle adresse", + "sending": "Envoi…", + "send": "Envoyer le code", + "cancel": "Annuler", + "sentTo": "Nous avons envoyé un code à 6 chiffres à {email}." + }, + "passkeys": { + "title": "Clés d’accès", + "description": "Connecte-toi avec ton empreinte, ton visage ou le code PIN de l’appareil au lieu d’un code. Personne ne peut hameçonner une clé d’accès.", + "add": "Ajouter une clé d’accès", + "waiting": "En attente de ton appareil…", + "detail": "Ajoutée {added} · Dernière utilisation : {used}", + "remove": "Retirer", + "empty": "Pas encore de clé d’accès. Ajoutes-en une et te connecter ne prend qu’une touche, au lieu d’un code par e-mail.", + "full": "Tu as le maximum de clés d’accès qu’un compte peut garder ({max}). Retires-en une pour en ajouter une autre.", + "confirmTitle": "Retirer cette clé d’accès ?", + "confirmDescription": "Cet appareil ne pourra plus te connecter avec elle. Tu peux toujours te connecter avec un code par e-mail.", + "confirmLabel": "Retirer la clé d’accès", + "defaultName": "Clé d’accès sur {system}", + "thisDevice": "cet appareil" + }, + "devices": { + "title": "Launchers", + "description": "Chaque PC où le launcher est connecté à ton compte.", + "detail": "{platform} · Dernière utilisation : {used}", + "signOut": "Se déconnecter", + "empty": "Aucun launcher n’est encore connecté.", + "confirmTitle": "Déconnecter ce launcher ?", + "confirmDescription": "Il passe hors ligne jusqu’à ce que quelqu’un se reconnecte sur ce PC.", + "confirmLabel": "Déconnecter le launcher" + }, + "browsers": { + "title": "Navigateurs", + "description": "Chaque navigateur connecté à cette page de compte. Nous ne gardons rien d’autre à leur sujet que le moment où ils ont servi.", + "thisBrowser": "Ce navigateur", + "here": "Tu es ici", + "other": "Autre navigateur", + "detail": "Connecté {created} · Dernière utilisation : {used}", + "signOut": "Se déconnecter", + "confirmThisTitle": "Se déconnecter de ce navigateur ?", + "confirmOtherTitle": "Déconnecter ce navigateur ?", + "confirmThisDescription": "Tu reviens à l’accueil et tu te reconnectes la prochaine fois.", + "confirmOtherDescription": "Il doit se reconnecter pour ouvrir ton compte.", + "confirmLabel": "Se déconnecter" + }, + "everywhere": { + "title": "Se déconnecter partout", + "description": "Chaque launcher et chaque navigateur, celui-ci compris. Utilise-le si un appareil est perdu ou si quelqu’un d’autre a pu se connecter.", + "button": "Se déconnecter partout", + "confirmTitle": "Se déconnecter partout ?", + "confirmDescription": "Chaque launcher et chaque navigateur est déconnecté d’un coup, celui-ci compris.", + "confirmLabel": "Se déconnecter partout" + } + }, + "privacy": { + "lead": "Qui voit ton profil et ton activité. Ton nom est toujours visible, pour que tes amis puissent te trouver.", + "profile": { + "title": "Ton profil", + "description": "Les changements comptent tout de suite, dans le launcher aussi." + }, + "parts": { + "trophies": { + "title": "Trophées", + "detail": "Ton nombre de trophées, tes points et ton niveau" + }, + "playtime": { + "title": "Temps de jeu", + "detail": "Combien de temps tu as joué, par jeu" + }, + "about": { + "title": "À propos", + "detail": "La ligne de ton profil" + }, + "favourites": { + "title": "Jeux favoris", + "detail": "Jusqu’à trois jeux que tu épingles" + }, + "avatar": { + "title": "Photo", + "detail": "Ta photo de profil" + } + }, + "whoSees": "Qui voit : {part}", + "activity": { + "title": "Ton activité", + "description": "Ce que tu fais sur AnyPlay, tel que tes amis le voient dans leur fil.", + "row": "Activité", + "detail": "Tes amis la voient dans leur fil. Les inconnus ne voient aucun fil. Choisis « {nobody} » pour masquer ce qui s’y trouve et arrêter les nouveaux éléments." + }, + "appearOffline": { + "title": "Statut en ligne", + "description": "S’applique à chaque launcher et navigateur connecté à ton compte.", + "label": "Apparaître hors ligne", + "detail": "Tes amis te voient hors ligne. Tu peux quand même jouer, discuter et rejoindre." + }, + "publicProfile": { + "title": "Profil public", + "description": "Désactivé tant que tu ne l’actives pas. S’applique à chaque launcher et navigateur connecté à ton compte.", + "label": "Afficher un profil public", + "detail": "Toute personne qui a le lien voit ton niveau, tes trophées et les jeux auxquels tu as joué. Il n’indique jamais si tu es en ligne.", + "page": "Ta page : {url}" + }, + "playedWith": { + "title": "Joué récemment avec", + "description": "Si tu le désactives, aucune nouvelle personne n’est ajoutée. La liste reste jusqu’à ce que tu la vides.", + "label": "Retenir avec qui je joue", + "detail": "Garde pendant 30 jours les personnes avec qui tu as partagé une party ou une session de jeu, jusqu’à 50. Toi seul vois la liste.", + "clear": "Vider la liste", + "cleared": "La liste est vide.", + "confirmTitle": "Vider la liste ?", + "confirmDescription": "Tout le monde est retiré de ta liste des personnes avec qui tu as joué. La liste des autres ne change pas, et tu ne peux pas annuler." + } + }, + "connections": { + "lead": "Lie les comptes avec lesquels tu joues déjà, pour que tes amis te trouvent et voient où tu es aussi.", + "result": { + "linked": "{name} est lié à ton compte.", + "denied": "{name} n’a pas été lié : la demande a été refusée là-bas.", + "expired": "La liaison de {name} a pris trop de temps. Recommence.", + "fresh": "La liaison de {name} demande une connexion récente. Recommence et confirme que c’est bien toi.", + "taken": "Ce compte {name} est déjà lié à un autre compte AnyPlay.", + "exists": "Tu as déjà un compte {name} lié. Dissocie-le d’abord.", + "failed": "La liaison de {name} a échoué. Réessaie dans une minute." + }, + "whoSees": "Qui voit ton compte {name}", + "linkedAgo": "Lié {ago}", + "unlink": "Dissocier", + "link": "Lier {name}", + "opening": "Ouverture…", + "soon": "Bientôt", + "notLinked": "Non lié", + "arrivesSoon": "La liaison arrive bientôt", + "confirmTitle": "Dissocier {name} ?", + "confirmDescription": "Ton compte {name} n’apparaît plus sur ton profil. Tu peux le lier de nouveau quand tu veux.", + "confirmLabel": "Dissocier {name}" + }, + "data": { + "lead": "Tout ce qu’AnyPlay garde sur toi est à toi : prends-en une copie, ou supprime le compte.", + "export": { + "title": "Télécharger tes données", + "description": "Ton compte, ton profil, tes amis, tes trophées, ton temps de jeu et tes comptes liés, dans un seul fichier JSON.", + "preparing": "Préparation…", + "button": "Télécharger" + }, + "scheduled": { + "title": "Suppression programmée", + "description": "Ton compte et tout ce qu’il contient seront supprimés le {date}. D’ici là, tu peux le garder.", + "keep": "Garder mon compte" + }, + "delete": { + "title": "Supprimer ton compte", + "description": "Ton compte est supprimé au bout de 30 jours ; d’ici là, tu peux changer d’avis. Ensuite, il a disparu, avec tes amis, tes trophées et ton temps de jeu.", + "button": "Supprimer le compte" + }, + "confirm": { + "title": "Supprimer ton compte ?", + "description": "Il est supprimé dans 30 jours. Chaque launcher et chaque navigateur est déconnecté maintenant." + } + }, + "compare": { + "title": "Comparer les trophées", + "description": "Tes trophées à côté de ceux de {handle}.", + "descriptionShared": "Toi et {handle}, dans un jeu que vous avez tous les deux.", + "signInDescription": "Vois comment tes trophées se comparent à ceux de {handle}.", + "signIn": "Connecte-toi pour comparer les trophées", + "hidden": "Les trophées de {handle} ne sont pas disponibles pour la comparaison.", + "loading": "Chargement", + "loadingComparison": "Chargement de la comparaison", + "empty": "Toi et {handle}, vous n’avez pas encore de jeu en commun. Joue à un jeu auquel {handle} a joué et il apparaîtra ici.", + "game": "Jeu", + "gameNamed": "Jeu {title}", + "verdictNone": "Aucun de vous n’a encore obtenu de trophée dans ce jeu.", + "verdictLevel": "Vous êtes à égalité de points dans ce jeu.", + "verdictAhead": "Tu as {count, plural, one {# point} other {# points}} d’avance dans ce jeu.", + "verdictBehind": "{handle} a {count, plural, one {# point} other {# points}} d’avance dans ce jeu.", + "caption": "Trophées dans {titleId} : toi à côté de {handle}", + "stat": "Catégorie", + "you": "Toi", + "ahead": "(devant)", + "rowTrophies": "Trophées", + "rowPoints": "Points dans ce jeu", + "rowLevel": "Niveau global", + "grade": { + "platinum": "Platine", + "gold": "Or", + "silver": "Argent", + "bronze": "Bronze" + }, + "both": "Obtenus par les deux", + "onlyYou": "Toi seul", + "onlyThem": "{handle} seul" + }, + "publicProfile": { + "home": "AnyPlay, accueil", + "yourAccount": "Ton compte", + "signIn": "Se connecter", + "loading": "Chargement", + "missingTitle": "Aucun joueur nommé {handle}", + "missingBody": "Vérifie l’orthographe du pseudo ou demande au joueur de te le renvoyer.", + "goHome": "Aller sur AnyPlay", + "player": "Joueur", + "trophies": "Trophées", + "trophiesEarned": "{count, number} obtenus.", + "noTrophies": "{handle} n’a pas encore obtenu de trophée.", + "favourites": "Jeux favoris", + "favouritesNote": "Affichés par ID de titre.", + "game": "Jeu", + "connections": "Connexions", + "playtime": "Jeux joués", + "playtimeNote": "{count, plural, one {# jeu} other {# jeux}}, {total} au total.", + "nothing": "Il n’y a encore rien d’autre à montrer de {handle}.", + "openProfile": "Ouvrir le profil", + "openProfileOn": "sur {brand} (s’ouvre dans un nouvel onglet)", + "signInToAdd": "Connecte-toi pour ajouter {handle}", + "editProfile": "Modifier ton profil", + "blocked": "Bloqué", + "friends": "Amis", + "requestSent": "Demande envoyée", + "accepting": "Acceptation…", + "accept": "Accepter la demande", + "sending": "Envoi…", + "addFriend": "Ajouter un ami", + "addInAnyPlay": "Ajouter un ami dans AnyPlay", + "minutes": "{n} min", + "hours": "{n} h" + }, + "friends": { + "title": "Amis", + "lead": "Ajoute les personnes avec qui tu joues, réponds aux demandes et décide qui reste dehors.", + "add": { + "title": "Ajouter un ami", + "description": "Saisis le nom d’un joueur pour lui envoyer une demande.", + "handle": "Nom", + "hint": "Son nom, avec ou sans le @.", + "send": "Envoyer la demande", + "sending": "Envoi…", + "enterHandle": "Saisis le nom du joueur que tu veux ajouter.", + "nowFriends": "Toi et @{handle} êtes maintenant amis.", + "sent": "Demande envoyée à @{handle}. Vous serez amis dès qu’elle sera acceptée." + }, + "requests": { + "title": "Demandes", + "description": "Les joueurs qui veulent devenir tes amis et les demandes que tu as envoyées.", + "empty": "Aucune demande en cours. Quand quelqu’un t’ajoute, la demande apparaît ici.", + "received": "Reçues", + "sent": "Envoyées", + "receivedAgo": "Reçue {when}", + "sentAgo": "Envoyée {when}", + "decline": "Refuser", + "accept": "Accepter", + "withdraw": "Retirer" + }, + "list": { + "title": "Tes amis", + "description": "Vois qui est en ligne et à quoi la personne joue.", + "empty": "Pas encore d’amis. Ajoute quelqu’un par son nom ci-dessus ; la personne apparaît ici une fois la demande acceptée.", + "online": "En ligne", + "offline": "Hors ligne", + "playing": "En train de jouer à {title}", + "block": "Bloquer", + "remove": "Retirer" + }, + "blocked": { + "title": "Bloqués", + "description": "Les joueurs bloqués sont retirés de tes amis et de tes demandes.", + "empty": "Tu n’as bloqué personne. Bloque un joueur depuis ta liste d’amis quand tu en as besoin.", + "unblock": "Débloquer" + }, + "confirm": { + "blockTitle": "Bloquer @{handle} ?", + "removeTitle": "Retirer @{handle} de tes amis ?", + "blockText": "Vous n’êtes plus amis et toute demande en cours est supprimée. Tu peux débloquer cette personne à tout moment.", + "removeText": "Vous ne verrez plus quand l’autre est en ligne. Tu pourras envoyer une nouvelle demande plus tard.", + "blockLabel": "Bloquer le joueur", + "removeLabel": "Retirer l’ami" + } + }, + "trophies": { + "title": "Trophées", + "lead": "Ce que tu as obtenu dans tes jeux. AnyPlay garde des totaux par jeu et par niveau, jamais les noms ni les visuels des trophées.", + "totals": { + "title": "Totaux", + "description": "{total} obtenus dans {games, plural, one {# jeu} other {# jeux}}.", + "empty": "Pas encore de trophées. Joue à un jeu avec AnyPlay et ceux que tu obtiens sont comptés ici." + }, + "byGame": { + "title": "Par jeu", + "description": "Les jeux sont classés par ID de titre, les plus récents d’abord.", + "detail": "{earned, plural, one {# trophée} other {# trophées}} · dernier obtenu : {when}" + }, + "grades": { + "platinum": "Platine", + "gold": "Or", + "silver": "Argent", + "bronze": "Bronze" + } + }, + "notifications": { + "title": "Notifications", + "lead": "Reçois une notification sur cet appareil quand un ami t’envoie une demande, t’invite dans une party ou t’écrit.", + "device": { + "title": "Cet appareil", + "description": "Chaque navigateur ou téléphone se règle séparément. Active l’option là où tu veux être prévenu.", + "notEnabled": "Ce cloud n’envoie pas de notifications.", + "checking": "Vérification de cet appareil…", + "unsupported": "Ce navigateur ne peut pas recevoir de notifications. Utilise une version récente de Chrome, Edge, Firefox ou Safari.", + "needsInstall": "Sur iPhone et iPad, les notifications demandent qu’AnyPlay soit sur ton écran d’accueil. Touche Partager, choisis Sur l’écran d’accueil, puis ouvre AnyPlay depuis là.", + "blocked": "Les notifications sont bloquées pour ce site. Autorise-les dans les réglages du site de ton navigateur, puis reviens.", + "switch": "Notifier cet appareil", + "others": "{count, plural, one {Un autre appareil reçoit aussi les notifications. Désactive-les sur cet appareil.} other {# autres appareils reçoivent aussi les notifications. Désactive-les sur ces appareils.}}" + }, + "kinds": { + "title": "Sujets des notifications", + "description": "Pour cet appareil uniquement.", + "friend_request": { + "label": "Demandes d’ami", + "detail": "Quelqu’un veut devenir ton ami." + }, + "party_invite": { + "label": "Invitations à une party", + "detail": "Un ami t’invite dans une party." + }, + "message": { + "label": "Messages", + "detail": "Un ami t’écrit. Le message lui-même n’apparaît pas dans la notification." + } + }, + "errors": { + "timeout": "Le service de notifications de ton navigateur n’a pas répondu. Vérifie ta connexion et réessaie.", + "noKey": "Ce cloud n’a pas de clé pour envoyer des notifications.", + "lostSubscription": "Ce navigateur a perdu son abonnement. Désactive les notifications puis réactive-les.", + "notAllowed": "Ce navigateur n’a pas autorisé les notifications. Elles ne fonctionnent pas en navigation privée ; sinon, vérifie les réglages du site." + } + } +} diff --git a/apps/web/messages/fr/auth.json b/apps/web/messages/fr/auth.json new file mode 100644 index 0000000..b56c31d --- /dev/null +++ b/apps/web/messages/fr/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Se connecter", + "description": "Connecte-toi à AnyPlay avec une clé d’accès ou un code reçu par e-mail." + }, + "create": { + "title": "Crée ton compte", + "description": "Crée ton compte AnyPlay : une adresse e-mail et un nom." + }, + "link": { + "title": "Ouvre ton compte", + "description": "Ouvre ton compte AnyPlay dans ce navigateur, depuis le launcher." + }, + "join": { + "title": "Invitation", + "description": "Une invitation à jouer ensemble sur AnyPlay." + } + }, + "shell": { + "home": "AnyPlay, accueil", + "back": "Retour à l’accueil", + "promise": "Joue à tes jeux ensemble.", + "toastOnline": "mira est en ligne", + "toastParty": "kit a rejoint ta party", + "toastTrophy": "Trophée obtenu · Into the Ember", + "help": "Besoin d’aide ?", + "helpLink": "Demande sur Discord", + "legalNav": "Mentions légales", + "legalNotice": "Mentions légales", + "privacyNotice": "Avis de confidentialité", + "termsOfUse": "Conditions d’utilisation" + }, + "step": { + "loading": "Chargement", + "unreachable": "Impossible de joindre AnyPlay", + "tryAgain": "Réessayer", + "newTab": "(s’ouvre dans un nouvel onglet)", + "backToStart": "Retour à l’accueil", + "signIn": "Se connecter", + "createAccount": "Créer un compte", + "getApp": "Obtenir AnyPlay", + "continue": "Continuer" + }, + "fields": { + "code": "Code" + }, + "flow": { + "authorize": "AnyPlay sur {device} attend cette connexion." + }, + "email": { + "create": { + "title": "Crée ton compte", + "lead": "Commence par ton adresse e-mail. Nous t’envoyons un code pour la confirmer." + }, + "signIn": { + "title": "Se connecter", + "lead": "Utilise ta clé d’accès ou reçois un code par e-mail." + }, + "passkey": "Se connecter avec une clé d’accès", + "or": "ou", + "label": "Adresse e-mail", + "placeholder": "name@example.com", + "invalid": "Saisis ton adresse e-mail, par exemple name@example.com.", + "sending": "Envoi…", + "send": "Envoyer le code", + "haveAccount": "Tu as déjà un compte ? Se connecter", + "newHere": "Nouveau sur AnyPlay ? Créer un compte" + }, + "code": { + "title": "Regarde tes e-mails", + "lead": "Nous avons envoyé un code à 6 chiffres à {email}. Il est valable 10 minutes.", + "incomplete": "Saisis les 6 chiffres de l’e-mail.", + "checking": "Vérification…", + "resent": "Un nouveau code est en route.", + "noMail": "Pas d’e-mail ?", + "resendIn": "Envoyer un nouveau code dans {seconds} s", + "resend": "Envoyer un nouveau code" + }, + "details": { + "title": "Choisis un nom", + "lead": "Ton adresse {email} est confirmée. Les autres joueurs voient ton nom.", + "handle": "Nom", + "handleHint": "Utilise de 3 à 16 lettres, chiffres, tirets ou tirets bas. Commence par une lettre.", + "ageHint": "C’est-à-dire 16 ans ou plus.", + "age": "J’ai l’âge d’accepter les conditions par moi-même.", + "terms": "J’accepte les conditions (version {version}) et j’ai lu l’avis de confidentialité.", + "creating": "Création…", + "submit": "Créer un compte" + }, + "terms": { + "title": "Relis les conditions", + "lead": "Les conditions ont changé. Lis les conditions et accepte la version {version} pour continuer.", + "accept": "J’accepte les conditions (version {version}).", + "required": "Accepte les conditions pour continuer.", + "submit": "Accepter et continuer" + }, + "banned": { + "title": "Ce compte est banni", + "leadUntil": "Le bannissement prend fin le {date}.", + "leadForever": "Le bannissement ne prend pas fin de lui-même.", + "rule": "Règle", + "reason": "Motif", + "appeal": "Lire la décision et faire appel" + }, + "handoff": { + "title": "Connecté", + "lead": "Nous te ramenons vers AnyPlay. Si rien ne se passe, utilise le bouton.", + "open": "Ouvrir AnyPlay", + "note": "Tu peux fermer cet onglet dès qu’AnyPlay t’affiche comme connecté." + }, + "done": { + "title": "Tu es connecté", + "inviteLead": "Nous te ramenons à l’invitation.", + "welcome": "Bienvenue, {handle}. Tes amis, tes partys et tes trophées t’attendent dans le launcher.", + "account": "Aller à ton compte" + }, + "join": { + "expiredTitle": "Cette invitation a expiré", + "expiredLead": "Le lien a peut-être expiré, ou le joueur a arrêté de jouer. Demande-en un nouveau.", + "title": "{handle} t’a envoyé une invitation", + "leadSignedIn": "Ouvre AnyPlay sur ton PC pour voir l’invitation. C’est là que tu choisis de rejoindre ou non.", + "leadSignedOut": "Connecte-toi ou crée un compte d’abord. Tu reviens ensuite ici pour ouvrir l’invitation.", + "party": "Party", + "game": "Jeu", + "partyName": "Une party AnyPlay", + "gameFallback": "Un jeu", + "joined": "Ont rejoint", + "players": "{members} sur {max, plural, one {# joueur} other {# joueurs}}", + "open": "Ouvrir dans AnyPlay", + "noApp": "Pas encore d’AnyPlay sur ce PC ? Obtiens-le d’abord, puis rouvre cette page." + }, + "link": { + "expiredTitle": "Ce lien a expiré", + "expiredLead": "Rouvre ta page de compte depuis AnyPlay.", + "close": "Tu peux fermer cet onglet.", + "title": "Ouvre ton compte", + "lead": "AnyPlay sur ton PC demande d’ouvrir le compte de {handle} dans ce navigateur.", + "open": "Ouvrir le compte", + "opening": "Ouverture…", + "notYou": "Ce n’est pas toi ? Ferme l’onglet." + } +} diff --git a/apps/web/messages/fr/common.json b/apps/web/messages/fr/common.json new file mode 100644 index 0000000..214b826 --- /dev/null +++ b/apps/web/messages/fr/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Une erreur s’est produite. Réessaie.", + "network": "Impossible de joindre AnyPlay. Vérifie ta connexion et réessaie.", + "internal": "AnyPlay a rencontré un problème. Réessaie dans une minute.", + "unauthorized": "Ta session est terminée. Reconnecte-toi.", + "forbidden": "Tu ne peux pas faire cela avec ce compte.", + "banned": "Ce compte est banni.", + "not_found": "Cela n’existe plus.", + "limit": "Tu as atteint la limite. Supprime-en un d’abord.", + "too_large": "C’est trop volumineux pour être envoyé.", + "code_wrong": "Ce code est incorrect. Vérifie-le et réessaie.", + "code_expired": "Ce code a expiré. Demande-en un nouveau.", + "handle_taken": "Ce pseudo est déjà pris. Essaie-en un autre.", + "handle_invalid": "Utilise de 3 à 16 lettres, chiffres, tirets ou tirets bas. Commence par une lettre.", + "rate_limited": "Trop d’essais. Attends un instant et réessaie.", + "rate_limited_wait": "Trop d’essais. Attends {seconds, plural, one {# seconde} other {# secondes}} et réessaie.", + "passkey_unsupported": "Ce navigateur ne gère pas les clés d’accès. Utilise plutôt le code reçu par e-mail." + } +} diff --git a/apps/web/messages/pt/account.json b/apps/web/messages/pt/account.json new file mode 100644 index 0000000..8f80d5e --- /dev/null +++ b/apps/web/messages/pt/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "nunca", + "on": "Ativado", + "off": "Desativado", + "cancel": "Cancelar", + "working": "Processando…", + "typeToConfirm": "Digite {word} para confirmar", + "audience": { + "everyone": "Todos", + "friends": "Amigos", + "nobody": "Só eu" + }, + "stepUp": { + "title": "Confirme que é você", + "needed": "Esta alteração exige um login recente. Ele vale por 15 minutos.", + "sent": "Enviamos um código de 6 dígitos para {hint}.", + "passkey": "Use sua chave de acesso", + "sending": "Enviando…", + "sendCode": "Enviar um código por e-mail" + } + }, + "shell": { + "home": "AnyPlay, início", + "signOut": "Sair", + "nav": "Conta", + "nav_overview": "Visão geral", + "nav_profile": "Perfil", + "nav_friends": "Amigos", + "nav_trophies": "Troféus", + "nav_security": "Segurança", + "nav_notifications": "Notificações", + "nav_privacy": "Privacidade", + "nav_connections": "Conexões", + "nav_data": "Seus dados", + "unreachable": "Não foi possível acessar o AnyPlay. Verifique sua conexão e tente de novo.", + "loading": "Carregando" + }, + "level": { + "top": "Nível máximo alcançado", + "toNext": "{count, plural, one {# ponto} other {# pontos}} para o nível {level}", + "level": "Nível {level}", + "points": "{count, plural, one {# ponto} other {# pontos}}", + "progress": "Progresso de nível", + "progressTo": "Progresso para o nível {level}" + }, + "meta": { + "title": "Sua conta", + "template": "%s · Sua conta · AnyPlay" + }, + "state": { + "loading": "Carregando", + "tryAgain": "Tentar de novo" + }, + "overview": { + "label": "Sua conta", + "memberSince": "Membro desde {date}", + "appearingOffline": "Você aparece offline", + "deletion": "Sua conta será excluída em {date}. Manter minha conta", + "passkeys": "{count, plural, one {# chave de acesso} other {# chaves de acesso}}", + "noPasskey": "Nenhuma chave de acesso ainda", + "launchersBrowsers": "{launchers, plural, one {# launcher} other {# launchers}} · {browsers, plural, one {# navegador} other {# navegadores}}", + "trophiesAudience": "Troféus: {audience}", + "pictureAudience": "Foto de perfil: {audience}", + "audience": { + "everyone": "Todos", + "friends": "Amigos", + "nobody": "Só você" + }, + "audienceInline": { + "everyone": "todos", + "friends": "amigos", + "nobody": "só você" + }, + "linked": "{count} vinculadas", + "nothingLinked": "Nada vinculado", + "connectionsDetail": "Discord, Steam", + "dataValue": "Exportar ou excluir", + "dataDetail": "Tudo o que guardamos sobre você", + "launcher": { + "title": "Baixe o launcher", + "description": "Entre no launcher com esta conta, e seus amigos, parties e troféus estarão lá." + } + }, + "profile": { + "lead": "O que seus amigos veem quando abrem seu perfil. Quem vê cada parte é você quem decide, na página Privacidade.", + "avatar": { + "title": "Foto", + "description": "PNG ou JPEG, de até 1 MB. Ela aparece redonda, então deixe o rosto no meio.", + "uploading": "Enviando…", + "change": "Alterar foto", + "upload": "Enviar foto", + "remove": "Remover" + }, + "about": { + "title": "Sobre", + "description": "Uma ou duas linhas sobre você: o que você joga, quando está por aqui.", + "placeholder": "Em co-op quando dá, sozinho quando não dá.", + "saved": "Salvo", + "saving": "Salvando…", + "save": "Salvar" + }, + "favourites": { + "title": "Jogos favoritos", + "description": "Até {max}, por ID do título. Este site mostra o ID. O launcher mostra o nome de cada jogo a partir da sua própria cópia.", + "empty": "Nenhum jogo favorito ainda. Adicione um ID de título abaixo.", + "listLabel": "Seus jogos favoritos", + "remove": "Remover {id}", + "fieldLabel": "ID do título", + "full": "Você tem {max} jogos. Remova um para adicionar outro.", + "add": "Adicionar jogo", + "saved": "Salvo", + "saving": "Salvando…", + "save": "Salvar", + "badId": "Use apenas letras, números, hifens e sublinhados.", + "duplicate": "{id} já está na lista." + } + }, + "security": { + "lead": "Como você entra e todos os launchers e navegadores conectados à sua conta.", + "email": { + "title": "E-mail", + "description": "Para onde vão seus códigos de entrada. Para mudar, é preciso um código enviado ao novo endereço.", + "change": "Alterar", + "invalid": "Digite o novo endereço, como nome@exemplo.com.", + "done": "Seu endereço de entrada agora é {email}.", + "newAddress": "Novo endereço", + "sending": "Enviando…", + "send": "Enviar código", + "cancel": "Cancelar", + "sentTo": "Enviamos um código de 6 dígitos para {email}." + }, + "passkeys": { + "title": "Chaves de acesso", + "description": "Entre com sua digital, seu rosto ou o PIN do dispositivo, em vez de um código. Ninguém consegue aplicar phishing em uma chave de acesso.", + "add": "Adicionar uma chave de acesso", + "waiting": "Aguardando seu dispositivo…", + "detail": "Adicionada {added} · Último uso: {used}", + "remove": "Remover", + "empty": "Nenhuma chave de acesso ainda. Adicione uma e entrar vira um toque, em vez de um código por e-mail.", + "full": "Você tem o máximo de chaves de acesso que uma conta pode guardar ({max}). Remova uma para adicionar outra.", + "confirmTitle": "Remover esta chave de acesso?", + "confirmDescription": "Esse dispositivo não poderá mais entrar na sua conta com ela. Você sempre pode entrar com um código por e-mail.", + "confirmLabel": "Remover chave de acesso", + "defaultName": "Chave de acesso em {system}", + "thisDevice": "este dispositivo" + }, + "devices": { + "title": "Launchers", + "description": "Todo PC em que o launcher está conectado à sua conta.", + "detail": "{platform} · Último uso: {used}", + "signOut": "Sair", + "empty": "Nenhum launcher conectado ainda.", + "confirmTitle": "Desconectar este launcher?", + "confirmDescription": "Ele fica offline até alguém entrar de novo nesse PC.", + "confirmLabel": "Desconectar launcher" + }, + "browsers": { + "title": "Navegadores", + "description": "Todo navegador conectado a esta página da conta. Guardamos sobre eles apenas quando foram usados.", + "thisBrowser": "Este navegador", + "here": "Você está aqui", + "other": "Outro navegador", + "detail": "Entrou {created} · Último uso: {used}", + "signOut": "Sair", + "confirmThisTitle": "Sair deste navegador?", + "confirmOtherTitle": "Desconectar aquele navegador?", + "confirmThisDescription": "Você volta ao início e entra de novo na próxima vez.", + "confirmOtherDescription": "Ele precisa entrar de novo para abrir sua conta.", + "confirmLabel": "Sair" + }, + "everywhere": { + "title": "Sair em todos os lugares", + "description": "Todo launcher e todo navegador, este também. Use quando um dispositivo for perdido ou alguém mais puder ter entrado.", + "button": "Sair em todos os lugares", + "confirmTitle": "Sair em todos os lugares?", + "confirmDescription": "Todo launcher e navegador é desconectado de uma vez, este também.", + "confirmLabel": "Sair em todos os lugares" + } + }, + "privacy": { + "lead": "Quem vê seu perfil e sua atividade. Seu nome sempre fica visível, para que seus amigos encontrem você.", + "profile": { + "title": "Seu perfil", + "description": "As mudanças valem na hora, no launcher também." + }, + "parts": { + "trophies": { + "title": "Troféus", + "detail": "Sua contagem de troféus, seus pontos e seu nível" + }, + "playtime": { + "title": "Tempo de jogo", + "detail": "Quanto você jogou, por jogo" + }, + "about": { + "title": "Sobre", + "detail": "A linha do seu perfil" + }, + "favourites": { + "title": "Jogos favoritos", + "detail": "Até três jogos que você fixa" + }, + "avatar": { + "title": "Foto", + "detail": "Sua foto de perfil" + } + }, + "whoSees": "Quem vê: {part}", + "activity": { + "title": "Sua atividade", + "description": "O que você faz no AnyPlay, como seus amigos veem no feed deles.", + "row": "Atividade", + "detail": "Seus amigos veem no feed deles. Desconhecidos não veem feed. Escolha “{nobody}” para esconder o que está lá e parar novos itens." + }, + "appearOffline": { + "title": "Status online", + "description": "Vale para todo launcher e navegador conectado à sua conta.", + "label": "Aparecer offline", + "detail": "Seus amigos veem você offline. Você ainda pode jogar, conversar e entrar." + }, + "publicProfile": { + "title": "Perfil público", + "description": "Desativado até você ativar. Vale para todo launcher e navegador conectado à sua conta.", + "label": "Mostrar um perfil público", + "detail": "Qualquer pessoa com o link vê seu nível, seus troféus e os jogos que você jogou. Nunca mostra se você está online.", + "page": "Sua página: {url}" + }, + "playedWith": { + "title": "Jogadores recentes", + "description": "Se você desativar, ninguém novo é adicionado. A lista fica até você esvaziá-la.", + "label": "Lembrar com quem eu jogo", + "detail": "Guarda por 30 dias as pessoas com quem você dividiu uma party ou uma sessão de jogo, até 50. Só você vê a lista.", + "clear": "Esvaziar a lista", + "cleared": "A lista está vazia.", + "confirmTitle": "Esvaziar a lista?", + "confirmDescription": "Todos saem da sua lista de pessoas com quem você jogou. A lista de outras pessoas não muda, e você não pode desfazer isso." + } + }, + "connections": { + "lead": "Vincule as contas com que você já joga, para que seus amigos encontrem você e vejam onde mais você está.", + "result": { + "linked": "{name} está vinculado à sua conta.", + "denied": "{name} não foi vinculado: o pedido foi recusado lá.", + "expired": "Vincular {name} demorou demais. Comece de novo.", + "fresh": "Vincular {name} exige uma entrada recente. Comece de novo e confirme que é você.", + "taken": "Essa conta {name} já está vinculada a outra conta do AnyPlay.", + "exists": "Você já tem uma conta {name} vinculada. Desvincule-a primeiro.", + "failed": "Não foi possível vincular {name}. Tente de novo em um minuto." + }, + "whoSees": "Quem vê sua conta {name}", + "linkedAgo": "Vinculada {ago}", + "unlink": "Desvincular", + "link": "Vincular {name}", + "opening": "Abrindo…", + "soon": "Em breve", + "notLinked": "Não vinculada", + "arrivesSoon": "A vinculação chega em breve", + "confirmTitle": "Desvincular {name}?", + "confirmDescription": "Sua conta {name} deixa de aparecer no seu perfil. Você pode vinculá-la de novo quando quiser.", + "confirmLabel": "Desvincular {name}" + }, + "data": { + "lead": "Tudo o que o AnyPlay guarda sobre você é seu: leve uma cópia ou exclua a conta.", + "export": { + "title": "Baixar seus dados", + "description": "Sua conta, seu perfil, amigos, troféus, tempo de jogo e contas vinculadas, em um único arquivo JSON.", + "preparing": "Preparando…", + "button": "Baixar" + }, + "scheduled": { + "title": "Exclusão agendada", + "description": "Sua conta e tudo que há nela serão excluídos em {date}. Até lá, você pode mantê-la.", + "keep": "Manter minha conta" + }, + "delete": { + "title": "Excluir sua conta", + "description": "Sua conta é excluída após 30 dias; até lá, você pode mudar de ideia. Depois disso ela some, junto com seus amigos, troféus e tempo de jogo.", + "button": "Excluir conta" + }, + "confirm": { + "title": "Excluir sua conta?", + "description": "Ela é excluída em 30 dias. Todo launcher e navegador é desconectado agora." + } + }, + "compare": { + "title": "Comparar troféus", + "description": "Seus troféus ao lado dos de {handle}.", + "descriptionShared": "Você e {handle}, em um jogo que vocês dois têm.", + "signInDescription": "Veja como seus troféus se comparam aos de {handle}.", + "signIn": "Entre para comparar troféus", + "hidden": "Os troféus de {handle} não estão disponíveis para comparação.", + "loading": "Carregando", + "loadingComparison": "Carregando a comparação", + "empty": "Você e {handle} ainda não têm nenhum jogo em comum. Jogue um jogo que {handle} já jogou e ele aparece aqui.", + "game": "Jogo", + "gameNamed": "Jogo {title}", + "verdictNone": "Nenhum dos dois conquistou um troféu neste jogo.", + "verdictLevel": "Vocês estão empatados em pontos neste jogo.", + "verdictAhead": "Você está {count, plural, one {# ponto} other {# pontos}} à frente neste jogo.", + "verdictBehind": "{handle} está {count, plural, one {# ponto} other {# pontos}} à frente neste jogo.", + "caption": "Troféus em {titleId}: você ao lado de {handle}", + "stat": "Categoria", + "you": "Você", + "ahead": "(à frente)", + "rowTrophies": "Troféus", + "rowPoints": "Pontos neste jogo", + "rowLevel": "Nível geral", + "grade": { + "platinum": "Platina", + "gold": "Ouro", + "silver": "Prata", + "bronze": "Bronze" + }, + "both": "Conquistados por ambos", + "onlyYou": "Só você", + "onlyThem": "Só {handle}" + }, + "publicProfile": { + "home": "AnyPlay, início", + "yourAccount": "Sua conta", + "signIn": "Entrar", + "loading": "Carregando", + "missingTitle": "Nenhum jogador chamado {handle}", + "missingBody": "Verifique a grafia do nome ou peça ao jogador que o envie de novo.", + "goHome": "Ir para o AnyPlay", + "player": "Jogador", + "trophies": "Troféus", + "trophiesEarned": "{count, number} conquistados.", + "noTrophies": "{handle} ainda não conquistou nenhum troféu.", + "favourites": "Jogos favoritos", + "favouritesNote": "Exibidos pelo ID do título.", + "game": "Jogo", + "connections": "Conexões", + "playtime": "Jogos jogados", + "playtimeNote": "{count, plural, one {# jogo} other {# jogos}}, {total} no total.", + "nothing": "Ainda não há mais nada para mostrar de {handle}.", + "openProfile": "Abrir perfil", + "openProfileOn": "no {brand} (abre em uma nova guia)", + "signInToAdd": "Entre para adicionar {handle}", + "editProfile": "Editar seu perfil", + "blocked": "Bloqueado", + "friends": "Amigos", + "requestSent": "Solicitação enviada", + "accepting": "Aceitando…", + "accept": "Aceitar solicitação", + "sending": "Enviando…", + "addFriend": "Adicionar amigo", + "addInAnyPlay": "Adicionar amigo no AnyPlay", + "minutes": "{n} min", + "hours": "{n} h" + }, + "friends": { + "title": "Amigos", + "lead": "Adicione as pessoas com quem você joga, responda a pedidos e decida quem fica de fora.", + "add": { + "title": "Adicionar um amigo", + "description": "Digite o nome de um jogador para enviar um pedido a ele.", + "handle": "Nome", + "hint": "O nome dele, com ou sem o @.", + "send": "Enviar pedido", + "sending": "Enviando…", + "enterHandle": "Digite o nome do jogador que você quer adicionar.", + "nowFriends": "Você e @{handle} agora são amigos.", + "sent": "Pedido enviado para @{handle}. Vocês serão amigos assim que ele for aceito." + }, + "requests": { + "title": "Pedidos", + "description": "Jogadores que querem ser seus amigos e os pedidos que você enviou.", + "empty": "Nenhum pedido em aberto. Quando alguém adicionar você, o pedido aparece aqui.", + "received": "Recebidos", + "sent": "Enviados", + "receivedAgo": "Recebido {when}", + "sentAgo": "Enviado {when}", + "decline": "Recusar", + "accept": "Aceitar", + "withdraw": "Retirar" + }, + "list": { + "title": "Seus amigos", + "description": "Veja quem está online e o que está jogando.", + "empty": "Você ainda não tem amigos. Adicione alguém pelo nome acima; a pessoa aparece aqui quando aceitar.", + "online": "Online", + "offline": "Offline", + "playing": "Jogando {title}", + "block": "Bloquear", + "remove": "Remover" + }, + "blocked": { + "title": "Bloqueados", + "description": "Jogadores bloqueados são removidos dos seus amigos e pedidos.", + "empty": "Você não bloqueou ninguém. Bloqueie um jogador pela sua lista de amigos quando precisar.", + "unblock": "Desbloquear" + }, + "confirm": { + "blockTitle": "Bloquear @{handle}?", + "removeTitle": "Remover @{handle} dos amigos?", + "blockText": "Vocês deixam de ser amigos e qualquer pedido em aberto é descartado. Você pode desbloquear a qualquer momento.", + "removeText": "Vocês deixam de ver quando o outro está online. Você pode enviar um novo pedido depois.", + "blockLabel": "Bloquear jogador", + "removeLabel": "Remover amigo" + } + }, + "trophies": { + "title": "Troféus", + "lead": "O que você conquistou nos seus jogos. O AnyPlay guarda contagens por jogo e nível, nunca nomes ou imagens de troféus.", + "totals": { + "title": "Totais", + "description": "{total} conquistados em {games, plural, one {# jogo} other {# jogos}}.", + "empty": "Ainda não há troféus. Jogue algo pelo AnyPlay e os que você conquistar serão contados aqui." + }, + "byGame": { + "title": "Por jogo", + "description": "Os jogos são listados por ID do título, os mais recentes primeiro.", + "detail": "{earned, plural, one {# troféu} other {# troféus}} · última conquista: {when}" + }, + "grades": { + "platinum": "Platina", + "gold": "Ouro", + "silver": "Prata", + "bronze": "Bronze" + } + }, + "notifications": { + "title": "Notificações", + "lead": "Receba uma notificação neste dispositivo quando um amigo enviar um pedido, convidar você para uma party ou escrever para você.", + "device": { + "title": "Este dispositivo", + "description": "Cada navegador ou celular é configurado separadamente. Ative onde você quiser ser avisado.", + "notEnabled": "Esta nuvem não envia notificações.", + "checking": "Verificando este dispositivo…", + "unsupported": "Este navegador não pode receber notificações. Use uma versão atual do Chrome, Edge, Firefox ou Safari.", + "needsInstall": "No iPhone e no iPad, as notificações precisam do AnyPlay na sua tela de início. Toque em Compartilhar, escolha Adicionar à Tela de Início e abra o AnyPlay por lá.", + "blocked": "As notificações estão bloqueadas para este site. Permita-as nas configurações do site no seu navegador e volte.", + "switch": "Notificar este dispositivo", + "others": "{count, plural, one {Outro dispositivo também recebe notificações. Desative-as nesse dispositivo.} other {Outros # dispositivos também recebem notificações. Desative-as nesses dispositivos.}}" + }, + "kinds": { + "title": "Sobre o que avisar", + "description": "Apenas para este dispositivo.", + "friend_request": { + "label": "Pedidos de amizade", + "detail": "Alguém quer ser seu amigo." + }, + "party_invite": { + "label": "Convites para party", + "detail": "Um amigo convida você para uma party." + }, + "message": { + "label": "Mensagens", + "detail": "Um amigo escreve para você. A mensagem em si não aparece na notificação." + } + }, + "errors": { + "timeout": "O serviço de notificações do seu navegador não respondeu. Verifique sua conexão e tente de novo.", + "noKey": "Esta nuvem não tem uma chave para enviar notificações.", + "lostSubscription": "Este navegador perdeu a assinatura. Desative as notificações e ative de novo.", + "notAllowed": "Este navegador não permitiu notificações. Elas não funcionam em uma janela privada; caso contrário, verifique as configurações do site." + } + } +} diff --git a/apps/web/messages/pt/auth.json b/apps/web/messages/pt/auth.json new file mode 100644 index 0000000..3d5089f --- /dev/null +++ b/apps/web/messages/pt/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Entrar", + "description": "Entre no AnyPlay com uma chave de acesso ou um código por e-mail." + }, + "create": { + "title": "Crie sua conta", + "description": "Crie sua conta do AnyPlay: um e-mail e um nome." + }, + "link": { + "title": "Abra sua conta", + "description": "Abra sua conta do AnyPlay neste navegador, a partir do launcher." + }, + "join": { + "title": "Convite", + "description": "Um convite para jogar junto no AnyPlay." + } + }, + "shell": { + "home": "AnyPlay, início", + "back": "Voltar ao início", + "promise": "Jogue seus jogos juntos.", + "toastOnline": "mira está online", + "toastParty": "kit entrou na sua party", + "toastTrophy": "Troféu conquistado · Into the Ember", + "help": "Precisa de ajuda?", + "helpLink": "Pergunte no Discord", + "legalNav": "Legal", + "legalNotice": "Aviso legal", + "privacyNotice": "Aviso de privacidade", + "termsOfUse": "Termos de uso" + }, + "step": { + "loading": "Carregando", + "unreachable": "Não foi possível conectar ao AnyPlay", + "tryAgain": "Tentar de novo", + "newTab": "(abre em uma nova aba)", + "backToStart": "Voltar ao início", + "signIn": "Entrar", + "createAccount": "Criar conta", + "getApp": "Baixar o AnyPlay", + "continue": "Continuar" + }, + "fields": { + "code": "Código" + }, + "flow": { + "authorize": "O AnyPlay em {device} está esperando você entrar." + }, + "email": { + "create": { + "title": "Crie sua conta", + "lead": "Comece com seu e-mail. Enviamos um código para confirmá-lo." + }, + "signIn": { + "title": "Entrar", + "lead": "Use sua chave de acesso ou receba um código por e-mail." + }, + "passkey": "Entrar com uma chave de acesso", + "or": "ou", + "label": "E-mail", + "placeholder": "name@example.com", + "invalid": "Digite seu e-mail, por exemplo name@example.com.", + "sending": "Enviando…", + "send": "Enviar código", + "haveAccount": "Já tem uma conta? Entrar", + "newHere": "Novo no AnyPlay? Criar conta" + }, + "code": { + "title": "Confira seu e-mail", + "lead": "Enviamos um código de 6 dígitos para {email}. Ele vale por 10 minutos.", + "incomplete": "Digite os 6 dígitos do e-mail.", + "checking": "Verificando…", + "resent": "Um novo código está a caminho.", + "noMail": "Não recebeu o e-mail?", + "resendIn": "Enviar novo código em {seconds} s", + "resend": "Enviar novo código" + }, + "details": { + "title": "Escolha um nome", + "lead": "Seu endereço {email} foi confirmado. Os outros jogadores veem seu nome.", + "handle": "Nome", + "handleHint": "Use de 3 a 16 letras, dígitos, hífens ou sublinhados. Comece com uma letra.", + "ageHint": "Ou seja, 16 anos ou mais.", + "age": "Tenho idade suficiente para aceitar os termos por conta própria.", + "terms": "Aceito os termos (versão {version}) e li o aviso de privacidade.", + "creating": "Criando…", + "submit": "Criar conta" + }, + "terms": { + "title": "Revise os termos", + "lead": "Os termos mudaram. Leia os termos e aceite a versão {version} para continuar.", + "accept": "Aceito os termos (versão {version}).", + "required": "Aceite os termos para continuar.", + "submit": "Aceitar e continuar" + }, + "banned": { + "title": "Esta conta foi banida", + "leadUntil": "O banimento termina em {date}.", + "leadForever": "O banimento não termina por conta própria.", + "rule": "Regra", + "reason": "Motivo", + "appeal": "Ler a declaração e apelar" + }, + "handoff": { + "title": "Conectado", + "lead": "Estamos levando você de volta ao AnyPlay. Se nada acontecer, use o botão.", + "open": "Abrir o AnyPlay", + "note": "Você pode fechar esta aba quando o AnyPlay mostrar que você está conectado." + }, + "done": { + "title": "Você está conectado", + "inviteLead": "Estamos levando você de volta ao convite.", + "welcome": "Boas-vindas, {handle}. Seus amigos, partys e troféus estão esperando no launcher.", + "account": "Ir para sua conta" + }, + "join": { + "expiredTitle": "Este convite expirou", + "expiredLead": "O link pode ter expirado, ou o jogador parou de jogar. Peça um novo.", + "title": "{handle} convidou você", + "leadSignedIn": "Abra o AnyPlay no seu PC para ver o convite. Lá você decide se quer entrar.", + "leadSignedOut": "Entre ou crie uma conta primeiro. Depois você volta aqui para abrir o convite.", + "party": "Party", + "game": "Jogo", + "partyName": "Uma party do AnyPlay", + "gameFallback": "Um jogo", + "joined": "Entraram", + "players": "{members} de {max, plural, one {# jogador} other {# jogadores}}", + "open": "Abrir no AnyPlay", + "noApp": "Ainda não tem o AnyPlay neste PC? Baixe primeiro e abra esta página de novo." + }, + "link": { + "expiredTitle": "Este link expirou", + "expiredLead": "Abra a página da sua conta pelo AnyPlay de novo.", + "close": "Você pode fechar esta aba.", + "title": "Abra sua conta", + "lead": "O AnyPlay no seu PC pediu para abrir a conta de {handle} neste navegador.", + "open": "Abrir conta", + "opening": "Abrindo…", + "notYou": "Não foi você? Feche a aba." + } +} diff --git a/apps/web/messages/pt/common.json b/apps/web/messages/pt/common.json new file mode 100644 index 0000000..d4ab3b6 --- /dev/null +++ b/apps/web/messages/pt/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Algo deu errado. Tente de novo.", + "network": "Não foi possível conectar ao AnyPlay. Verifique sua conexão e tente de novo.", + "internal": "O AnyPlay teve um problema. Tente de novo em um minuto.", + "unauthorized": "Sua sessão terminou. Entre de novo.", + "forbidden": "Você não pode fazer isso com esta conta.", + "banned": "Esta conta está banida.", + "not_found": "Isso não existe mais.", + "limit": "Você atingiu o limite. Remova um primeiro.", + "too_large": "Isso é grande demais para enviar.", + "code_wrong": "Esse código está errado. Confira e tente de novo.", + "code_expired": "Esse código expirou. Peça um novo.", + "handle_taken": "Esse nome de usuário já está em uso. Tente outro.", + "handle_invalid": "Use de 3 a 16 letras, dígitos, hífens ou sublinhados. Comece com uma letra.", + "rate_limited": "Muitas tentativas. Aguarde um momento e tente de novo.", + "rate_limited_wait": "Muitas tentativas. Aguarde {seconds, plural, one {# segundo} other {# segundos}} e tente de novo.", + "passkey_unsupported": "Este navegador não oferece suporte a passkeys. Use o código por e-mail." + } +} diff --git a/apps/web/messages/tr/account.json b/apps/web/messages/tr/account.json new file mode 100644 index 0000000..880d855 --- /dev/null +++ b/apps/web/messages/tr/account.json @@ -0,0 +1,461 @@ +{ + "ui": { + "never": "hiç", + "on": "Açık", + "off": "Kapalı", + "cancel": "Vazgeç", + "working": "İşleniyor…", + "typeToConfirm": "Onaylamak için {word} yaz", + "audience": { + "everyone": "Herkes", + "friends": "Arkadaşlar", + "nobody": "Sadece ben" + }, + "stepUp": { + "title": "Sen olduğunu doğrula", + "needed": "Bu değişiklik için yakın zamanda giriş yapmış olman gerekir. 15 dakika geçerli kalır.", + "sent": "{hint} adresine 6 haneli bir kod gönderdik.", + "passkey": "Geçiş anahtarını kullan", + "sending": "Gönderiliyor…", + "sendCode": "E-postayla kod gönder" + } + }, + "shell": { + "home": "AnyPlay, ana sayfa", + "signOut": "Çıkış yap", + "nav": "Hesap", + "nav_overview": "Genel bakış", + "nav_profile": "Profil", + "nav_friends": "Arkadaşlar", + "nav_trophies": "Kupalar", + "nav_security": "Güvenlik", + "nav_notifications": "Bildirimler", + "nav_privacy": "Gizlilik", + "nav_connections": "Bağlantılar", + "nav_data": "Verilerin", + "unreachable": "AnyPlay’e ulaşılamıyor. Bağlantını kontrol et ve tekrar dene.", + "loading": "Yükleniyor" + }, + "level": { + "top": "En üst seviyeye ulaşıldı", + "toNext": "{level}. seviyeye {count, plural, one {# puan} other {# puan}} kaldı", + "level": "Seviye {level}", + "points": "{count, plural, one {# puan} other {# puan}}", + "progress": "Seviye ilerlemesi", + "progressTo": "Seviye {level} ilerlemesi" + }, + "meta": { + "title": "Hesabın", + "template": "%s · Hesabın · AnyPlay" + }, + "state": { + "loading": "Yükleniyor", + "tryAgain": "Tekrar dene" + }, + "overview": { + "label": "Hesabın", + "memberSince": "Üyelik başlangıcı: {date}", + "appearingOffline": "Çevrimdışı görünüyorsun", + "deletion": "Hesabın {date} tarihinde silinecek. Hesabımı koru", + "passkeys": "{count, plural, one {# geçiş anahtarı} other {# geçiş anahtarı}}", + "noPasskey": "Henüz geçiş anahtarı yok", + "launchersBrowsers": "{launchers, plural, one {# Launcher} other {# Launcher}} · {browsers, plural, one {# tarayıcı} other {# tarayıcı}}", + "trophiesAudience": "Kupalar: {audience}", + "pictureAudience": "Profil resmi: {audience}", + "audience": { + "everyone": "Herkes", + "friends": "Arkadaşlar", + "nobody": "Yalnızca sen" + }, + "audienceInline": { + "everyone": "herkes", + "friends": "arkadaşlar", + "nobody": "yalnızca sen" + }, + "linked": "{count} bağlı", + "nothingLinked": "Hiçbir şey bağlı değil", + "connectionsDetail": "Discord, Steam", + "dataValue": "Dışa aktar ya da sil", + "dataDetail": "Hakkında tuttuğumuz her şey", + "launcher": { + "title": "Launcher’ı indir", + "description": "Bu hesapla Launcher’a giriş yap; arkadaşların, partilerin ve kupaların orada olur." + } + }, + "profile": { + "lead": "Profilini açan arkadaşlarının gördükleri. Her bölümü kimin göreceğini Gizlilik sayfasından sen seçersin.", + "avatar": { + "title": "Resim", + "description": "PNG ya da JPEG, en fazla 1 MB. Yuvarlak görünür; bu yüzden yüzünü ortada tut.", + "uploading": "Yükleniyor…", + "change": "Resmi değiştir", + "upload": "Resim yükle", + "remove": "Kaldır" + }, + "about": { + "title": "Hakkında", + "description": "Kendin hakkında bir iki satır: ne oynadığın, ne zaman çevrimiçi olduğun.", + "placeholder": "Olursa ortak oyun, olmazsa tek başıma.", + "saved": "Kaydedildi", + "saving": "Kaydediliyor…", + "save": "Kaydet" + }, + "favourites": { + "title": "Favori oyunlar", + "description": "En fazla {max}, oyun kimliğiyle. Bu site kimliği gösterir. Launcher her oyunun adını kendi kopyandan gösterir.", + "empty": "Henüz favori oyun yok. Aşağıdan bir oyun kimliği ekle.", + "listLabel": "Favori oyunların", + "remove": "{id} kaldır", + "fieldLabel": "Oyun kimliği", + "full": "{max} oyunun var. Yenisini eklemek için birini kaldır.", + "add": "Oyun ekle", + "saved": "Kaydedildi", + "saving": "Kaydediliyor…", + "save": "Kaydet", + "badId": "Yalnızca harf, rakam, tire ve alt çizgi kullan.", + "duplicate": "{id} zaten listede." + } + }, + "security": { + "lead": "Nasıl giriş yaptığın ve hesabına giriş yapmış her Launcher ile tarayıcı.", + "email": { + "title": "E-posta", + "description": "Giriş kodlarının gittiği adres. Değiştirmek için yeni adrese gönderilen bir kod gerekir.", + "change": "Değiştir", + "invalid": "Yeni adresi gir, örneğin ad@ornek.com.", + "done": "Giriş adresin artık {email}.", + "newAddress": "Yeni adres", + "sending": "Gönderiliyor…", + "send": "Kod gönder", + "cancel": "Vazgeç", + "sentTo": "{email} adresine 6 haneli bir kod gönderdik." + }, + "passkeys": { + "title": "Geçiş anahtarları", + "description": "Kod yerine parmak izin, yüzün ya da cihaz PIN’inle giriş yap. Geçiş anahtarını kimse oltalayamaz.", + "add": "Geçiş anahtarı ekle", + "waiting": "Cihazın bekleniyor…", + "detail": "Eklenme: {added} · Son kullanım: {used}", + "remove": "Kaldır", + "empty": "Henüz geçiş anahtarı yok. Bir tane ekle; e-posta kodu yerine tek dokunuşla giriş yaparsın.", + "full": "Bir hesabın tutabileceği en fazla geçiş anahtarına sahipsin ({max}). Yenisini eklemek için birini kaldır.", + "confirmTitle": "Bu geçiş anahtarı kaldırılsın mı?", + "confirmDescription": "Bu cihaz artık onunla giriş yapamaz. E-postayla gelen kodla her zaman giriş yapabilirsin.", + "confirmLabel": "Geçiş anahtarını kaldır", + "defaultName": "{system} geçiş anahtarı", + "thisDevice": "bu cihaz" + }, + "devices": { + "title": "Launcher’lar", + "description": "Launcher’ın hesabınla giriş yaptığı tüm bilgisayarlar.", + "detail": "{platform} · Son kullanım: {used}", + "signOut": "Çıkış yap", + "empty": "Henüz hiçbir Launcher’da giriş yapılmadı.", + "confirmTitle": "Bu Launcher’dan çıkış yapılsın mı?", + "confirmDescription": "O bilgisayarda biri tekrar giriş yapana kadar çevrimdışı olur.", + "confirmLabel": "Launcher’dan çıkış yap" + }, + "browsers": { + "title": "Tarayıcılar", + "description": "Bu hesap sayfasına giriş yapmış her tarayıcı. Onlar hakkında, ne zaman kullanıldıklarından fazlasını tutmuyoruz.", + "thisBrowser": "Bu tarayıcı", + "here": "Şu an buradasın", + "other": "Başka tarayıcı", + "detail": "Giriş: {created} · Son kullanım: {used}", + "signOut": "Çıkış yap", + "confirmThisTitle": "Bu tarayıcıdan çıkış yapılsın mı?", + "confirmOtherTitle": "O tarayıcıdan çıkış yapılsın mı?", + "confirmThisDescription": "Başa dönersin ve bir sonraki sefer yeniden giriş yaparsın.", + "confirmOtherDescription": "Hesabını açmak için yeniden giriş yapması gerekir.", + "confirmLabel": "Çıkış yap" + }, + "everywhere": { + "title": "Her yerden çıkış yap", + "description": "Bu dahil her Launcher ve her tarayıcı. Bir cihaz kaybolduysa ya da başkası giriş yapmış olabilirse kullan.", + "button": "Her yerden çıkış yap", + "confirmTitle": "Her yerden çıkış yapılsın mı?", + "confirmDescription": "Bu dahil her Launcher ve tarayıcıdan aynı anda çıkış yapılır.", + "confirmLabel": "Her yerden çıkış yap" + } + }, + "privacy": { + "lead": "Profilini ve etkinliğini kimin gördüğü. Kullanıcı adın her zaman görünür; böylece arkadaşların seni bulabilir.", + "profile": { + "title": "Profilin", + "description": "Değişiklikler hemen geçerli olur, Launcher’da da." + }, + "parts": { + "trophies": { + "title": "Kupalar", + "detail": "Kupa sayıların, puanların ve seviyen" + }, + "playtime": { + "title": "Oynama süresi", + "detail": "Her oyunu ne kadar oynadığın" + }, + "about": { + "title": "Hakkında", + "detail": "Profilindeki satır" + }, + "favourites": { + "title": "Favori oyunlar", + "detail": "Sabitlediğin en fazla üç oyun" + }, + "avatar": { + "title": "Resim", + "detail": "Profil resmin" + } + }, + "whoSees": "Kim görür: {part}", + "activity": { + "title": "Etkinliğin", + "description": "AnyPlay’de yaptıkların, arkadaşlarının akışında göründüğü gibi.", + "row": "Etkinlik", + "detail": "Arkadaşların akışlarında görür. Yabancılar akış görmez. Oradakini gizlemek ve yeni öğeleri durdurmak için “{nobody}” seç." + }, + "appearOffline": { + "title": "Çevrimiçi durumu", + "description": "Hesabına giriş yapmış her Launcher ve tarayıcı için geçerlidir.", + "label": "Çevrimdışı görün", + "detail": "Arkadaşların seni çevrimdışı görür. Yine de oynayabilir, sohbet edebilir ve katılabilirsin." + }, + "publicProfile": { + "title": "Herkese açık profil", + "description": "Sen açana kadar kapalıdır. Hesabına giriş yapmış her Launcher ve tarayıcı için geçerlidir.", + "label": "Herkese açık profil göster", + "detail": "Bağlantıya sahip herkes seviyeni, kupalarını ve oynadığın oyunları görür. Çevrimiçi olup olmadığını asla göstermez.", + "page": "Sayfan: {url}" + }, + "playedWith": { + "title": "Son oynadıkların", + "description": "Kapatırsan yeni kimse eklenmez. Liste, sen temizleyene kadar kalır.", + "label": "Oynadığım kişileri hatırla", + "detail": "Birlikte parti ya da oyun oturumu paylaştığın kişileri 30 gün boyunca, en fazla 50 kişi tutar. Listeyi yalnızca sen görürsün.", + "clear": "Listeyi temizle", + "cleared": "Liste boş.", + "confirmTitle": "Liste temizlensin mi?", + "confirmDescription": "Birlikte oynadıklarının listesindeki herkes kaldırılır. Başkalarının listesi değişmez ve bunu geri alamazsın." + } + }, + "connections": { + "lead": "Zaten oynadığın hesapları bağla; arkadaşların seni bulsun ve başka nerede olduğunu görsün.", + "result": { + "linked": "{name} hesabına bağlandı.", + "denied": "{name} bağlanmadı: istek orada reddedildi.", + "expired": "{name} bağlama çok uzun sürdü. Baştan başla.", + "fresh": "{name} bağlamak için yakın zamanda giriş yapmış olman gerekir. Baştan başla ve senin olduğunu doğrula.", + "taken": "Bu {name} hesabı başka bir AnyPlay hesabına zaten bağlı.", + "exists": "Zaten bir {name} hesabın bağlı. Önce onun bağlantısını kaldır.", + "failed": "{name} bağlanamadı. Bir dakika sonra tekrar dene." + }, + "whoSees": "{name} hesabını kim görür", + "linkedAgo": "Bağlanma: {ago}", + "unlink": "Bağlantıyı kaldır", + "link": "{name} bağla", + "opening": "Açılıyor…", + "soon": "Yakında", + "notLinked": "Bağlı değil", + "arrivesSoon": "Bağlama yakında geliyor", + "confirmTitle": "{name} bağlantısı kaldırılsın mı?", + "confirmDescription": "{name} hesabın artık profilinde görünmez. İstediğin zaman yeniden bağlayabilirsin.", + "confirmLabel": "{name} bağlantısını kaldır" + }, + "data": { + "lead": "AnyPlay’in hakkında tuttuğu her şey senindir: bir kopyasını al ya da hesabı sil.", + "export": { + "title": "Verilerini indir", + "description": "Hesabın, profilin, arkadaşların, kupaların, oynama sürelerin ve bağlı hesapların tek bir JSON dosyası olarak.", + "preparing": "Hazırlanıyor…", + "button": "İndir" + }, + "scheduled": { + "title": "Silme planlandı", + "description": "Hesabın ve içindeki her şey {date} tarihinde silinecek. O zamana kadar hesabını koruyabilirsin.", + "keep": "Hesabımı koru" + }, + "delete": { + "title": "Hesabını sil", + "description": "Hesabın 30 gün sonra silinir; o zamana kadar fikrini değiştirebilirsin. Sonrasında arkadaşların, kupaların ve oynama sürelerinle birlikte tamamen gider.", + "button": "Hesabı sil" + }, + "confirm": { + "title": "Hesabın silinsin mi?", + "description": "Hesap 30 gün içinde silinir. Her Launcher ve tarayıcıdan şimdi çıkış yapılır." + } + }, + "compare": { + "title": "Kupaları karşılaştır", + "description": "Kupaların, {handle} ile yan yana.", + "descriptionShared": "Sen ve {handle}, ikinizin de sahip olduğu bir oyunda.", + "signInDescription": "Kupalarının {handle} ile nasıl kıyaslandığını gör.", + "signIn": "Kupaları karşılaştırmak için giriş yap", + "hidden": "{handle} kullanıcısının kupaları karşılaştırmaya uygun değil.", + "loading": "Yükleniyor", + "loadingComparison": "Karşılaştırma yükleniyor", + "empty": "Sen ve {handle} için henüz ortak bir oyun yok. {handle} oynadığı bir oyunu oynarsan burada görünür.", + "game": "Oyun", + "gameNamed": "Oyun {title}", + "verdictNone": "İkinizin de bu oyunda kazandığı bir kupa yok.", + "verdictLevel": "Bu oyunda puanlarınız eşit.", + "verdictAhead": "Bu oyunda {count, number} puan öndesin.", + "verdictBehind": "Bu oyunda {handle}, {count, number} puan önde.", + "caption": "{titleId} oyunundaki kupalar: sen ve {handle}", + "stat": "Kategori", + "you": "Sen", + "ahead": "(önde)", + "rowTrophies": "Kupalar", + "rowPoints": "Bu oyunda puan", + "rowLevel": "Genel seviye", + "grade": { + "platinum": "Platin", + "gold": "Altın", + "silver": "Gümüş", + "bronze": "Bronz" + }, + "both": "İkinizin de kazandığı", + "onlyYou": "Sadece sen", + "onlyThem": "Sadece {handle}" + }, + "publicProfile": { + "home": "AnyPlay, ana sayfa", + "yourAccount": "Hesabın", + "signIn": "Giriş yap", + "loading": "Yükleniyor", + "missingTitle": "{handle} adında bir oyuncu yok", + "missingBody": "Kullanıcı adının yazımını kontrol et ya da oyuncudan tekrar göndermesini iste.", + "goHome": "AnyPlay’e git", + "player": "Oyuncu", + "trophies": "Kupalar", + "trophiesEarned": "{count, number} kazanıldı.", + "noTrophies": "{handle} henüz kupa kazanmadı.", + "favourites": "Favori oyunlar", + "favouritesNote": "Başlık kimliğiyle gösterilir.", + "game": "Oyun", + "connections": "Bağlantılar", + "playtime": "Oynanan oyunlar", + "playtimeNote": "{count, number} oyun, toplam {total}.", + "nothing": "{handle} hakkında gösterilecek başka bir şey yok.", + "openProfile": "Profili aç", + "openProfileOn": "{brand} üzerinde (yeni sekmede açılır)", + "signInToAdd": "{handle} kullanıcısını eklemek için giriş yap", + "editProfile": "Profilini düzenle", + "blocked": "Engellendi", + "friends": "Arkadaşlar", + "requestSent": "İstek gönderildi", + "accepting": "Kabul ediliyor…", + "accept": "İsteği kabul et", + "sending": "Gönderiliyor…", + "addFriend": "Arkadaş ekle", + "addInAnyPlay": "AnyPlay’de arkadaş ekle", + "minutes": "{n} dk", + "hours": "{n} sa" + }, + "friends": { + "title": "Arkadaşlar", + "lead": "Birlikte oynadığın kişileri ekle, isteklere yanıt ver ve kimin dışarıda kalacağına karar ver.", + "add": { + "title": "Arkadaş ekle", + "description": "İstek göndermek için bir oyuncunun adını gir.", + "handle": "Ad", + "hint": "Oyuncunun adı, @ ile ya da @ olmadan.", + "send": "İstek gönder", + "sending": "Gönderiliyor…", + "enterHandle": "Eklemek istediğin oyuncunun adını gir.", + "nowFriends": "Sen ve @{handle} artık arkadaşsınız.", + "sent": "İstek @{handle} kişisine gönderildi. Kabul ettiğinde arkadaş olursunuz." + }, + "requests": { + "title": "İstekler", + "description": "Arkadaşın olmak isteyen oyuncular ve gönderdiğin istekler.", + "empty": "Açık istek yok. Biri seni eklediğinde istek burada görünür.", + "received": "Gelen", + "sent": "Giden", + "receivedAgo": "Alındı: {when}", + "sentAgo": "Gönderildi: {when}", + "decline": "Reddet", + "accept": "Kabul et", + "withdraw": "Geri çek" + }, + "list": { + "title": "Arkadaşların", + "description": "Kimlerin çevrimiçi olduğunu ve ne oynadıklarını gör.", + "empty": "Henüz arkadaşın yok. Yukarıdan birini adıyla ekle; kabul ettiğinde burada görünür.", + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "playing": "{title} oynuyor", + "block": "Engelle", + "remove": "Çıkar" + }, + "blocked": { + "title": "Engellenenler", + "description": "Engellenen oyuncular arkadaşlarından ve isteklerinden çıkarılır.", + "empty": "Kimseyi engellemedin. Gerektiğinde arkadaş listenden bir oyuncuyu engelleyebilirsin.", + "unblock": "Engeli kaldır" + }, + "confirm": { + "blockTitle": "@{handle} engellensin mi?", + "removeTitle": "@{handle} arkadaşlıktan çıkarılsın mı?", + "blockText": "Artık arkadaş olmazsınız ve açık istekler silinir. Engeli istediğin zaman kaldırabilirsin.", + "removeText": "Birbirinizin çevrimiçi olup olmadığını görmezsiniz. Sonra yeni istek gönderebilirsin.", + "blockLabel": "Oyuncuyu engelle", + "removeLabel": "Arkadaşı çıkar" + } + }, + "trophies": { + "title": "Kupalar", + "lead": "Oyunlarında kazandıkların. AnyPlay oyun ve derece başına sayıları tutar, kupa adlarını ya da görsellerini asla tutmaz.", + "totals": { + "title": "Toplam", + "description": "{games} oyunda toplam {total} kupa kazanıldı.", + "empty": "Henüz kupa yok. AnyPlay üzerinden bir oyun oyna, kazandıkların burada sayılır." + }, + "byGame": { + "title": "Oyuna göre", + "description": "Oyunlar başlık kimliğine göre, en yenisi başta listelenir.", + "detail": "{earned} kupa · son kazanılan: {when}" + }, + "grades": { + "platinum": "Platin", + "gold": "Altın", + "silver": "Gümüş", + "bronze": "Bronz" + } + }, + "notifications": { + "title": "Bildirimler", + "lead": "Bir arkadaşın seni arkadaş olarak eklemek istediğinde, seni bir partiye davet ettiğinde ya da sana yazdığında bu cihazda bildirim al.", + "device": { + "title": "Bu cihaz", + "description": "Her tarayıcı ya da telefon ayrı ayarlanır. Bildirim almak istediğin yerde bunu aç.", + "notEnabled": "Bu bulut bildirim göndermiyor.", + "checking": "Bu cihaz kontrol ediliyor…", + "unsupported": "Bu tarayıcı bildirim alamıyor. Güncel bir Chrome, Edge, Firefox ya da Safari kullan.", + "needsInstall": "iPhone ve iPad’de bildirimler için AnyPlay’in Ana Ekran’da olması gerekir. Paylaş’a dokun, Ana Ekrana Ekle’yi seç, sonra AnyPlay’i oradan aç.", + "blocked": "Bu site için bildirimler engellenmiş. Tarayıcının site ayarlarından izin ver, sonra geri dön.", + "switch": "Bu cihaza bildirim gönder", + "others": "{count, plural, one {Başka bir cihaz da bildirim alıyor. Bildirimleri o cihazda kapat.} other {Başka # cihaz da bildirim alıyor. Bildirimleri o cihazlarda kapat.}}" + }, + "kinds": { + "title": "Hangi bildirimler gelsin", + "description": "Yalnızca bu cihaz için.", + "friend_request": { + "label": "Arkadaşlık istekleri", + "detail": "Biri seninle arkadaş olmak istiyor." + }, + "party_invite": { + "label": "Parti davetleri", + "detail": "Bir arkadaşın seni bir partiye davet ediyor." + }, + "message": { + "label": "Mesajlar", + "detail": "Bir arkadaşın sana yazıyor. Mesajın kendisi bildirimde görünmez." + } + }, + "errors": { + "timeout": "Tarayıcının bildirim hizmeti yanıt vermedi. Bağlantını kontrol edip tekrar dene.", + "noKey": "Bu bulutta bildirim göndermek için anahtar yok.", + "lostSubscription": "Bu tarayıcı aboneliğini kaybetti. Bildirimleri kapatıp yeniden aç.", + "notAllowed": "Bu tarayıcı bildirimlere izin vermedi. Gizli pencerede çalışmazlar; değilse site ayarlarına bak." + } + } +} diff --git a/apps/web/messages/tr/auth.json b/apps/web/messages/tr/auth.json new file mode 100644 index 0000000..bdcb551 --- /dev/null +++ b/apps/web/messages/tr/auth.json @@ -0,0 +1,143 @@ +{ + "meta": { + "signIn": { + "title": "Giriş yap", + "description": "AnyPlay’e geçiş anahtarıyla ya da e-postayla gelen kodla giriş yap." + }, + "create": { + "title": "Hesabını oluştur", + "description": "AnyPlay hesabını oluştur: bir e-posta adresi ve bir ad yeterli." + }, + "link": { + "title": "Hesabını aç", + "description": "AnyPlay hesabını başlatıcıdan bu tarayıcıda aç." + }, + "join": { + "title": "Davet", + "description": "AnyPlay’de birlikte oynamak için bir davet." + } + }, + "shell": { + "home": "AnyPlay, ana sayfa", + "back": "Başa dön", + "promise": "Oyunlarını birlikte oyna.", + "toastOnline": "mira çevrimiçi", + "toastParty": "kit partine katıldı", + "toastTrophy": "Kupa kazanıldı · Into the Ember", + "help": "Yardım mı lazım?", + "helpLink": "Discord’da sor", + "legalNav": "Yasal", + "legalNotice": "Yasal bildirim", + "privacyNotice": "Gizlilik bildirimi", + "termsOfUse": "Kullanım koşulları" + }, + "step": { + "loading": "Yükleniyor", + "unreachable": "AnyPlay’e ulaşılamıyor", + "tryAgain": "Tekrar dene", + "newTab": "(yeni sekmede açılır)", + "backToStart": "Başa dön", + "signIn": "Giriş yap", + "createAccount": "Hesap oluştur", + "getApp": "AnyPlay’i indir", + "continue": "Devam" + }, + "fields": { + "code": "Kod" + }, + "flow": { + "authorize": "{device} cihazındaki AnyPlay bu girişi bekliyor." + }, + "email": { + "create": { + "title": "Hesabını oluştur", + "lead": "E-posta adresinle başla. Onaylamak için bir kod gönderiyoruz." + }, + "signIn": { + "title": "Giriş yap", + "lead": "Geçiş anahtarını kullan ya da e-postayla bir kod al." + }, + "passkey": "Geçiş anahtarıyla giriş yap", + "or": "ya da", + "label": "E-posta adresi", + "placeholder": "name@example.com", + "invalid": "E-posta adresini yaz, örneğin name@example.com.", + "sending": "Gönderiliyor…", + "send": "Kodu gönder", + "haveAccount": "Zaten hesabın var mı? Giriş yap", + "newHere": "AnyPlay’de yeni misin? Hesap oluştur" + }, + "code": { + "title": "E-postana bak", + "lead": "{email} adresine 6 haneli bir kod gönderdik. 10 dakika geçerli.", + "incomplete": "E-postadaki 6 haneyi gir.", + "checking": "Denetleniyor…", + "resent": "Yeni bir kod yolda.", + "noMail": "E-posta gelmedi mi?", + "resendIn": "Yeni kodu {seconds} sn sonra gönder", + "resend": "Yeni kod gönder" + }, + "details": { + "title": "Bir ad seç", + "lead": "{email} adresin onaylandı. Diğer oyuncular adını görür.", + "handle": "Ad", + "handleHint": "3 ile 16 arasında harf, rakam, tire veya alt çizgi kullan. Bir harfle başla.", + "ageHint": "Yani 16 yaşında ya da daha büyüksün.", + "age": "Koşulları tek başıma kabul edecek yaştayım.", + "terms": "Koşulları (sürüm {version}) kabul ediyorum ve gizlilik bildirimini okudum.", + "creating": "Oluşturuluyor…", + "submit": "Hesap oluştur" + }, + "terms": { + "title": "Koşulları gözden geçir", + "lead": "Koşullar değişti. Koşulları oku ve devam etmek için {version} sürümünü kabul et.", + "accept": "Koşulları kabul ediyorum (sürüm {version}).", + "required": "Devam etmek için koşulları kabul et.", + "submit": "Kabul et ve devam et" + }, + "banned": { + "title": "Bu hesap engellendi", + "leadUntil": "Engel {date} tarihinde bitiyor.", + "leadForever": "Engel kendiliğinden bitmez.", + "rule": "Kural", + "reason": "Sebep", + "appeal": "Açıklamayı oku ve itiraz et" + }, + "handoff": { + "title": "Giriş yapıldı", + "lead": "Seni AnyPlay’e geri götürüyoruz. Bir şey olmazsa düğmeyi kullan.", + "open": "AnyPlay’i aç", + "note": "AnyPlay seni giriş yapmış olarak gösterdiğinde bu sekmeyi kapatabilirsin." + }, + "done": { + "title": "Giriş yaptın", + "inviteLead": "Seni davete geri götürüyoruz.", + "welcome": "Hoş geldin, {handle}. Arkadaşların, partilerin ve kupaların başlatıcıda seni bekliyor.", + "account": "Hesabına git" + }, + "join": { + "expiredTitle": "Bu davetin süresi doldu", + "expiredLead": "Bağlantının süresi dolmuş ya da oyuncu oynamayı bırakmış olabilir. Yenisini iste.", + "title": "{handle} seni davet etti", + "leadSignedIn": "Daveti görmek için PC’nde AnyPlay’i aç. Katılıp katılmayacağına orada sen karar verirsin.", + "leadSignedOut": "Önce giriş yap ya da hesap oluştur. Daveti açmak için buraya geri dönersin.", + "party": "Parti", + "game": "Oyun", + "partyName": "Bir AnyPlay partisi", + "gameFallback": "Bir oyun", + "joined": "Katılanlar", + "players": "{members} / {max, plural, other {# oyuncu}}", + "open": "AnyPlay’de aç", + "noApp": "Bu PC’de henüz AnyPlay yok mu? Önce indir, sonra bu sayfayı yeniden aç." + }, + "link": { + "expiredTitle": "Bu bağlantının süresi doldu", + "expiredLead": "Hesap sayfanı AnyPlay’den yeniden aç.", + "close": "Bu sekmeyi kapatabilirsin.", + "title": "Hesabını aç", + "lead": "PC’ndeki AnyPlay, {handle} hesabının bu tarayıcıda açılmasını istedi.", + "open": "Hesabı aç", + "opening": "Açılıyor…", + "notYou": "Bunu sen istemedin mi? Sekmeyi kapat." + } +} diff --git a/apps/web/messages/tr/common.json b/apps/web/messages/tr/common.json new file mode 100644 index 0000000..3418d30 --- /dev/null +++ b/apps/web/messages/tr/common.json @@ -0,0 +1,20 @@ +{ + "errors": { + "generic": "Bir şeyler ters gitti. Yeniden dene.", + "network": "AnyPlay’e ulaşılamıyor. Bağlantını kontrol edip yeniden dene.", + "internal": "AnyPlay’in tarafında bir sorun oldu. Bir dakika sonra yeniden dene.", + "unauthorized": "Oturumun sona erdi. Yeniden giriş yap.", + "forbidden": "Bunu bu hesapla yapamazsın.", + "banned": "Bu hesap engellendi.", + "not_found": "Bu artık yok.", + "limit": "Bunun için sınıra ulaştın. Önce birini kaldır.", + "too_large": "Bu göndermek için fazla büyük.", + "code_wrong": "Bu kod yanlış. Kontrol edip yeniden dene.", + "code_expired": "Bu kodun süresi doldu. Yeni bir kod iste.", + "handle_taken": "Bu kullanıcı adı alınmış. Başka bir tane dene.", + "handle_invalid": "3 ile 16 arasında harf, rakam, tire veya alt çizgi kullan. Bir harfle başla.", + "rate_limited": "Çok fazla deneme. Biraz bekleyip yeniden dene.", + "rate_limited_wait": "Çok fazla deneme. {seconds, plural, one {# saniye} other {# saniye}} bekleyip yeniden dene.", + "passkey_unsupported": "Bu tarayıcı geçiş anahtarlarını kullanamıyor. Bunun yerine e-posta kodunu kullan." + } +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 5e33406..8f0c33d 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; /** * Hardening, because the 2025-26 Next.js advisories are real and every self-hoster would have to @@ -47,4 +48,7 @@ const config: NextConfig = { }, }; -export default config; +// The words come from messages// (src/i18n); no language in the path, the proxy picks one. +const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); + +export default withNextIntl(config); diff --git a/apps/web/package.json b/apps/web/package.json index 9c294ad..2b54515 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,6 +23,7 @@ "@tanstack/react-query": "5.104.4", "lenis": "1.3.26", "next": "16.4.2", + "next-intl": "4.14.7", "react": "19.3.1", "react-dom": "19.3.1" }, diff --git a/apps/web/src/app/(account)/account/connections/page.tsx b/apps/web/src/app/(account)/account/connections/page.tsx index 00f5ab2..95a6417 100644 --- a/apps/web/src/app/(account)/account/connections/page.tsx +++ b/apps/web/src/app/(account)/account/connections/page.tsx @@ -1,9 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { Suspense } from "react"; import { ConnectionsPage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Connections" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_connections") }; +} export default function Page() { return ( diff --git a/apps/web/src/app/(account)/account/data/page.tsx b/apps/web/src/app/(account)/account/data/page.tsx index 0cbbc38..20b6347 100644 --- a/apps/web/src/app/(account)/account/data/page.tsx +++ b/apps/web/src/app/(account)/account/data/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { DataPage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Your data" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_data") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/friends/page.tsx b/apps/web/src/app/(account)/account/friends/page.tsx index a9ace41..5138986 100644 --- a/apps/web/src/app/(account)/account/friends/page.tsx +++ b/apps/web/src/app/(account)/account/friends/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { FriendsPage } from "@/components/account/social"; -export const metadata: Metadata = { title: "Friends" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_friends") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/layout.tsx b/apps/web/src/app/(account)/account/layout.tsx index c2cb2e3..329a0fb 100644 --- a/apps/web/src/app/(account)/account/layout.tsx +++ b/apps/web/src/app/(account)/account/layout.tsx @@ -1,16 +1,27 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { NextIntlClientProvider } from "next-intl"; +import { getMessages, getTranslations } from "next-intl/server"; import { connection } from "next/server"; import type { ReactNode } from "react"; import { AccountShell } from "@/components/account/shell"; -export const metadata: Metadata = { - title: { default: "Your account", template: "%s · Your account · AnyPlay" }, - robots: { index: false }, -}; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.meta"); + return { + title: { default: t("title"), template: t("template") }, + robots: { index: false }, + }; +} export default async function AccountLayout({ children }: { children: ReactNode }) { // Per request, so Next's inline scripts carry the nonce of the script policy (src/proxy.ts). await connection(); - return {children}; + // The account pages add the auth words too: a sensitive change asks for a code or a passkey. + const { common, account, auth } = (await getMessages()) as Record>; + return ( + + {children} + + ); } diff --git a/apps/web/src/app/(account)/account/notifications/page.tsx b/apps/web/src/app/(account)/account/notifications/page.tsx index f8a9a01..cd1d399 100644 --- a/apps/web/src/app/(account)/account/notifications/page.tsx +++ b/apps/web/src/app/(account)/account/notifications/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { NotificationsPage } from "@/components/account/notifications"; -export const metadata: Metadata = { title: "Notifications" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_notifications") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/page.tsx b/apps/web/src/app/(account)/account/page.tsx index 87b23f5..7508016 100644 --- a/apps/web/src/app/(account)/account/page.tsx +++ b/apps/web/src/app/(account)/account/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { OverviewPage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Overview" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_overview") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/privacy/page.tsx b/apps/web/src/app/(account)/account/privacy/page.tsx index dfee064..5b715f5 100644 --- a/apps/web/src/app/(account)/account/privacy/page.tsx +++ b/apps/web/src/app/(account)/account/privacy/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { PrivacyPage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Privacy" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_privacy") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/profile/page.tsx b/apps/web/src/app/(account)/account/profile/page.tsx index 6cbee78..be3c1e2 100644 --- a/apps/web/src/app/(account)/account/profile/page.tsx +++ b/apps/web/src/app/(account)/account/profile/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { ProfilePage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Profile" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_profile") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/security/page.tsx b/apps/web/src/app/(account)/account/security/page.tsx index 775352b..3ac4aa8 100644 --- a/apps/web/src/app/(account)/account/security/page.tsx +++ b/apps/web/src/app/(account)/account/security/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { SecurityPage } from "@/components/account/pages"; -export const metadata: Metadata = { title: "Security" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_security") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(account)/account/trophies/page.tsx b/apps/web/src/app/(account)/account/trophies/page.tsx index 11c5196..df50e6c 100644 --- a/apps/web/src/app/(account)/account/trophies/page.tsx +++ b/apps/web/src/app/(account)/account/trophies/page.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { TrophiesPage } from "@/components/account/social"; -export const metadata: Metadata = { title: "Trophies" }; +export async function generateMetadata(): Promise { + const t = await getTranslations("account.shell"); + return { title: t("nav_trophies") }; +} export default function Page() { return ; diff --git a/apps/web/src/app/(auth)/create/page.tsx b/apps/web/src/app/(auth)/create/page.tsx index d60038d..43fb314 100644 --- a/apps/web/src/app/(auth)/create/page.tsx +++ b/apps/web/src/app/(auth)/create/page.tsx @@ -1,15 +1,19 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { connection } from "next/server"; import { AuthFlow } from "@/components/auth/auth-flow"; import { AuthShell } from "@/components/auth/auth-shell"; import { safeNext } from "@/lib/join"; -export const metadata: Metadata = { - title: "Create your account", - description: "Create your AnyPlay account: an e-mail address and a handle.", - robots: { index: false }, -}; +export async function generateMetadata(): Promise { + const t = await getTranslations("auth.meta.create"); + return { + title: t("title"), + description: t("description"), + robots: { index: false }, + }; +} export default async function Page({ searchParams }: { searchParams: Promise<{ next?: string | string[] }> }) { // Per request, so Next's inline scripts carry the nonce of the script policy (src/proxy.ts). diff --git a/apps/web/src/app/(auth)/join/[code]/page.tsx b/apps/web/src/app/(auth)/join/[code]/page.tsx index b0bfd61..698eebd 100644 --- a/apps/web/src/app/(auth)/join/[code]/page.tsx +++ b/apps/web/src/app/(auth)/join/[code]/page.tsx @@ -1,17 +1,21 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { connection } from "next/server"; import { AuthShell } from "@/components/auth/auth-shell"; import { JoinFlow } from "@/components/auth/join-flow"; // The address holds a join code. It stays out of search results, and out of the Referer of // everything this page links to or loads. -export const metadata: Metadata = { - title: "Invitation", - description: "An invitation to play together on AnyPlay.", - robots: { index: false, follow: false }, - referrer: "no-referrer", -}; +export async function generateMetadata(): Promise { + const t = await getTranslations("auth.meta.join"); + return { + title: t("title"), + description: t("description"), + robots: { index: false, follow: false }, + referrer: "no-referrer", + }; +} export default async function Page({ params }: { params: Promise<{ code: string }> }) { // Per request, so Next's inline scripts carry the nonce of the script policy (src/proxy.ts). diff --git a/apps/web/src/app/(auth)/layout.tsx b/apps/web/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..eda9809 --- /dev/null +++ b/apps/web/src/app/(auth)/layout.tsx @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-only +import { NextIntlClientProvider } from "next-intl"; +import { getMessages } from "next-intl/server"; +import type { ReactNode } from "react"; + +/** Sign-in, sign-up, device link and join: the shared words and the auth words, in the visitor's language. */ +export default async function AuthLayout({ children }: { children: ReactNode }) { + const { common, auth } = (await getMessages()) as Record>; + return {children}; +} diff --git a/apps/web/src/app/(auth)/link/page.tsx b/apps/web/src/app/(auth)/link/page.tsx index fb37341..5bd8ada 100644 --- a/apps/web/src/app/(auth)/link/page.tsx +++ b/apps/web/src/app/(auth)/link/page.tsx @@ -1,14 +1,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { connection } from "next/server"; import { AuthShell } from "@/components/auth/auth-shell"; import { LinkFlow } from "@/components/auth/link-flow"; -export const metadata: Metadata = { - title: "Open your account", - description: "Open your AnyPlay account in this browser, from the launcher.", - robots: { index: false }, -}; +export async function generateMetadata(): Promise { + const t = await getTranslations("auth.meta.link"); + return { + title: t("title"), + description: t("description"), + robots: { index: false }, + }; +} export default async function Page({ searchParams }: { searchParams: Promise<{ c?: string | string[] }> }) { // Per request, so Next's inline scripts carry the nonce of the script policy (src/proxy.ts). diff --git a/apps/web/src/app/(auth)/sign-in/page.tsx b/apps/web/src/app/(auth)/sign-in/page.tsx index c3ad816..0597832 100644 --- a/apps/web/src/app/(auth)/sign-in/page.tsx +++ b/apps/web/src/app/(auth)/sign-in/page.tsx @@ -1,15 +1,19 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { connection } from "next/server"; import { AuthFlow } from "@/components/auth/auth-flow"; import { AuthShell } from "@/components/auth/auth-shell"; import { safeNext } from "@/lib/join"; -export const metadata: Metadata = { - title: "Sign in", - description: "Sign in to AnyPlay with a passkey or a code by e-mail.", - robots: { index: false }, -}; +export async function generateMetadata(): Promise { + const t = await getTranslations("auth.meta.signIn"); + return { + title: t("title"), + description: t("description"), + robots: { index: false }, + }; +} export default async function Page({ searchParams }: { searchParams: Promise<{ next?: string | string[] }> }) { // Per request, so Next's inline scripts carry the nonce of the script policy (src/proxy.ts). diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index ad689c2..d696311 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,4 +1,6 @@ import type { Metadata, Viewport } from "next"; +import { NextIntlClientProvider } from "next-intl"; +import { getLocale, getMessages } from "next-intl/server"; import { connection } from "next/server"; import { Monitoring } from "@/components/monitoring"; import { SiteProvider } from "@/components/site-context"; @@ -27,10 +29,15 @@ export const viewport: Viewport = { themeColor: "#0b0a09", colorScheme: "dark li export default async function RootLayout({ children }: { children: React.ReactNode }) { const site = readSite(); + const locale = await getLocale(); + // Only the shared words go to every page; the account flows add theirs in their own layouts. + const { common } = (await getMessages()) as { common: Record }; return ( - + - {children} + + {children} + diff --git a/apps/web/src/app/u/layout.tsx b/apps/web/src/app/u/layout.tsx new file mode 100644 index 0000000..82e0e56 --- /dev/null +++ b/apps/web/src/app/u/layout.tsx @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-only +import { NextIntlClientProvider } from "next-intl"; +import { getMessages } from "next-intl/server"; +import type { ReactNode } from "react"; + +/** Public profiles: the shared words and the account words, in the visitor's language. */ +export default async function ProfileLayout({ children }: { children: ReactNode }) { + const { common, account } = (await getMessages()) as Record>; + return {children}; +} diff --git a/apps/web/src/components/account/compare.tsx b/apps/web/src/components/account/compare.tsx index 23c8b5d..7e90d62 100644 --- a/apps/web/src/components/account/compare.tsx +++ b/apps/web/src/components/account/compare.tsx @@ -1,12 +1,14 @@ // SPDX-License-Identifier: AGPL-3.0-only "use client"; -import { type CompareSide, type PublicUser, type TrophyCompare, describeError, isApiError } from "@anyplay/api-client"; +import { type CompareSide, type PublicUser, type TrophyCompare, isApiError } from "@anyplay/api-client"; +import { useFormatter, useTranslations } from "next-intl"; import Link from "next/link"; import { type ReactNode, useEffect, useId, useState } from "react"; import { Failed } from "./pages"; import { GRADES } from "./social"; import { Card, Empty, Notice, smallButton } from "./ui"; +import { useDescribeError } from "@/i18n/errors"; import { loadApi } from "@/lib/api"; /** @@ -16,8 +18,6 @@ import { loadApi } from "@/lib/api"; * would be a column of digits that says nothing without the names. */ -const num = (n: number) => n.toLocaleString("en"); - type Shared = | { status: "loading" } | { status: "ready"; titles: string[] } @@ -30,10 +30,10 @@ type Result = | { status: "hidden" } | { status: "failed"; message: string }; -const HIDDEN = (handle: string) => `${handle}’s trophies aren’t available to compare.`; - /** The compare card of a profile; the page shows it only when the player's trophies are visible. */ export function CompareCard({ user }: { user: PublicUser }) { + const t = useTranslations("account.compare"); + const describeError = useDescribeError(); const { handle } = user.account; const [shared, setShared] = useState({ status: "loading" }); const [attempt, setAttempt] = useState(0); @@ -61,14 +61,14 @@ export function CompareCard({ user }: { user: PublicUser }) { return () => { alive = false; }; - }, [handle, attempt]); + }, [handle, attempt, describeError]); - const description = shared.status === "ready" && shared.titles.length > 0 ? `You and ${handle}, in a game you both have.` : `Your trophies next to ${handle}’s.`; + const description = shared.status === "ready" && shared.titles.length > 0 ? t("descriptionShared", { handle }) : t("description", { handle }); let body: ReactNode; if (shared.status === "loading") { body = (
- Loading + {t("loading")}
@@ -84,16 +84,16 @@ export function CompareCard({ user }: { user: PublicUser }) { /> ); } else if (shared.status === "hidden") { - body = {HIDDEN(handle)}; + body = {t("hidden", { handle })}; } else if (shared.titles.length === 0 || !title) { - body = You and {handle} have no game in common yet. Play a game {handle} has played and it shows up here.; + body = {t("empty", { handle })}; } else { body = (
{shared.titles.length > 1 ? (